API Docs for:
Show:

File: generic-list-search.js

/*******************************************************************************
 * Copyright (c) 2017 Genialist Software Ltd.
 * All rights reserved.
 ******************************************************************************/

const pSearch = new (function() {
	var that = this, library_id = "", lv;
		
	function f_initKeys() {
		pDocumenti.removeOnKeyDown(that.close, pKeys.escape, true, 'search-escape'); // avoid duplicate entries
		pDocumenti.addOnKeyDown(that.close, pKeys.escape, true, 'search-escape');
	};
	
	function init(p_library_id) {
		library_id = p_library_id;
		lv = new pListViewInstance(p_library_id, 'search-results', pDocument.getStyleValue('st-search-results-title'));
		lv.show_list_name_values = true;
		lv.homevideos = false;
		lv.filter = function(i) { return pResources.get('library.list.('+i.lid+').enabled') != 'false'; };
		
		pDocumenti.addOnKeyDown(that.focus, pKeys.f3);
	};
	
	this.close = function() {
		pSearch.closeSearch('search-results');
	};
	
	this.focus = function() {
		if (!pElement.focus('form-search-text'))
			pElement.focus('form-mobile-search-text');
	};
	
	this.doSearch = function(t) {
		if (!pString.v(t)) {
			t = pElement.getValue('form-search-text');
			if (!pString.v(t))
				t = pElement.getValue('form-mobile-search-text');

			if (!pString.v(t) && localStorage)
				t = localStorage.getItem('search.text');
		}
		
		if (pString.v(t)) {
			that.text = t.trim();
				
			var x = pElement.x('search-results');
			if (x) {
				
				if (x = pElement.x('window-play'))
					x.style.maxHeight = 'none';
	
				pDocument.showComponent('search-results', 'search-hide');
	
				lv.show();
				lv.open(pURL.addParam(url_prefix+library_id+'/movies.movies/search', 'text', t), function(p_lv, p_results) {
					pDocument.setHistoryState("search", { results: p_results });
				});
				
				f_initKeys();
			}
		}
	};
	
	this.closeSearch = function(p_id) {
		
		if (window.f_resize_window_play) 
			window.f_resize_window_play();
		
		//pDocument.hideSmooth(p_id);
		pDocument.hideComponent(p_id);

		pDocument.setHistoryState("search", null);
		
		lv.close();
		pDocumenti.removeOnKeyDown(pSearch.close, pKeys.escape);
		
	};
	
	this.render = function(p_results) {
		var x = pElement.x('search-results');
		if (x) {
			pDocument.showComponent('search-results', 'search-hide');

			lv.show();
			lv.render(p_results);
			
			f_initKeys();
		}
	};
	
	// "text" property
	Object.defineProperty(this, "text", {
		get: function() { return localStorage? localStorage.getItem('search.text') : null; },
		set: function(v) { if (localStorage) localStorage.setItem('search.text', v); }
	});

	this.query = function() {
		
		var d = {
			id: 'search',
			options: [
				{ id: 'search-text', input: { 
						validators: /^.+$/, pattern: ".+", required: true, spellcheck: false, autocomplete: true, autocorrect: false, autocapitalize: false, 
						placeholder: 'Search...', 
						oninput: function(){ 
							var id = this.getAttribute('attr_dialog_id');
							pDialog.validate(id); 
							pSearch.text = pDialog.getInputValue(id, 'name'); 
						} 
					}, 
					value: that.text || '' 
				},
				'ok', 'cancel'
			],
			fcall: function(did, v) {
				that.doSearch(did.inputs.getValue('search-text', ''));
			}
		};
		pDialog.dialogQuestion(d);
		
	};

	pDocument.addHistoryStateHandler(function(s) { if (s.search && s.search.results) pSearch.render(s.search.results); });

	init(pMediaLibrary.getLibraryIDfromURL());
});

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

function pLVImageVersion(t) {
	var that = this, type = t, listeners_ids = [];
		
	function f_update(lid, id, v) {
		var v = (v || '').split('|')/*p = e.key.split('_'), lid = p[2], id = p[3]*/, x = pElement.x(lid+'.'+id);
		if (x) {
			var vv = (v.length>0 && pString.v(v[0]))? v[0] : pImageVersions.version(type, lid, id);
			if (vv != 'null') {
				var i_nurl = url_prefix+pMediaLibrary.getLibraryIDfromURL()+'/resources/'+(v.length>1? v[1] : pImageVersions.URL(type, lid, id));
				if (!i_nurl.endsWith('undefined')) {
					var i_turl = pImageVersions.addVersion(i_nurl || x.getAttribute('data-'+type+'_url'), vv); 
					
					pConsole.info(lid+'-'+id+'('+type+') -> ' + i_turl);
					
					x.setAttribute('data-'+type+'_url', i_turl);
					pListViewItem.thumbnail.update(x);
				}
			}
		}
	}
	
	function f_removeListener(id) {
		pImageVersions.removeListener(id, f_update);
	}
	
	function f_refreshOne(id) {
		id = id.split('_');
		f_update(id[id.length-2], id[id.length-1]);
	}
	
	function f_refreshAll() {
		listeners_ids.forEach(f_refreshOne);
	}
	
	this.addListener = function(lid, id) {
		var eid = pImageVersions.versionListenerID(type, lid, id);
		listeners_ids.push(eid);
		pImageVersions.addListener(eid, f_update);
	};
	
	this.clear = function() {
		listeners_ids.forEach(f_removeListener);
		listeners_ids = [];
	};
	
	//*** REFRESH ON PAGE SHOW (when history changes)
	function f_autoRefresh() {
		f_refreshAll();
		
		window.removeEventListener('pageshow', f_autoRefresh);
		window.removeEventListener('pageshow', f_refreshAll);
	};
	window.addEventListener('pageshow', f_autoRefresh);
};

/******************************************************************************/

const classnames_lv_text = [ 'tableview-text', 'listview-text', 'listview-text-selected' ];

function pListViewInstance(library_id, pid, p_title, p_lines) {
	var that = this, id = pid/*''+Math.random()*/, m_pageOffset = 0, m_lines = p_lines || -1, sorted = false, startCookieName = 's-'+pLocation.getPath().substring(url_prefix.length)+'?page_min='+pLocation.getQueryParameter('page_min', '1');

	this.items = [];
	
	this.library_id = library_id;
	this.element_id = pid;
	this.title = p_title;
	
	this.homevideos = false;

	// "lines" property
	Object.defineProperty(this, "lines", { get: function() { return m_lines; } });
	
	this.v_artwork = new pLVImageVersion('thumbnail');
	this.v_backdrop = new pLVImageVersion('backdrop');
	this.v_poster =new pLVImageVersion('poster');
	this.v_banner = new pLVImageVersion('banner');
	
	this.show_list_name_values = false;
	if (id)
		objects.put(id, this);

	var key_installed;
	function f_keys(e, k) {
		if (pKeys.pure(e)) {
			pConsole.debug(that, id+' KeyCode: ' + k);
			switch (k) {
				case pKeys.down: return that.goDown();
				case pKeys.up:	 return that.goUp();
				case pKeys.right: return that.goRight();
				case pKeys.left:	return that.goLeft();
			}
		}
	}

	function f_uninstallKeys() {
		if (key_installed) {
			pDocumenti.removeOnKeyDown(f_keys, null, 'lv.'+id);
			key_installed = false;
		}
	};
		
	this.installKeys = function() {
		if (!key_installed) {
			pDocumenti.addOnKeyDown(f_keys, null, true, 'lv.'+id);
			key_installed = true;
		}
	};
		
	// "busy" property
	var m_busy = false;
	Object.defineProperty(this, "busy", { get: function() { return m_busy; }, set: function(v) { 
		if (typeof v == "boolean") 
			if (m_busy = v) {
				pElement.setInnerHTML(
					'generic-list-content-'+id,
					'<div id="busy.'+that.element_id+'" class="td-busy"><span width="100%" align="center"><img src="/resources/html/images/line-wait.gif"/></span></div>');
				that.clear();
			} 
	}});
	
	this.clear = function() {
		f_uninstallKeys();
		
		that.v_artwork.clear();
		that.v_backdrop.clear();
		that.v_poster.clear();
		that.v_banner.clear();
	};
	
	this.close = function() {
		that.clear();
	};
	
	var divs = [];
	this.removeAllDivs = function() {
		divs = [];
	};
	
	this.addDiv = function(id) {
		divs.push({ id: id });
	};
		
	this.addDivsFrom = function(x) {
		if (x = pElement.x(x))
			[].forEach.call(x.children, function(x) { if (pElement.hasClassName(x, 'listview-item')) divs.push({ id: x.id }) });
	};
	
	this.goFirst = function(s, r) {
		var dd = divs;
		if (dd.length>0) {
			if (dd.length==1)
				return pElement.go('a.'+dd[0].id, dd[0].id);

			if (r === true) {
				for(var j=s ; j>=0 ; j--) {
					var d = dd[j], x = pElement.x(d.id);
					if (x && pElement.checkDisplayed(x))
						return pElement.go('a.'+d.id, x);
				}
			}
			else if (dd.find(function(d, j) {
				if (!s || j>=s) {
					var x = pElement.x(d.id);
					if (x && pElement.checkDisplayed(x))
						return pElement.go('a.'+d.id, x);
				}
			}))
				return true;
		}
	};
	
	this.goDown = function() {
		var dd = divs;
		if (dd.length>0) {
			if (dd.length==1)
				return pElement.go('a.'+dd[0].id, dd[0].id);
			
			var f = pDocumenti.activeElement();
			if (f && pElement.hasClassName(f, classnames_lv_text)) {
				var i = dd.findIndex(function(d) { return 'a.'+d.id == f.id });
				if (i>=0 && i<dd.length-1) {
					var y = pDocument.top(dd[i].id), m = pDocument.middle(dd[i].id);
					
					if (dd.find(function(d, j) {
						var x = pElement.x(d.id);
						if (x && pElement.checkDisplayed(x)) {
							//console.log(y + ' - ' +pDocument.top(d.id) + ' ' + m + ' ' + pDocument.middle(d.id) + ' ' + (pDocument.middle(d.id)-m));
							var dm = pDocument.middle(x)-m;
							if (j>i && pDocument.top(x)>y && dm<3 && dm>-3)
								return pElement.go('a.'+d.id, x);
						}
					}))
						return true;
				}
			}
			else
				return that.goFirst();
		}
	};
	
	this.goUp = function() {
		var dd = divs;
		if (dd.length>0) {
			if (dd.length==1)
				return pElement.go('a.'+dd[0].id, dd[0].id);

			var f = pDocumenti.activeElement();
			if (f && pElement.hasClassName(f, classnames_lv_text)) {
				var i = dd.findIndex(function(d) { return 'a.'+d.id == f.id });
				if (i>0) {
					var y = pDocument.top(dd[i].id), m = pDocument.middle(dd[i].id);
					
					for(var j = i-1 ; j>=0 ; j--) {
						var d = dd[j], x = pElement.x(d.id);
						if (x && pElement.checkDisplayed(x)) {
							var dm = pDocument.middle(x)-m;
							if (pDocument.top(x)<y && dm<3 && dm>-3)
								return pElement.go('a.'+d.id, x);
						}
					}
				}
			}
			else
				return that.goFirst();
		}
	};
	
	this.goRight = function() {
		var dd = divs;
		if (dd && dd.length>0) {
			if (dd.length==1)
				return pElement.go('a.'+dd[0].id, dd[0].id);

			var f = pDocumenti.activeElement();
			if (f && pElement.hasClassName(f, classnames_lv_text)) {
				var i = dd.findIndex(function(d) { return 'a.'+d.id == f.id });
				if (i>=0 && i<dd.length-1)
					return that.goFirst(i+1);
			}
			else
				return that.goFirst();
		}
	};
	
	this.goLeft = function() {
		var dd = divs;
		if (dd && dd.length>0) {
			if (dd.length==1)
				return pElement.go('a.'+dd[0].id, dd[0].id);
			
			var f = pDocumenti.activeElement();
			if (f && pElement.hasClassName(f, classnames_lv_text)) {
				var i = dd.findIndex(function(d) { return 'a.'+d.id == f.id });
				if (i>0)
					return that.goFirst(i-1, true);//pElement.go('a.'+dd[i-1].id, dd[i-1].id);
			}
			else
				return that.goFirst();//pElement.go('a.'+dd[0].id, dd[0].id);
		}
	};

	this.show = function() {
		//*** SET DEFAULT CONTENT
		var h = '<div id="generic-list-'+id+'" class="table-generic-list box">';
		
		if (pString.v(that.title))
			h += '<div class="listview-title"><span class="selected">'+pString.encodeHTML(that.title)+'</span></nobr></div>';
		
		h += '<div class="listview-close"><span class="link close noselect" onclick="javascript:pSearch.closeSearch(\''+id+'\')">&times;</span></div>';
		h += '<div id="generic-list-content-'+id+'" class="listview-content"/>';

		pElement.setInnerHTML(that.element_id, h);

		//*** SHOW AS BUSY
		that.busy = true;
	};

	var open_url_request, responseText;
	this.open = function(url, f_callback) {

		//*** SEARCH THE DATA
		pConsole.info(that, "GetList: " + url);
		open_url_request = { id: ''+Math.random() };
		pHTTPRequest.invoke({
			id: open_url_request.id,
			url: url,
			onreadystatechange: function(request, data, p_request) {

				if (request.status!=200)
					return; //TODO: ...

				//requires login
				if (request.status==200 && pROSE.isErrorPage(request)) {
					window.location.replace("/login?url="+encodeURI(pLocation.getPathAndQuery())+"&expired=true");
					return false;
				}

				// other errors
				if ((request.status!=200 && request.status!=304) || pROSE.isErrorPage(request)) {
					pROSE.showErrorPage(request.responseText);
					return false;
				}

				//if (pROSE.handleHTTPResponse(request)===false)
				//	return;

				var lv = this.lv;
				if (p_request.id === open_url_request.id) {

					//try {
					if (that.busy || !responseText || responseText != request.responseText){
						responseText = request.responseText;
						var obj = pJSON.parse(request.responseText);
						
						lv.items = (lv.filter? obj.reply.filter(lv.filter) : obj.reply).filter(pObject.isNotNull);
						if (obj.reply.length>0)
							lv.show();
						lv.render();
												
						if (this.fcall)
							this.fcall(lv, lv.items);
					}
						//	}
					/*}
					catch(exception) {
						alert("Exception: " + exception + "\nReply: " + request.responseText);
						pConsole.error('Search', "Exception: " + exception + "\nReply: " + request.responseText);
					}*/
				}
			}.bind({ lv: this, fcall: f_callback }),
		});
		//open_url_request.id = ''+Math.random();
	};

	this.onPageResize = function() {
		that.busy = true;
		that.render();
	};

	this.render = function(p_items) {
		that.busy = false;
		
		if (p_items)
			that.items = p_items.filter(pObject.isNotNull);

		var x = pElement.x(that.element_id);
		if (x && x.style.display != "none") {
	
			if (x = pElement.x('generic-list-'+that.element_id))
				x.style.display = "block";
	
			that.renderImpl('ALL');
		}
	};

	this.nextPage = function(p_start) {
		that.busy = true;

		var d = m_list_render.getValue(that.element_id, null);
		if (d) {
			that.renderImpl(d.m_id, p_start);
			pCookieManager.set(startCookieName, ''+p_start);
		}
	};

	this.nextTablePage = function(p_start) {
		that.busy = true;

		var d = m_list_render.getValue(that.element_id, null);
		if (d) {
			that.renderTable(d.m_id, p_start);
			pCookieManager.set(startCookieName, ''+p_start);
		}
	};
	
	this.initRange = function(i_hid, p_id, items, m_max, p_start) {
		if (null==p_start) {
			p_start = m_list_page.getValue(p_id, null);
			if (p_start && p_start.max==m_max)
				p_start = p_start.start;
			else
				p_start = pCookieManager.getCookie(startCookieName);

			if (p_start)
				p_start = parseInt(p_start);
			else
				p_start = pLocation.getQueryParameter('start', '0');
		}
		else
			p_start = parseInt(p_start);

		if (p_start >= items.length || p_start<0)
			p_start = 0;
		if ((p_start % m_max)>0)
			p_start = 0;

		m_list_render.put(i_hid, { m_id: p_id, library_id: that.library_id, albums: items, lines: -1});
		m_list_page.put(p_id, { max: m_max, start: p_start });
		
		return p_start;
	};
	
	this.b_thumb;
	this.b_name;
	this.wi = 0;
	
	/**
	 * @param p_id ???
	 */	
	this.renderImpl = function(p_id, p_start) {
		
		that.b_thumb = pApplicationUI.OPTION_THUMB_SHOW_THUMB;
		that.b_name = pApplicationUI.OPTION_THUMB_SHOW_NAME;
		
		var i_hid = id, i_albums = that.items;

		//console.log(pElement.x('generic-list-content-'+i_hid).getBoundingClientRect());
		//console.log('w: ' + pDocument.width('generic-list-content-'+i_hid) + ' ' + pDocument.innerWidth('generic-list-content-'+i_hid) + ' ' + window.innerHeight);
		
		/*var w = pDocument.innerWidth('generic-list-content-'+i_hid) - 17;
		wi = 0;
		if (w>300) {
			for(var wi = 120 ; wi >= 100 ; wi--) {
				var n = parseInt(w / wi);
				var owi = wi + ((w % 100) / n);
				console.log('w: ' + w + ' ' + n + ' ' + owi + ' ' + (w % owi))
			}
		}
		wi = 0;*/
		
		if (window.pMoviePlaylist)
			pMoviePlaylist.clear();
		
		//*** HOME VIDEOS
		if (that.homevideos || (window.pPage && window.pPage.homeVideos)) {
			if (null==p_start) {
				p_start = m_list_page.getValue(p_id, null);
				if (p_start)
					p_start = p_start.start;
				else
					p_start = pCookieManager.getCookie(startCookieName);
					if (!p_start)
						p_start = pLocation.getQueryParameter('start', '0');
			}
			if (p_start >= i_albums.length)
				p_start = 0;

			m_list_render.put(i_hid, { m_id: p_id, library_id: that.library_id, albums: i_albums, lines: m_lines});
			m_list_page.put(p_id, { start: p_start });

			this.renderHomeVideoList(p_id, p_start);
			return;
		}

		const m_max = pApplicationUI.OPTION_PAGE_MAXSIZE;

		//*** INIT RANGE
		if (m_lines != 1)
			p_start = this.initRange(i_hid, p_id, i_albums, m_max, p_start);

		p_start = parseInt(p_start);
		var i_lid = "", h = '', i_images = [], i_divs = [];
		
		//*** FILTER
		i_albums = i_albums.filter(function(i) { return i && i.lid!='movies.groups' && i.lid.indexOf('podcast.')<0 });

		//*** PAGE TOOLBAR
		h += this.renderPageToolbar(i_albums, m_max, p_start, m_lines);

		//*** ADD PLAYLIST
		if ((i_albums.length>0 && i_albums[0].lid.indexOf('playlists.')==0) || pMediaLibrary.getLIDfromURL() === 'playlists.groups') {
			h += '<div id="new-playlist" class="listview-item" ondragstart="return false;" ondrop="return false;">';
			h += '<div class="listview-item-image">'
			h += '<a class="image tooltip" href="javascript:void(pMediaLibrary.createPlaylist())" tabindex="-1">';
			h += '<img class="image listview-image" src="/resources/html/images/32x32/script_add.png"/>';
			h += '</a>';
			h += '</div>';
			h += '<a id="a.new-playlist" class="listview-text" href="javascript:void(pMediaLibrary.createPlaylist())" title="Create a new playlist...">New Playlist...</a>';
			h += '</div>';
			
			i_divs[i_divs.length] = { id: 'new-playlist' };
		}

		//*** ITEMS
		var options = {
			tooltips: pDevice.isMobile(),
			buttons: pApplicationUI.OPTION_LISTVIEW_BUTTONS
		}, c = pElement.x('generic-list-content-'+i_hid);
		
		//pElement.removeAllChildren(c);
		for(var i=(m_lines != 1)? p_start : 0 ; i<i_albums.length ; i++) {
			if (m_lines != 1 && m_max>0 && i>=p_start+m_max) break;

			if (!pGenericList.acceptItem(i_albums[i])) continue;

			if (i_lid=="" || !equalsLID(i_lid, i_albums[i].lid)) {
				i_lid = i_albums[i].lid;
				if (this.show_list_name_values === true)
					h += '<h2>'+pGenericList.getListObjectNames(i_lid)+'</h2>';
			}
			else
				i_lid = i_albums[i].lid;

			if (i_lid == 'music.medias') {
				//alert('here');
				var i_playlist_items = [];
				for( ; i<i_albums.length ; i++)  {

					i_lid = i_albums[i].lid;
					if (i_lid != 'music.medias') {

						i_lid = 'music.medias';
						i--;
						break;
					}

					i_playlist_items[i_playlist_items.length] = i_albums[i];
				}
				//alert('here');
				h += this.renderMusicPlaylist(that.library_id, i_playlist_items);
				continue;
			}

			/** TODO: is-private */
			var i_result = null;
			if (i_albums[i].lid == 'radios.radios') {
				i_result = pListViewItem.render(this, i_albums[i], i, 
					'javascript:pRadio.click(objects.getValue(\''+id+'\'), '+i+');',
					'javascript:pRadio.open(objects.getValue(\''+id+'\'), '+i+');',
					'javascript:pRadio.play(objects.getValue(\''+id+'\'), '+i+');', options);
				
				pRadio.preload(this, i);
			}
			else
				i_result = pListViewItem.render(this, i_albums[i], i, pListViewItem.url(this, i_albums[i]), null, null, options);

			h += i_result.html;
			//c.appendChild(i_result.html);
			
			pArray.append(i_images, i_result.images || []);
			i_divs[i_divs.length] = i_result;
		}
		h += '<div style="clear: both;">';
		//c.appendChild(pElement.createClearDiv());

		//*** PAGE TOOLBAR
		h += this.renderPageToolbar(i_albums, m_max, p_start, m_lines);

		pElement.setInnerHTML('generic-list-content-'+id, h);
		
		i_images.forEach(function(i_img) {
			var image = new Image();
			image.setAttribute('img_id', i_img.id);
			image.onload = function() {
				pElement.setSrc(this.getAttribute('img_id'), this.src);
			};
			image.src = i_img.uri;
		});
		
		//*** UPDATE THUMBNAILS
		if (that.b_thumb)
			pListViewItem.thumbnail.update();
		
		//*** SET DROP TARGETS
		i_divs.forEach(function(d) {
			if (d.ondrop)
				pElement.setOnDrop(d.id, d.ondrop, null, d.dropfilter);
		});
		
		divs = i_divs;
		
		//*** INSTALL KEYBOARD
		that.installKeys();
	};
	
	this.renderPageToolbar = function(i_albums, m_max, p_start, p_lines, table) {
		var h = '';
		if (m_max > 0 && i_albums.length > m_max && p_lines != 1) {
			h += '<div class="listview-page">';
			for(var l=0 ; l<i_albums.length ; l+=m_max) {
				var i_max = l+m_max;
				if (i_max > i_albums.length)
					i_max = i_albums.length;

				if (l==p_start)
					h += '<div class="listview-item-page listview-item-page-selected selected">' + (m_pageOffset+l+1) + '-' + (m_pageOffset+i_max) + '</div>';
				else {
 					h += '<div class="listview-item-page"><a class="listview-item-page-link link" href="javascript:void(';
 					if (table)
 						h += 'objects.getValue(\''+id+'\').nextTablePage(\''+l+'\')';
 					else
 						h += 'objects.getValue(\''+id+'\').nextPage(\''+l+'\')';
					h += ')">' + (m_pageOffset+l+1) + '-' + (m_pageOffset+i_max) + '</a></div>';

				}
			}
			h += '</div>';
		}
		return h;
	};
	
	this.renderHomeVideoList = function(p_id, p_start) {

		//pMoviePlaylist.clear();

		if (!sorted) {
			that.items = that.items.sort(function(a, b) { if (a.release_date < b.release_date) return -1; if (a.release_date > b.release_date) return 1; return 0; });
			sorted = true;
		}

		var i_hid = that.element_id, i_albums = that.items, i_play = pROSE.getProp('view-user-advanced-play-homevideos');

		const m_max = pApplicationUI.OPTION_PAGE_MAXSIZE;
		var h = '', i_header = '', i_select = '', i_pager = '';

		//*** PAGE TOOLBAR
		var i_rdate = null, i_rdate_first = null, i_rdates = new pMap();

		i_header += '<div class="listview-page listview-page-homevideos">';
		i_albums.forEach(function(i_album) {
			var i_cdate = i_album.release_date;
			var i_pos = i_cdate.lastIndexOf('-');
			i_cdate = (i_cdate == "(Unknown)" || i_pos<0)? "(Unknown)" : i_cdate.substring(0, i_pos);

			if (i_rdate==null || i_rdate!=i_cdate) {
				i_rdate = i_cdate;
				if (!i_rdate_first) i_rdate_first = i_rdate;

				var i_month = '';
				try {
					i_month = pDate.toLocaleDateString(pDate.create(i_rdate+'-01'), null, options_long_ym, i_rdate);
					//i_month = pHomeVideosList.renderSubText(i_rdate);//pDate.toMonth(pDate.create(i_rdate));
				}
				catch(exception) {
					i_month = i_rdate;
				}
				if (!(typeof i_month=="string"))
					i_month = i_rdate;
				if (i_cdate == "(Unknown)")
					i_month = "(Unknown)";

				if (null==i_rdates.getValue(i_rdate, null)) {
					i_rdates.put(i_rdate, i_month);

					/*if (p_start == i_rdate+',0')
						i_header += '<div class="listview-item-page listview-item-page-selecte selected">'+i_month+'</div>';
					else {
						i_header += '<div class="listview-item-page"><a class="listview-item-page-link link" href="';
						i_header += 'javascript:objects.getValue(\''+id+'\').nextPage(\''+i_rdate+',0\')'
						i_header += '">' + i_month+'</a></div>';
					}*/
				}
			}
		});

		var i_keys = i_rdates.keys().slice(0).sort(), i_rdate_prev = null, i_rdate_next = null;
		for(var i=0 ; i<i_keys.length ; i++) {
			var i_rdate = i_keys[i];
			if (p_start == i_rdate+',0') {
				if (i+1<i_keys.length)
					i_rdate_next = i_keys[i+1];
				break;
			}
			i_rdate_prev = i_rdate;
		};

		if (i_rdate_prev) {
			i_header += '<div class="listview-item-page listview-item-page-link link previous" onclick="';
			i_header += 'objects.getValue(\''+id+'\').nextPage(\''+i_rdate_prev+',0\')'
			i_header += '">&#10094;</div>';
		}
		else
			i_header += '<div class="listview-item-page previous" style="visibility:hidden;">&#10094;</div>';

		i_keys.forEach(function(i_rdate, i) {
			var i_month = i_rdates.getValue(i_rdate, null);
			if (p_start == i_rdate+',0') {
				i_header += '<div class="listview-item-page listview-item-page-selected selected page-link">'+i_month+'</div>';

				i_select += '<option selected value="'+i_rdate+',0">' + i_month + '</option>';
			}
			else {

				i_header += '<div class="listview-item-page listview-item-page-link link page-link" onclick="';
				i_header += 'objects.getValue(\''+id+'\').nextPage(\''+i_rdate+',0\')'
				i_header += '">' + i_month+'</div>';

				//i_header += '<div class="listview-item-page"><a class="listview-item-page-link link" href="';
				//i_header += 'javascript:objects.getValue(\''+id+'\').nextPage(\''+i_rdate+',0\')'
				//i_header += '">' + i_month+'</a></div>';

				i_select += '<option value="'+i_rdate+',0">'+ i_month + '</option>';
			}
		});

		if (p_start && p_start.indexOf('ALL,')==0) {
			i_header += '<div class="listview-item-page listview-item-page-selected selected page-link">ALL</div>';

			i_select += '<option selected value="ALL,0">ALL</option>';
		}
		else {
			i_header += '<div class="listview-item-page listview-item-page-link link page-link" onclick="';
			i_header += 'objects.getValue(\''+id+'\').nextPage(\'ALL,0\')'
			i_header += '">ALL</div>';

			i_select += '<option value="ALL,0">ALL</option>';
		}

		i_header += '<select class="listview-page-homevideos" onchange="objects.getValue(\''+id+'\').nextPage(this.value)">' + i_select + '</select>';
		if (p_start && p_start.indexOf('ALL,')==0) {
			i_header += '<div class="listview-item-page next" style="visibility: hidden;">&#10095;</div>';
		}
		else {
			i_header += '<div class="listview-item-page listview-item-page-link link next" onclick="';
			i_header += 'objects.getValue(\''+id+'\').nextPage(\''+((i_rdate_next)? i_rdate_next : 'ALL')+',0\')'
			i_header += '">&#10095;</div>';
		}
		i_header += '</div>';

		//*** COUNT ITEMS
		var i_accepted = false, i_first = -1, i_count = 0, i_comp = "";
		if (p_start && p_start.indexOf('ALL,')==0) {
			i_first = 0;
			i_count = i_albums.length;
			i_comp = "ALL";
		}
		else {
			i_comp = (!p_start || p_start==0)? p_start : p_start.substring(0, p_start.indexOf(','));
			for(var i=0 ; i<i_albums.length ; i++) {
				var i_cdate = i_albums[i].release_date, i_pos = i_cdate.lastIndexOf('-');
				i_cdate = (i_cdate == "(Unknown)" || i_pos<0)? "(Unknown)" : i_cdate.substring(0, i_pos);

				//console.log(''+i+': '+p_start+' - '+i_cdate+' - '+i_rdate_first);

				if (!p_start || p_start==0) {
					if (i_cdate!=i_rdate_first) {
						if (i_accepted === true) break;
						continue;
					}
				}
				else if (i_cdate!=i_comp) {
					if (i_accepted === true) break;
					continue;
				}

				i_accepted = true;
				i_count++;
				if (i_first<0) i_first = i;
			}
		}

		if (m_max > 0 && i_count > m_max) {
			i_header += '<div class="listview-page">';
			for(var l=0 ; l<i_count ; l+=m_max) {
				var i_max = l+m_max;
				if (i_max > i_count)
					i_max = i_count;

				if (i_comp+','+l==p_start)
					i_header += '<div class="listview-item-page listview-item-page-selected selected">' + (l+1) + '-'+i_max + '</div>';
				else {
					i_header += '<div class="listview-item-page"><a class="listview-item-page-link link" href="';
					//i_header += 'javascript:nextListPage(\''+i_hid+'\',\''+i_comp+','+l+'\')';
					i_header += 'javascript:objects.getValue(\''+id+'\').nextPage(\''+i_comp+','+l+'\')';
					i_header += '">' + (l+1) + '-'+i_max + '</a></div>';
				}
			}
			i_header += '</div>';
			//i_header += '<div style="clear: both;">';
		}

		//*** LOOP
		var i_start = (!p_start || p_start==0)? 0 : parseInt(p_start.substring(p_start.indexOf(',')+1)), options = {
			tooltips: pDevice.isMobile(),
			buttons: pApplicationUI.OPTION_LISTVIEW_BUTTONS
		}, i_divs = [];
		for(var i=i_first+i_start ; i<i_first+i_count ; i++) {
			if (m_max>0 && i>=i_first+i_start+m_max) break;

			if (!pGenericList.acceptItem(i_albums[i])) continue;

			/** TODO: is-private */
			var r = pListViewItem.render(this, i_albums[i], i, 
				'javascript:pHomeVideo.click(objects.getValue(\''+id+'\'), '+i+');',
				'javascript:pHomeVideo.open(objects.getValue(\''+id+'\'), '+i+');',
				'javascript:pHomeVideo.play(objects.getValue(\''+id+'\'), '+i+');', options);
			h += r.html;
			i_divs[i_divs.length] = { id: r.id };
			
			//*** PRELOAD MOVIES
			pHomeVideo.preload(this, i);
		}
		h += '<div style="clear: both;">';

		pElement.setInnerHTML('generic-list-content-'+i_hid, i_header + h);

		//*** UPDATE THUMBS
		if (that.b_thumb)
			pListViewItem.thumbnail.update();
		
		divs = i_divs;
		
		//*** INSTALL KEYBOARD
		that.installKeys();
	};

	/**
	 * 
	 * @param p_id - letter or 'ALL'
	 */
	this.renderTable = function(p_id, p_start) {

		var thumbs = pApplicationUI.OPTION_THUMB_SHOW_THUMB;
		
		var i_hid = that.element_id;
		var i_albums = that.items;

		var i_show_artist = i_albums.length>0 && i_albums[0].container_lid!='music.artists' && i_albums[0].container_lid.indexOf('music.')==0;
		var i_show_album = i_albums.length>0 && i_albums[0].container_lid.indexOf('music.')==0;
		var i_show_title = i_albums.length>0 && i_albums[0].container_lid.indexOf('music.')!=0;
		var i_show_rating = i_albums.length>0 && (i_albums[0].container_lid.indexOf('movies.')==0 || i_albums[0].container_lid.indexOf('tvshows.')==0);
		var i_show_length = i_albums.length>0 && i_albums[0].container_lid.indexOf('movies.')==0;
		var i_show_countries = i_albums.length>0 && i_albums[0].container_lid.indexOf('radios.')==0;
		var i_show_year = i_albums.length>0 && i_albums[0].container_lid.indexOf('radios.')<0;
		var i_show_play =  i_albums.length>0 && i_albums[0].container_lid.indexOf('radios.')==0;
		//var i_length_pattern = /:/;

		//*** INIT RANGE
		const m_max = pApplicationUI.OPTION_PAGE_MAXSIZE;
		p_start = that.initRange(i_hid, p_id, i_albums, m_max, p_start);
		
		//*** INIT COLUMNS
		var cols = [];
		cols.push(pMetadata.columns.getValue('rating'));
		if (i_show_rating === true)
			cols.push(pMetadata.columns.getValue('tv-content-rating'));
		if (i_show_artist === true)
			cols.push(pMetadata.columns.getValue('artist'));
		if (i_show_album === true)
			cols.push(pMetadata.columns.getValue('album'));
		if (i_show_title === true)
			cols.push(pMetadata.columns.getValue('title'));
		if (i_show_year)
			cols.push(pMetadata.columns.getValue('year'));
		
		if (i_show_countries || pApplicationUI.OPTION_SHOW_COLUMN_LANGS)
			cols.push(pMetadata.columns.getValue('lang'));
		
		if (i_show_countries)
			cols.push(pMetadata.columns.getValue('countries'));
		
		if (i_show_length === true)
			cols.push(pMetadata.columns.getValue('length'));

		var h = '', i_divs = [];
		
		//*** PAGE TOOLBAR
		h += that.renderPageToolbar(i_albums, m_max, p_start, -1, true);
		
		h += '<table id=\"table-'+i_hid+'\" class="listview-table"><tr>';

		if (i_show_play)
			h += '<th/>';
		if (thumbs)
			h += '<th class="td-list-cell td-list-image"></th>';
		cols.forEach(function(col) { 
			h += '<th class="td-list-cell td-list-'+col.id+'">';
			h += '<span class="link" title="Click here to sort this column" onclick="pListManager.getValue(\''+i_hid+'\').sort(\''+col.id+'\')">'+col.th+'</span></th>';
			//h += '<a class="link" title="Click here to sort this column" href="javascript:void(pTable.sortString(\'table-'+i_hid+'\', pTable.columnIndex(\'table-'+i_hid+'\', \''+col.th+'\', 0, 0), 0, true))">'+col.th+'</a></th>'; 
		});
		h += '</tr>';

		//*** ITEMS
		for(var i=p_start ; i<i_albums.length ; i++) {
			if (!i_albums[i]) continue;
			
			if (m_max>0 && i>=p_start+m_max) break;
		
			var i_item = i_albums[i], mlid = i_item.lid + '.' + i_item.id;
			if (!pGenericList.acceptItem(i_item)) continue;

			/** TODO: is-private */

			//var i_id = i_item.id;
			//var i_lid = i_item.lid;
			var i_subtext = pGenericList.getItemSubtext(i_item);
			var i_title = pGenericList.getItemTitle(i_item) + ((null!=i_subtext && i_subtext.length > 0)? ' - ':'') + i_subtext //TODO: encodeHTML
			//var i_turl = i_item.thumbnail_url;
			//var i_artist = i_item.artist;
			//var i_length = (null!=i_item.duration)? i_item.duration : '0';

			var i_url = pListViewItem.url(this, i_item);

			//var i_season_name = i_item.tv_season_name;
			//var i_season = i_item.season;

			var i_tooltip = pGenericList.getItemTooltip(i_item, i_title);
			
			h += '<tr id="id.item.'+mlid+'">';

			if (i_show_play) {
				pRadio.preload(this, i);
				h += '<td>';
				h += '<div '/*id="movie-play-${episode}-button"*/+' class="link-soft button-common icon-button-play" ';
				h += 'onclick="pRadio.play(objects.getValue(\''+id+'\'), '+i+')"><!----></div>';
				h += '</td>';
			}
			
			if (thumbs) {
				h += '<td class="td-list-cell tableview-item td-list-image" id="table.id.'+pString.encodeHTML(i_title)+'">';
				h += '<a class="image tooltip" href="'+i_url+'" title="'+pString.encodeHTMLLines(i_tooltip)+'" tabindex="-1">';
				h += pListViewItem.thumbnail.renderImage(this, i_hid, i_item, i, 'tableview');
				h += '</a></td>';
			}
			
			cols.forEach(function(col) { 
				h += '<td class="td-list-cell td-list-'+col.id+'"';
				if (col.sort)
					h += ' data-sort="'+col.sort(i_item)+'"';
				h += '>' + col.content(i_item, that.library_id, i_title, i_url, i_tooltip, i) + '</td>';
			});

			h += '</tr>';
			i_divs[i_divs.length] = { id: 'id.item.'+mlid };
		}
		
		
		h += '</table>';

		//*** PAGE TOOLBAR
		h += that.renderPageToolbar(i_albums, m_max, p_start, -1, true);

		pElement.setInnerHTML('generic-list-content-'+i_hid, h);
		
		divs = i_divs;
		
		//*** UPDATE THUMBNAILS
		if (that.b_thumb)
			pListViewItem.thumbnail.update();
		
		//*** INSTALL KEYBOARD
		that.installKeys();
	};
	
	this.renderMusicPlaylist = function(p_library_id, p_items) {

		if (p_items && p_items.length>0) {
			var id = 'playlist-'+Math.random();
	
			var h = '<table class="box table-playlist-list" id="'+id+'">';
			h += '<thead>';
			h += '<tr>';
			//h += '	<th class="td-playlist-buttons"></th>';
	
			h += '	<th class="td-playlist-track"><a class="link" title="Click here to sort this column" href="javascript:pPlaylist.sortTableString(\''+id+'\',\'Track\')">Track</a></th>';
			h += '	<th class="td-playlist-rating"><a class="link" title="Click here to sort this column" href="javascript:pPlaylist.sortTableTitle(\''+id+'\',\'Rating\')">Rating</a></th>';
			//h += '	<xj:if test="${show-artist}">
			h += '		<th class="td-playlist-artist"><a class="link" title="Click here to sort this column" href="javascript:pPlaylist.sortTableTitle(\''+id+'\',\'Artist\')">Artist</a></th>';
			//h += '	</xj:if>
			//h += '	<xj:if test="${show-album}">
			h += '		<th class="td-playlist-album"><a class="link" title="Click here to sort this column" href="javascript:pPlaylist.sortTableTitle(\''+id+'\',\'Album\')">Album</a></th>';
			//h += '	</xj:if>
			h += '	<th class="td-playlist-title"><a class="link" title="Click here to sort this column" href="javascript:pPlaylist.sortTableTitle(\''+id+'\',\'Title\')">Title</a></th>';
			h += '	<th name="name.th.lang" class="td-playlist-lang" style="xdisplay: none;"><a class="link" title="Click here to sort this column" href="javascript:pPlaylist.sortTableTitle(\''+id+'\',\'Lang\')">Lang</a></th>';
	
			h += '<th class="td-playlist-duration search-result"><a class="link" title="Click here to sort this column" href="javascript:pPlaylist.sortTableDuration(\''+id+'\',\'Length\')">Length</a></th>';
	
			h += '</tr>';
			h += '</thead>';
			h += '<tbody>';
			/*<xj:for-each select="${metadata.children}">
	
			<!-- xj:variable name="metadata.has-video" select="${metadata.has-video}${metadata.has-video:-false}"/ -->
			<script>
			<!--
				m_urls[m_urls.length] = {
					url:"${urlprefix}${metadata.lid}/${metadata.id}?play&vlcurl=0&mode=web",
					start_time:${metadata.start-time}${metadata.start-time:-"-1"}, stop_time:${metadata.stop-time}${metadata.stop-time:-"-1"},
					has_video:${metadata.has-video}
				};
			-->
			</script>
	
			<xj:variable name="visited" select="${replaceAll:metadata.played.count/0/}"/>
			*/
	
			p_items.forEach(function(i_item, i) {
				h += '<tr id="tr.'+id+'.'+i+'">';
				h += '<td id="tr.'+id+'.'+i+'.td.track" class="td-playlist-track search-result">'+p_items[i].track+'</td>';
				//h += '		<td id="tr.'+i+'.td.track" class="td-playlist-track">'+p_items[i].track+'</td>';
				/*		<script>
							pElement.x('tr.${this.position}.td.track').setAttribute("data-sort", pString.padLeft('${metadata.disc}', 6, '0') + '/' + pString.padLeft('${metadata.track}', 6, '0'));
						</script>
					</xj:when>
					<xj:otherwise>
						<td id="tr.${this.position}.td.track" class="td-playlist-track">${this.position}</td>
						<script>
							pElement.x('tr.${this.position}.td.track').setAttribute("data-sort", pString.padLeft('${this.position}', 6, '0'));
						</script>
					</xj:otherwise>
				</xj:choose>*/
	
				h += '<td class="td-playlist-rating search-result">';
				//h += '	<span class="rating-normal">'+p_items[i].rating+'</span>';
	
				h += '<span class="rating-normal" id="rating-'+i+'">'+p_items[i].rating+'</span>';
				//h += '<script>pUserRating.rating(\'rating-'+i+'\', \''+p_items[i].rating+'\', url_prefix+p_library_id+'/'+i_item.lid+'/'+i_item.id+');</script>';
	
	
				h += '</td>';
	
				//<xj:if test="${show-artist}">
				h += '<td class="td-playlist-artist search-result">';
				h += '<span class="child playlist-artist">';
						//<xj:choose>
						//	<xj:when test="${show-artist-link}">
				var i_url = url_prefix+p_library_id+'/music.artists/name:x?name='+encodeURIComponent(p_items[i].artist);
				h += '<a class="link" href="'+i_url+'">'+p_items[i].artist+'</a>';
						//	</xj:when>
						//	<xj:otherwise>
						//		<span>${metadata.artist}</span>
						//	</xj:otherwise>
						//</xj:choose>
				h += '</span>';
				h += '</td>';
				//</xj:if>
	
				//<xj:if test="${show-album}">
				h += '<td class="td-playlist-album search-result">';
				if (i_item.albumArtist && i_item.albumArtist!="")
					i_url = url_prefix+p_library_id+'/music.albums/search?artist='+encodeURIComponent(i_item.albumArtist)+'&name='+encodeURIComponent(i_item.album);
				else
					i_url = url_prefix+p_library_id+'/music.albums/search?artist='+encodeURIComponent(i_item.artist)+'&name='+encodeURIComponent(i_item.album);
				h += '<a class="link" href="'+i_url+'"><nobr>'+pString.encodeHTML(i_item.album)+'</nobr></a>';
				h += '</td>';
				//</xj:if>
	
				h += '<td class="td-playlist-title search-result">';
				//h += '<a class="link" href="javascript:playlist.doPlay(\''+i+'\')">'+i_item.title+'</a>';
				h += '<a class="link" href="'+i_url+'&start-doc-url='+pURL.fixedEncodeURIComponent(url_prefix+p_library_id+'/'+i_item.lid+'/'+i_item.id)+'">'+i_item.title+'</a>';
	
				h += '<span class="playlist-title-icons">';
				h += pMetadata.renderTablePC(i_item);
				if (i_item.video && (i_item.video === true || i_item.video == "true"))
					h += '<img class="image" src="/resources/html/images/16x16/movies.png" title="This song/track has video"/>';
				h += '</span>';
	
				h += '</td>';
	
				h += '<td name="name.td.lang" class="td-playlist-lang search-result">';
				h += '<span class="lang">'+pLang.display(i_item.lang)+'</span>';
				h += '</td>';
	
				h += '<td class="td-playlist-duration search-result">';
				h += '<span class="duration">'+i_item.length+'</span>';
				h += '</td>';
	
				h += '</tr>';
			});
	
			h += '</tbody>';
			h += '</table>';
			return h;
		}
	}
	
	// "pageOffset" property
	Object.defineProperty(this, "pageOffset", { get: function() { return m_pageOffset; }, set: function(v) { if (typeof v == "int") m_pageOffset = v; } });
};

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

function renderSearchListBusy(i_hid) {
	pElement.setInnerHTML(
		'generic-list-content-'+i_hid,
		'<div id="busy.'+i_hid+'" class="td-busy"><span width="100%" align="center"><img src="/resources/html/images/line-wait.gif"/></span></div>');
}

/******************************************************************************/

function equalsLID(p_lida, p_lidb) {
	if (p_lida == 'books.medias' && p_lidb == 'books.groups')
		return true;
	if (p_lida == 'books.groups' && p_lidb == 'books.medias')
		return true;

	if (p_lida == 'pictures.medias' && p_lidb == 'pictures.groups')
		return true;
	if (p_lida == 'pictures.groups' && p_lidb == 'pictures.medias')
		return true;

	if (p_lida.indexOf('playlists.')==0 && p_lidb.indexOf('playlists.')==0)
		return true;

	return p_lida == p_lidb;
}

/******************************************************************************/

var m_list_render = new pMap();
var m_list_page = new pMap();
/*
var pHomeVideosList = {
	monthNames: [ "", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ],
	renderSubText: function(text) {
		var i_year = text.substring(0, 4);
		var i_month = text.substring(5, 7);
		//var i_day = text.substring(8, 10);
		//alert(this.monthNames);
		return pHomeVideosList.monthNames[parseInt(i_month)]+ ' ' + i_year;
	}
};
*/

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

const pListViewItem = (new function() {
	
	// list for which to show background play buttons...
	var listview_buttons_lid = [ 
		'music.albums', 'music.genres', 'music.artists', 'movies.movies', 'pictures.medias', 'books.medias', 'playlists.medias', 'playlists.default', 'homevideos.movies',
		'radios.radios'
	];
	
	this.create = function(lv, p_item, i, u, ourl, purl, options) {

		var i_id = ''+Math.random(), mlid = p_item.lid + '.' + p_item.id, h = '', i_lid = p_item.lid, i_title = pGenericList.getItemTitle(p_item), 
		t = options.tooltips? '' : pString.encodeHTMLLines(pGenericList.getItemTooltip(p_item, i_title));

		var h = pElement.create('div', 'id.item.'+mlid, 'listview-item', null, lv.lines==1? 'display: table-cell;':'');
		
		if (lv.b_thumb) {
			var i_btn = options && options.buttons &&  listview_buttons_lid.includes(i_lid);
			
			var th = pElement.create('div', null, 
				'listview-item-image '+(i_btn? 'btn-bg':''), 
				[ 
					[ 'a', null, 'image tooltip', [ this.createImage(mlid, p_item, i, 'listview') ], null, [ 'title', t, 'tabindex', '-1', 'href', u ] ] 
				], 
				null, 
				i_lid=='pictures.medias'?[ 'drop-picture', 'true' ] : null);
			
			if (i_btn) {
				//th.appendChild(pElement.create('div', null, 'listview-button-background listview-image', '&nbsp;', null, [ 'title', t, 'onclick', ourl || 'pLocation.assign(\''+u+'\')' ]));
				th.appendChild(pElement.create('div', null, 'listview-button-play', [
					[ 'span', null, null, '&nbsp;\u{25B6}&nbsp;', null, [ 'onclick', purl || 'pLocation.assign(\''+u+'?start=0\')', 'title', t ]],
					[ 'span', null, null, '&nbsp;\u{22EF}&nbsp;', null, [ 'onclick', ourl || 'pLocation.assign(\''+u+'\')', 'title', t ]]
				]));
			}
			
			h.appendChild(th);
		}

		if (lv.b_name) {
			////var ds = ' ondragstart="x=pElement.create(\'div\'); x.className=\'listview-item\'; x.innerHTML = pElement.x(\'id.item.'+mlid +'\').innerHTML; event.dataTransfer.setDragImage(x, 0, 0)"';
			var ds = 'x=pElement.x(\'id.item.'+mlid+'\'); event.dataTransfer.setDragImage(x, pDocument.width(x)/2, pDocument.height(x)/2)';
			//var ds = ' ondragstart="i=pElement.x(\''+ mlid +'\'); event.dataTransfer.setDragImage(i, i.width/2, i.height/2)"';
			//var ds = ' ondragstart="pElement.toCanvas(\''+ i_lid+'-'+p_item.id +'\').then(function(c) { event.dataTransfer.setDragImage(c, 0, 0); })"';
			//var ds = ' ondragstart="pElement.toCanvas(\'id.item.'+p_item.id+'\').then(function(c) { event.dataTransfer.setDragImage(c, 0, 0); })"';
			
			h.appendChild(pElement.create(
				'a',
				'a.id.item.'+mlid,
				'listview-text listview-text-'+i_lid.replace(re_dot, '-'),
				pString.encodeHTML(i_title),
				null,
				[ 'href', u, 'ondragstart', ds ]
			));
			h.appendChild(pElement.create(
				'span',
				null,
				'listview-subtext listview-subtext-'+i_lid.replace(re_dot, '-'),
				pString.encodeHTMLLines(pGenericList.getItemSubtext(p_item), '<br>'),
				null,
				[ 'ondragstart', ds ]
			));
		}

		var r = { id: 'id.item.'+mlid, html: h, 
			ondrop: pGenericList.getOnDrop(p_item, u), 
			url: u
		};//, images: [ { id: i_id, uri: url_prefix+lv.that.library_id+'/resources/'+i_turl }] };
		if (r.ondrop)
			r.dropfilter = pGenericList.getOnDropFilter(p_item, u);
		return r;
	};
	
	this.render = function(lv, p_item, i, u, ourl, purl, options) {

		var i_id = ''+Math.random(), mlid = p_item.lid + '.' + p_item.id, h = '', i_lid = p_item.lid, i_title = pGenericList.getItemTitle(p_item), 
			t = options.tooltips? '' : ' title="'+pString.encodeHTMLLines(pGenericList.getItemTooltip(p_item, i_title))+'"';

		var s = '';
		if (lv.lines==1)
			s += ' display: table-cell;';
		if (lv.wi > 0)
			s += ' width: ' + lv.wi + 'px; min-width: ' + lv.wi + 'px;';
		
		h += '<div class="listview-item" id="id.item.'+mlid+'" '+(pString.v(s)? ' style="'+s+'"':'')+'>';// ondragstart="pDrag.setURL(event)">';
		
		if (lv.b_thumb) {
			var i_btn = options && options.buttons &&  listview_buttons_lid.includes(i_lid);
			
			//var i_turl = url_prefix+lv.library_id+'/resources/'+p_item.thumbnail_url;
			
			h += '<div class="listview-item-image '+(i_btn? 'btn-bg':'')+'"'+ ((i_lid == 'pictures.medias')? ' drop-picture="true"' : '') +'>';
			h += '<a class="image tooltip" href="'+u+'"'+t+' tabindex="-1">';
			h += pListViewItem.thumbnail.renderImage(lv, mlid, p_item, i, 'listview', options);
			h += '</a>';
			
			if (i_btn) {
				//h += '<div class="listview-button-background" onclick="' + (ourl || ('pLocation.assign(\''+u+'\')') ) + '"'+t+'>&nbsp;</div>';
				h += '<div class="listview-button-play">';
				h += '<span onclick="' + (purl || ('pLocation.assign(\''+u+'?start=0\')') )+'"'+t+'>&nbsp;\u{25B6}&nbsp;</span>';
				h += '<span onclick="' + (ourl || ('pLocation.assign(\''+u+'\')') ) + '"'+t+'>&nbsp;\u{22EF}&nbsp;</span>';
				h += '</div>';
			}
			h += '</div>';
		}

		if (lv.b_name) {
			////var ds = ' ondragstart="x=pElement.create(\'div\'); x.className=\'listview-item\'; x.innerHTML = pElement.x(\'id.item.'+mlid +'\').innerHTML; event.dataTransfer.setDragImage(x, 0, 0)"';
			var ds = ' ondragstart="x=pElement.x(\'id.item.'+mlid+'\'); event.dataTransfer.setDragImage(x, pDocument.width(x)/2, pDocument.height(x)/2)"';
			//var ds = ' ondragstart="i=pElement.x(\''+ mlid +'\'); event.dataTransfer.setDragImage(i, i.width/2, i.height/2)"';
			//var ds = ' ondragstart="pElement.toCanvas(\''+ i_lid+'-'+p_item.id +'\').then(function(c) { event.dataTransfer.setDragImage(c, 0, 0); })"';
			//var ds = ' ondragstart="pElement.toCanvas(\'id.item.'+p_item.id+'\').then(function(c) { event.dataTransfer.setDragImage(c, 0, 0); })"';
			
			h += '<a id="a.id.item.'+mlid+'" class="listview-text listview-text-'+i_lid.replace(re_dot, '-')+'" href="'+u+'"'+t+ds+'>'+pString.encodeHTML(i_title)+'</a>';
			h += '<span class="listview-subtext listview-subtext-'+i_lid.replace(re_dot,'-')+'"'+ds+'>'+pString.encodeHTMLLines(pGenericList.getItemSubtext(p_item), '<br>')+'</span>';
		}
		h += '</div>';

		var r = { id: 'id.item.'+mlid, html: h, 
			ondrop: pGenericList.getOnDrop(p_item, u), 
			url: u
		};//, images: [ { id: i_id, uri: url_prefix+lv.library_id+'/resources/'+i_turl }] };
		if (r.ondrop)
			r.dropfilter = pGenericList.getOnDropFilter(p_item, u);
		return r;
	};
	
	this.url = function(lv, p_item) {
		var u = url_prefix + lv.library_id +'/'+ p_item.lid +'/'+p_item.id;

		if (p_item.url)
			u = p_item.url;
		else if (p_item.lid == 'tvshows.medias') {
			u = url_prefix + lv.library_id+'/tvshows.seasons/search?name='+encodeURI(pString.decodeHTML(p_item.tv_show));
			u += '&artist='+encodeURI('Season '+p_item.season)+'#'+p_item.id;
		}
		/*else if (p_item.lid == 'books.medias') {
			if (pROSE.getProp('view-user-advanced-play-books') === true)
				u += '?start=0';//'?play';
		}
		else if (p_item.lid == 'pictures.medias')  {
			if (pROSE.getProp('view-user-advanced-play-images') === true)
				u += '?start=0';//'?play';
		}*/
		return u;
	};
});

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

pListViewItem.thumbnail = (new function() {
	var that = this, thumbnail_error_url = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";	
	
	this.createImage = function(lv, p_id, p_item, i, p_type) {

		var dvd = (''+p_item.dvd) == "true", br = (''+p_item.bluray) == "true";
		var lid = p_item.lid, i_id = p_item.id, i = pElement.create(
			'img', 
			//p_id, 
			lv.element_id+'-'+p_type+'-item-img-'+i,
			[ 'image', p_type+'-image', 'listview-dynamic-thumbnail', lid.substring(0, lid.indexOf('.')), lid.replace('.','-'), dvd? 'dvd':null, br? 'bluray':null ],
			null,
			null,
			[ 'onError', 'pListViewItem.thumbnail.onerror(this)', 'onload', 'pListViewItem.thumbnail.resizeBackground(this)' ]
		);
		i.onerror = function() { handleThumbnailError(this) };
		i.src = u;
		
		var u = pImageVersions.addVersion(url_prefix+lv.library_id+'/resources/'+p_item.thumbnail_url, pImageVersions.artworkVersion(lid, i_id)); 
		i.src = u;
		i.setAttribute('data-thumbnail_url', u);
		
		u = pImageVersions.addVersion(url_prefix+lv.library_id+'/resources/'+p_item.backdrop_url, pImageVersions.backdropVersion(lid, i_id));
		
		var v = pLocation.param(u, 'v');
		u = pString.dirname(u)+'/=Thumbs/'+pString.removeExtension(pString.basename(u))+'.jpg';
		if (v)
			u = pURL.addParam(u, 'v', v);	
		
		i.setAttribute('data-backdrop_url', u);
		
		//u = url_prefix+lv.library_id+'/resources/'+p_item.poster_url;
		//u = pImageVersions.addVersion(url_prefix+lv.library_id+'/resources/'+p_item.poster_url, pImageVersions.posterVersion(lid, i_id));
		i.setAttribute('data-poster_url', pImageVersions.addVersion(url_prefix+lv.library_id+'/resources/'+p_item.poster_url, pImageVersions.posterVersion(lid, i_id)));
		
		//u = url_prefix+lv.library_id+'/resources/'+p_item.banner_url;
		//u = pImageVersions.addVersion(url_prefix+lv.library_id+'/resources/'+p_item.banner_url, pImageVersions.bannerVersion(lid, i_id));
		i.setAttribute('data-banner_url', pImageVersions.addVersion(url_prefix+lv.library_id+'/resources/'+p_item.banner_url, pImageVersions.bannerVersion(lid, i_id)));
		
		lv.v_artwork.addListener(lid, i_id);
		lv.v_backdrop.addListener(lid, i_id);
		lv.v_poster.addListener(lid, i_id);
		lv.v_banner.addListener(lid, i_id);
		
		return i;
	};

	this.renderImage = function(lv, p_id, p_item, i, p_type, options) {
		
		var dvd = (''+p_item.dvd)=="true", br = (''+p_item.bluray)=="true";
		var lid = p_item.lid, i_id = p_item.id, h = '<img id="'+p_id+'" class="image '+p_type+'-image listview-dynamic-thumbnail '+lid.substring(0, lid.indexOf('.'))+' '+lid.replace('.','-')+' ';
		h += dvd? ' dvd':'';
		h += br? ' bluray': '';
		h += '" id="'+lv.element_id+'-'+p_type+'-item-img-'+i+'" ';

		h += ' onError=\"pListViewItem.thumbnail.onerror(this)\" ';

		//var u = url_prefix+lv.library_id+'/resources/'+p_item.thumbnail_url;
		var u = pImageVersions.addVersion(url_prefix+lv.library_id+'/resources/'+p_item.thumbnail_url, pImageVersions.artworkVersion(lid, i_id)); 
		h += ' src=\"'+u+'\" alt=\"\"';
		h += ' data-thumbnail_url=\"'+u+'\" ';
		
		//u = url_prefix+lv.library_id+'/resources/'+p_item.backdrop_url;
		u = pImageVersions.addVersion(url_prefix+lv.library_id+'/resources/'+p_item.backdrop_url, pImageVersions.backdropVersion(lid, i_id));
		
		var v = pLocation.param(u, 'v');
		u = pString.dirname(u)+'/=Thumbs/'+pString.removeExtension(pString.basename(u))+'.jpg';
		if (v)
			u = pURL.addParam(u, 'v', v);	
		
		h += ' data-backdrop_url=\"'+u+'\" ';
		
		//u = url_prefix+lv.library_id+'/resources/'+p_item.poster_url;
		//u = pImageVersions.addVersion(url_prefix+lv.library_id+'/resources/'+p_item.poster_url, pImageVersions.posterVersion(lid, i_id));
		h += ' data-poster_url=\"' + pImageVersions.addVersion(url_prefix+lv.library_id+'/resources/'+p_item.poster_url, pImageVersions.posterVersion(lid, i_id)) + '\" ';
		
		//u = url_prefix+lv.library_id+'/resources/'+p_item.banner_url;
		//u = pImageVersions.addVersion(url_prefix+lv.library_id+'/resources/'+p_item.banner_url, pImageVersions.bannerVersion(lid, i_id));
		h += ' data-banner_url=\"' + pImageVersions.addVersion(url_prefix+lv.library_id+'/resources/'+p_item.banner_url, pImageVersions.bannerVersion(lid, i_id)) + '\" ';
		
		//h += ' onload="pListViewItem.thumbnail.resizeBackground(this)" />';
		h += '/>';
		
		lv.v_artwork.addListener(lid, i_id);
		lv.v_backdrop.addListener(lid, i_id);
		lv.v_poster.addListener(lid, i_id);
		lv.v_banner.addListener(lid, i_id);
		
		return h;
	};
	
	/** @param x The IMG that triggered onload method to call this method... */
	/*this.resizeBackground = function(x) {
		var w = x.width, h = x.height;//pDocument.width(i), h = pDocument.height(i);
		if (w > 32 && h > 32) {
			var b = pElement.c(x.parentElement.parentElement, 'listview-button-background');
			if (b.length>0) {
				b = b[0];
				//var bw = pDocument.width(b), bh = pDocument.height(b);
				//if (bw < w || bw > w) {
					w++;
					b.style.maxWidth = w + 'px';
					b.style.width = w + 'px';
				//}

				//if (bh < h || bh > h) {
					b.style.maxHeight = h + 'px';
					b.style.height = h + 'px';
				//}
			}
		}
	}*/
	
	this.onerror = function(x) {
		var i_src = pLocation.path(x.src);

		const i_backdrop = pApplicationUI.OPTION_THUMB_USE_BACKDROP;
		const i_poster = pApplicationUI.OPTION_THUMB_USE_POSTER;
		const i_banner = pApplicationUI.OPTION_THUMB_USE_BANNER;
		
		if (i_backdrop || i_poster || i_banner) {
			
			console.log("failed to load " + i_src);
			
			x.src = thumbnail_error_url;
			return;
		}

		//0.9.20
		if (x.getAttribute('data-poster')!='true')
		//var i_turl = pLocation.path(x.getAttribute('data-thumbnail_url'));
		//if (i_src == i_turl)
			if (i_src.indexOf('/music.')<0 && i_src.indexOf('/playlist.')<0 && i_src.indexOf('/pictures.')<0 && i_src.indexOf('/books.')<0 && 
					i_src.indexOf(':music.')<0 && i_src.indexOf(':playlist.')<0 && i_src.indexOf(':pictures.')<0 && i_src.indexOf(':books.')<0) {
				x.setAttribute('data-poster', 'true');
				x.src = x.getAttribute('data-poster_url');
				
				//console.log('use poster: ' + x.getAttribute('data-poster_url'));
				return;
			}

		//console.log('here: ' + thumbnail_error_url);
		x.src = thumbnail_error_url;
		return;
	};
	
	this.update = function(x) {
		if (x && x.getElementsByTagName) {
			const i_backdrop = pApplicationUI.OPTION_THUMB_USE_BACKDROP;
			const i_poster = pApplicationUI.OPTION_THUMB_USE_POSTER;
			const i_banner = pApplicationUI.OPTION_THUMB_USE_BANNER;
			
			if (i_backdrop)
				pElement.setSrc(x, x.getAttribute('data-backdrop_url'));
			else if (i_poster)
				pElement.setSrc(x, x.getAttribute('data-poster_url'));
			else if (i_banner)
				pElement.setSrc(x, x.getAttribute('data-banner_url'));
			else if (x.getAttribute('data-poster') == 'true')
				pElement.setSrc(x, x.getAttribute('data-poster_url'));
			else if (x.getAttribute('data-banner') == 'true')
				pElement.setSrc(x, x.getAttribute('data-banner_url'));
			else
				pElement.setSrc(x, x.getAttribute('data-thumbnail_url'));
		}
		else
			pElement.c('listview-dynamic-thumbnail').forEach(that.update);
	};
	
	//*** INSTALL
	pDocument.addOnPageReshow(that.update);
	
	pLocalStorage.addListener(pApplicationUI.options.OPTION_THUMB_USE_BACKDROP.name, that.update);
	pLocalStorage.addListener(pApplicationUI.options.OPTION_THUMB_USE_BANNER.name, that.update);
	pLocalStorage.addListener(pApplicationUI.options.OPTION_THUMB_USE_POSTER.name, that.update);
});

/******************************************************************************/
/**  END OF FILE  *************************************************************/
/******************************************************************************/