Add files via upload
parent
ea8ed0e4f8
commit
4ed6a6ac54
|
@ -0,0 +1,111 @@
|
|||
'use strict';
|
||||
|
||||
/* jshint unused:false */
|
||||
|
||||
/**
|
||||
* Cut/copy files/links system storage singleton
|
||||
* manages buffer items in two modes (cut/copy)
|
||||
* @author Stanislav Kalashnik <sk@infomir.eu>
|
||||
* @type {globalBuffer}
|
||||
*/
|
||||
var globalBuffer = (function () {
|
||||
// private vars
|
||||
var data = []; // all items
|
||||
var mode = 1; // default is "copy"
|
||||
var place = {}; // place where copy was made
|
||||
|
||||
// main body
|
||||
return {
|
||||
// constants
|
||||
MODE_COPY : 1, // do nothing on paste
|
||||
MODE_CUT : 2, // remove source item on paste
|
||||
|
||||
/**
|
||||
* callback function on each item paste event
|
||||
* @param {*} data item that was pasted successfully
|
||||
*/
|
||||
onPaste : null,
|
||||
/**
|
||||
* push new item to the list if not exist
|
||||
* @param {*} item buffer element
|
||||
* @return {boolean} operation status: true - item was successfully added
|
||||
*/
|
||||
add : function ( item ) {
|
||||
if ( data.indexOf(item) === -1 ) {
|
||||
data.push(item);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
/**
|
||||
* collects all buffer items and returns new copy of this list
|
||||
* @return {Array}
|
||||
*/
|
||||
paste : function ( func ) {
|
||||
var self = this;
|
||||
// clone data
|
||||
data.forEach(function(item){
|
||||
// run callback after cut
|
||||
if ( func(item) && typeof self.onPaste === 'function' ) {
|
||||
self.onPaste(item);
|
||||
}
|
||||
});
|
||||
// reset everything
|
||||
this.clear();
|
||||
},
|
||||
/**
|
||||
* setter/getter for mode inner flag
|
||||
* @param {number} [newMode] if applied then mode will be set otherwise returns the current value
|
||||
* @return {number} new or current mode value
|
||||
*/
|
||||
mode : function ( newMode ) {
|
||||
if ( newMode !== undefined ) {
|
||||
mode = newMode;
|
||||
}
|
||||
return mode;
|
||||
},
|
||||
/**
|
||||
* setter/getter for place inner flag (the place where copy operation was made)
|
||||
* @param {Object} [newPlace] if applied then place will be set otherwise returns the current value
|
||||
* @return {Object} new or current place value
|
||||
*/
|
||||
place : function ( newPlace ) {
|
||||
if ( newPlace !== undefined ) {
|
||||
place = newPlace;
|
||||
}
|
||||
return place;
|
||||
},
|
||||
/**
|
||||
* gets the buffer items total amount
|
||||
* @return {number}
|
||||
*/
|
||||
size : function () {
|
||||
return data.length;
|
||||
},
|
||||
/**
|
||||
* deletes the given item from the buffer
|
||||
* @param {*} item buffer element to be removed
|
||||
* @return {boolean} operation status: true - item was successfully removed
|
||||
*/
|
||||
remove : function ( item ) {
|
||||
// some input
|
||||
if ( item !== undefined ) {
|
||||
// find item index to remove
|
||||
var index = data.indexOf(item);
|
||||
// one item should be removed
|
||||
if ( index !== -1 ) {
|
||||
return data.splice(index, 1).length === 1;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
/**
|
||||
* resets all data
|
||||
*/
|
||||
clear : function () {
|
||||
data = [];
|
||||
place = {};
|
||||
this.onPaste = null;
|
||||
}
|
||||
};
|
||||
})();
|
|
@ -0,0 +1,244 @@
|
|||
'use strict';
|
||||
|
||||
function CAutocomplete ( parent, parameters ){
|
||||
var self = this;
|
||||
this.attributes = {
|
||||
wrapper: {
|
||||
className: 'cautocomplete-box'
|
||||
},
|
||||
row: {
|
||||
className: 'row',
|
||||
onmouseover: function(){
|
||||
self.SetActive(this);
|
||||
},
|
||||
onclick: function(){
|
||||
self.Abort();
|
||||
self.trigger('onEnter', {text: self.GetValue(), type: self.GetType()});
|
||||
self.Clear();
|
||||
},
|
||||
ico: {
|
||||
className: 'ico'
|
||||
},
|
||||
hint: {
|
||||
className: 'hint'
|
||||
},
|
||||
text: {
|
||||
className: 'text'
|
||||
}
|
||||
}
|
||||
};
|
||||
CBase.call(this, parent || null, parameters);
|
||||
this.name = 'CAutocomplete';
|
||||
this.active = null;
|
||||
this.events = {};
|
||||
this.isVisible = false;
|
||||
CAutocomplete.parameters.forEach(function(name){
|
||||
if (parameters[name] !== undefined) {
|
||||
self[name] = parameters[name];
|
||||
}
|
||||
});
|
||||
|
||||
this.bind(this.events);
|
||||
|
||||
this.base = this.base || this.input;
|
||||
this.size = this.size || 0;
|
||||
this.Init( element('div', this.attributes.wrapper) );
|
||||
this.buffer = {};
|
||||
|
||||
this.input.oninput = (function(oninput){
|
||||
return function(event){
|
||||
self.Show(false, false);
|
||||
clearTimeout(self.input_timer);
|
||||
if (self.input.value !== self.GetDefault()){
|
||||
self.buffer.data = self.input.value;
|
||||
self.input_timer = setTimeout(function(){
|
||||
self.RefreshData(self.input.value, function(){
|
||||
this.Show(true, false);
|
||||
this.RefreshUi();
|
||||
});
|
||||
}, 600);
|
||||
}
|
||||
if (typeof oninput === 'function') {
|
||||
oninput(event);
|
||||
}
|
||||
};
|
||||
})(this.input.oninput);
|
||||
|
||||
|
||||
}
|
||||
|
||||
CAutocomplete.parameters = ['input', 'base', 'size', 'data', 'url', 'events', 'titleField', 'valueField', 'typeField', 'hintField'];
|
||||
|
||||
// extending
|
||||
CAutocomplete.prototype = Object.create(CBase.prototype);
|
||||
CAutocomplete.prototype.constructor = CAutocomplete;
|
||||
|
||||
CAutocomplete.prototype.onInit = function (){
|
||||
elchild(document.body, this.handle);
|
||||
};
|
||||
|
||||
CAutocomplete.prototype.UpdateGeometry = function (){
|
||||
var inputRect = this.base.getBoundingClientRect();
|
||||
this.handle.style.top = (inputRect.top + inputRect.height) + 'px';
|
||||
this.handle.style.left = inputRect.left + 'px';
|
||||
this.handle.style.width = inputRect.width + 'px';
|
||||
};
|
||||
|
||||
CAutocomplete.prototype.RefreshUi = function ( data ){
|
||||
var i, size, row;
|
||||
if (this.handle.style.width === ''){
|
||||
this.UpdateGeometry();
|
||||
}
|
||||
data = data || this._data;
|
||||
elclear(this.handle);
|
||||
size = this.size > data.length ? data.length : this.size;
|
||||
if (size > 0){
|
||||
for (i = 0; i < size; i++){
|
||||
row = this.AddRow(data[i]);
|
||||
}
|
||||
this.SetActive(this.buffer.nextSibling = this.handle.firstElementChild);
|
||||
this.buffer.previousSibling = this.handle.lastElementChild;
|
||||
}else{
|
||||
this.Show(false);
|
||||
}
|
||||
};
|
||||
|
||||
CAutocomplete.prototype.RefreshData = function (data, callback ) {
|
||||
var url, self = this;
|
||||
if (data === '') {
|
||||
this.Show(false, false);
|
||||
return;
|
||||
}
|
||||
this.buffer.data = data;
|
||||
url = this.url ? this.url + data : data;
|
||||
if (url){
|
||||
this.xhr = ajax('GET', url, function(res, status){
|
||||
if (status === 200){
|
||||
if (typeof self.data === 'function'){
|
||||
self._data = self.data.call(this, res);
|
||||
}else{
|
||||
self._data = res[self.data];
|
||||
}
|
||||
callback.call(self, res);
|
||||
self.trigger('onDataLoad', data);
|
||||
} else {
|
||||
self.Show(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
CAutocomplete.prototype.GetTitle = function ( data ) {
|
||||
data = data || this.active.data;
|
||||
if (this.active === this.buffer) {
|
||||
return data;
|
||||
}
|
||||
return this.titleField ? data[this.titleField] : this.active.data;
|
||||
};
|
||||
|
||||
CAutocomplete.prototype.GetValue = function ( data ) {
|
||||
data = data || this.active.data;
|
||||
if (this.active === this.buffer) {
|
||||
return data;
|
||||
}
|
||||
data = this.valueField ? data[this.valueField] : this.GetTitle();
|
||||
data = data.replace(/<\/?b>/g,'');
|
||||
return data;
|
||||
};
|
||||
|
||||
CAutocomplete.prototype.GetType = function ( data ) {
|
||||
data = data || this.active.data;
|
||||
return data[this.typeField] ? data[this.typeField] : 'search';
|
||||
};
|
||||
|
||||
CAutocomplete.prototype.GetHint = function ( data ) {
|
||||
data = data || this.active.data;
|
||||
return this.hintField ? data[this.hintField] : '';
|
||||
};
|
||||
|
||||
CAutocomplete.prototype.GetDefault = function () {
|
||||
return this.buffer.data;
|
||||
};
|
||||
|
||||
CAutocomplete.prototype.SetActive = function ( active ){
|
||||
if (this.active !== null && this.active !== this.buffer){
|
||||
this.active.classList.remove('active');
|
||||
}
|
||||
if (active !== this.buffer){
|
||||
active.classList.add('active');
|
||||
}
|
||||
this.active = active;
|
||||
this.trigger('onChange');
|
||||
};
|
||||
|
||||
CAutocomplete.prototype.Next = function (){
|
||||
if ( this.active.nextSibling ) {
|
||||
this.SetActive(this.active.nextSibling);
|
||||
} else {
|
||||
this.SetActive(this.buffer);
|
||||
}
|
||||
};
|
||||
|
||||
CAutocomplete.prototype.Previous = function (){
|
||||
if (this.active.previousSibling){
|
||||
this.SetActive(this.active.previousSibling);
|
||||
}else{
|
||||
this.SetActive(this.buffer);
|
||||
}
|
||||
};
|
||||
|
||||
CAutocomplete.prototype.AddRow = function ( data ){
|
||||
var el, ico, hint, text, attributes = extend({}, this.attributes.row);
|
||||
attributes.className += ' ' + this.GetType(data);
|
||||
ico = element('div', this.attributes.row.ico);
|
||||
hint = element('div', this.attributes.row.hint);
|
||||
text = element('div', this.attributes.row.text);
|
||||
elchild(this.handle, el = element('div', attributes, [ico, text, hint]));
|
||||
text.innerHTML = this.GetTitle(data);
|
||||
hint.innerHTML = this.GetHint(data);
|
||||
el.data = data;
|
||||
return el;
|
||||
};
|
||||
|
||||
CAutocomplete.prototype.Abort = function(){
|
||||
if (this.xhr !== undefined){
|
||||
this.xhr.abort();
|
||||
}
|
||||
};
|
||||
|
||||
CAutocomplete.prototype.Clear = function (){
|
||||
this.Show(false);
|
||||
elclear(this.handle);
|
||||
clearTimeout(this.input_timer);
|
||||
this.Abort();
|
||||
this.active = null;
|
||||
this.buffer = {};
|
||||
};
|
||||
|
||||
CAutocomplete.prototype.EventHandler = function ( event ){
|
||||
eventPrepare(event);
|
||||
if (this.isVisible === true){
|
||||
switch(event.code){
|
||||
case KEYS.UP:
|
||||
this.Previous();
|
||||
event.preventDefault();
|
||||
break;
|
||||
case KEYS.DOWN:
|
||||
this.Next();
|
||||
event.preventDefault();
|
||||
break;
|
||||
case KEYS.OK:
|
||||
this.Abort();
|
||||
this.trigger('onEnter', {text: this.GetValue(), type: this.GetType()});
|
||||
this.Clear();
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
Events.inject(CAutocomplete);
|
|
@ -0,0 +1,265 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* Base class for any visual component.
|
||||
* Always has one html placeholder.
|
||||
* @class CBase
|
||||
* @constructor
|
||||
* @author Stanislav Kalashnik <sk@infomir.eu>
|
||||
*/
|
||||
function CBase ( parent ) {
|
||||
/**
|
||||
* The component inner name
|
||||
* @type {string}
|
||||
*/
|
||||
this.name = 'CBase';
|
||||
|
||||
/**
|
||||
* Component placeholder
|
||||
* should be never used for append nodes inside the component methods
|
||||
* use this.handleInner instead
|
||||
* @type {Node}
|
||||
*/
|
||||
this.handle = null;
|
||||
|
||||
/**
|
||||
* Object owner of the component
|
||||
* @type {CBase}
|
||||
*/
|
||||
this.parent = null;
|
||||
|
||||
/**
|
||||
* Component main body placeholder (should be the same as this.handle)
|
||||
* can be different only in case when the given placeholder is invalid (don't have the necessary class)
|
||||
* so the valid wrapper with this.baseClass should be created instead
|
||||
* @type {Node}
|
||||
*/
|
||||
this.handleInner = null;
|
||||
|
||||
/**
|
||||
* CSS "display" attribute value to make the component visible
|
||||
* to hide the default value is "none"
|
||||
* @type {string}
|
||||
*/
|
||||
this.showAttr = 'block';
|
||||
|
||||
/**
|
||||
* CSS class name associated with the component
|
||||
* it is checking on initialization in the placeholder
|
||||
* if not found a wrapper is created
|
||||
* @type {string}
|
||||
*/
|
||||
this.baseClass = '';
|
||||
|
||||
/**
|
||||
* The previous DOM element that had focus
|
||||
* (used for focus management on show/hide operations)
|
||||
* @type {Node}
|
||||
*/
|
||||
this.prevFocus = null;
|
||||
|
||||
/**
|
||||
* Flag to indicate the component state
|
||||
* using for event handling and activate/deactivate hooks
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.isActive = false;
|
||||
|
||||
/**
|
||||
* Current active child element
|
||||
* @type {CBase}
|
||||
*/
|
||||
this.activeChild = null;
|
||||
|
||||
/**
|
||||
* default visibility state
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.isVisible = true;
|
||||
|
||||
// apply hierarchy
|
||||
this.SetParent(parent);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the parent object owner of this component
|
||||
* @param {CBase} parent object owner
|
||||
*/
|
||||
CBase.prototype.SetParent = function ( parent ) {
|
||||
// check input
|
||||
if ( parent instanceof CBase ) {
|
||||
// store here as well
|
||||
this.parent = parent;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Component initialization with its placeholder.
|
||||
* Should be called once before use just after constructor invoke and all hooks preparation.
|
||||
* @param {Node} handle component placeholder (it should be an empty div element)
|
||||
*/
|
||||
CBase.prototype.Init = function ( handle ) {
|
||||
// input validation
|
||||
if ( handle && handle.nodeName ) {
|
||||
// global store
|
||||
this.handle = handle;
|
||||
|
||||
// the given placeholder is invalid (don't have the necessary base class)
|
||||
if ( this.baseClass && handle.className.indexOf(this.baseClass) === -1 ) {
|
||||
// add wrapper
|
||||
this.handleInner = this.handle.appendChild(element('div', {className:this.baseClass}));
|
||||
} else {
|
||||
// the component body pointer
|
||||
this.handleInner = this.handle;
|
||||
}
|
||||
|
||||
var self = this;
|
||||
this.handle.addEventListener('click', function(event){
|
||||
if ( typeof self.onClick === 'function' && self.onClick() === false ) {
|
||||
// prevents further propagation of the current event
|
||||
event.stopPropagation();
|
||||
}
|
||||
});
|
||||
|
||||
// run callback hook
|
||||
if ( typeof this.onInit === 'function' ) {
|
||||
this.onInit();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Makes the component active or disable it
|
||||
* process the activation/deactivation hooks for this component and its parent
|
||||
* @param {boolean} [active=true]
|
||||
*/
|
||||
CBase.prototype.Activate = function ( active ) {
|
||||
this.isActive = active !== false;
|
||||
if ( this.isActive ) {
|
||||
// run this component activation callback hook
|
||||
if ( typeof this.onActivate === 'function' ) {
|
||||
this.onActivate();
|
||||
}
|
||||
// has parent
|
||||
if ( this.parent ) {
|
||||
// run the previous active component deactivation (if not itself)
|
||||
if ( this.parent.activeChild && this.parent.activeChild !== this ) {
|
||||
this.parent.activeChild.Activate(false);
|
||||
}
|
||||
// set link in the parent
|
||||
this.parent.activeChild = this;
|
||||
}
|
||||
} else {
|
||||
// run this component deactivation callback hook
|
||||
if ( typeof this.onDeactivate === 'function' ) {
|
||||
this.onDeactivate();
|
||||
}
|
||||
// has parent
|
||||
if ( this.parent ) {
|
||||
// set link in the parent
|
||||
this.parent.activeChild = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Manage the component visibility, global focus and exec show/hide hooks
|
||||
* @param {boolean} [visible=true] component visibility: true - visible, false - hidden
|
||||
* @param {boolean} [manageFocus=true] focus handling mode: true - set/remove focus accordingly, false - manual focus management
|
||||
* @return {boolean} status: true - operation was successful (mode changed), false - operation was skipped
|
||||
*/
|
||||
CBase.prototype.Show = function ( visible, manageFocus ) {
|
||||
var success = false;
|
||||
// placeholder validation
|
||||
if ( this.handle ) {
|
||||
this.isVisible = visible !== false;
|
||||
// show
|
||||
if ( this.isVisible ) {
|
||||
// prevent double invoke
|
||||
if ( this.handle.style.display !== this.showAttr ) {
|
||||
// save the previous focused element
|
||||
this.prevFocus = document.activeElement;
|
||||
// remove focus if necessary and set
|
||||
if ( manageFocus !== false && document.activeElement ) {
|
||||
document.activeElement.blur();
|
||||
}
|
||||
// show this component
|
||||
this.handle.style.display = this.showAttr;
|
||||
// set focus if necessary
|
||||
if ( manageFocus !== false ) {
|
||||
this.handle.focus();
|
||||
}
|
||||
// invoke callback hook
|
||||
if ( typeof this.onShow === 'function' ) {
|
||||
this.onShow();
|
||||
}
|
||||
success = true;
|
||||
}
|
||||
// hide
|
||||
} else {
|
||||
// prevent double invoke
|
||||
if ( this.handle.style.display !== 'none' ) {
|
||||
// remove focus if necessary and set
|
||||
if ( manageFocus !== false && document.activeElement ) {
|
||||
document.activeElement.blur();
|
||||
}
|
||||
// hide this component
|
||||
this.handle.style.display = 'none';
|
||||
// return focus to the previous element if necessary and set
|
||||
if ( manageFocus !== false && this.prevFocus ) {
|
||||
this.prevFocus.focus();
|
||||
}
|
||||
// invoke callback hook
|
||||
if ( typeof this.onHide === 'function' ) {
|
||||
this.onHide();
|
||||
}
|
||||
// deactivate this component if necessary
|
||||
if ( this.isActive ) {
|
||||
this.Activate(false);
|
||||
}
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return success;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set visibility mode
|
||||
* @param {boolean} [mode=false] show/hide the component
|
||||
*/
|
||||
CBase.prototype.Visible = function ( mode ) {
|
||||
this.handle.style.visibility = Boolean(mode) ? 'visible' : 'hidden';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Events handler entry point.
|
||||
* Should be recreated if necessary in each child object to handle parent events.
|
||||
* @type {Function}
|
||||
*/
|
||||
CBase.prototype.EventHandler = null;
|
||||
|
||||
|
||||
// hooks to redefine
|
||||
CBase.prototype.onInit = null;
|
||||
CBase.prototype.onShow = null;
|
||||
CBase.prototype.onHide = null;
|
||||
CBase.prototype.onActivate = null;
|
||||
CBase.prototype.onDeactivate = null;
|
||||
|
||||
|
||||
/**
|
||||
* Method to activate a component by mouse click on it
|
||||
* @returns {boolean}
|
||||
*/
|
||||
CBase.prototype.onClick = function () {
|
||||
this.Activate(true);
|
||||
return true;
|
||||
};
|
|
@ -0,0 +1,196 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* Class for breadcrumb with images.
|
||||
* @class CBreadCrumb
|
||||
* @constructor
|
||||
* @author Stanislav Kalashnik <sk@infomir.eu>
|
||||
*/
|
||||
function CBreadCrumb ( parent ) {
|
||||
// parent constructor
|
||||
CBase.call(this, parent);
|
||||
|
||||
/**
|
||||
* The component inner name
|
||||
* @type {string}
|
||||
*/
|
||||
this.name = 'CBreadCrumb';
|
||||
|
||||
/**
|
||||
* CSS class name associated with the component
|
||||
* @type {string}
|
||||
*/
|
||||
this.baseClass = 'cbcrumb-main';
|
||||
|
||||
/**
|
||||
* directory with icons
|
||||
* depends on screen resolution
|
||||
* @type {string}
|
||||
*/
|
||||
this.path = '';
|
||||
|
||||
/**
|
||||
* Amount of left items before the ...
|
||||
* @type {number}
|
||||
*/
|
||||
this.leftItems = 3;
|
||||
|
||||
/**
|
||||
* Amount of right items after the ...
|
||||
* @type {number}
|
||||
*/
|
||||
this.rightItems = 2;
|
||||
}
|
||||
|
||||
// extending
|
||||
CBreadCrumb.prototype = Object.create(CBase.prototype);
|
||||
CBreadCrumb.prototype.constructor = CBreadCrumb;
|
||||
|
||||
|
||||
/**
|
||||
* Component initialization with image path set.
|
||||
* @param {string} path image path dependant on resolution
|
||||
* @param {Node} [handle] component placeholder
|
||||
* @param {number} [leftItems] amount of left items before the ...
|
||||
* @param {number} [rightItems] amount of right items after the ...
|
||||
*/
|
||||
CBreadCrumb.prototype.Init = function ( path, handle, leftItems, rightItems ) {
|
||||
// global vars
|
||||
this.path = path;
|
||||
|
||||
// parent call init with placeholder
|
||||
CBase.prototype.Init.call(this, handle);
|
||||
|
||||
this.dom = {};
|
||||
// add to component container
|
||||
elchild(this.handleInner, [
|
||||
this.dom.list = element('div', {className: 'cbcrumb-list'}),
|
||||
this.dom.name = element('div', {className: 'cbcrumb-name'})
|
||||
]);
|
||||
|
||||
this.leftItems = leftItems || this.leftItems;
|
||||
this.rightItems = rightItems || this.rightItems;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set optional auto-hide title to the right from the root icon
|
||||
* @param {string} text label to show
|
||||
*/
|
||||
CBreadCrumb.prototype.SetName = function ( text ) {
|
||||
elclear(this.dom.name);
|
||||
elchild(this.dom.name, element('div', {innerHTML: text, className: 'cbcrumb-name-text'}));
|
||||
// show or hide title
|
||||
this.DrawName();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set title visibility
|
||||
*/
|
||||
CBreadCrumb.prototype.DrawName = function () {
|
||||
// show or hide title
|
||||
this.dom.name.style.display = this.dom.list.children.length === 1 ? 'table-cell' : 'none';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Append a new button
|
||||
* @param {string} path
|
||||
* @param {string} [icon] file of the button icon
|
||||
* @param {string} [text] button title
|
||||
* @param {string} [iid] item id
|
||||
* @returns {Node}
|
||||
*/
|
||||
CBreadCrumb.prototype.Push = function ( path, icon, text, iid ) {
|
||||
var last = this.dom.list.lastChild;
|
||||
|
||||
if ( last ) {
|
||||
last.className = 'cbcrumb-item';
|
||||
}
|
||||
|
||||
// build item
|
||||
var item = element('div', {className: 'cbcrumb-item active', onclick: null, path: path}, [
|
||||
icon ? element('img', {className: 'cbcrumb-icon', onclick: null, src: this.path + icon}) : null,
|
||||
text ? element('div', {className: 'cbcrumb-text', onclick: null}, text) : null
|
||||
]);
|
||||
|
||||
// set id
|
||||
if ( iid ) {
|
||||
item.iid = iid;
|
||||
}
|
||||
|
||||
// add divider
|
||||
if ( this.dom.list.children.length === this.leftItems ) {
|
||||
elchild(this.dom.list, element('div', {className: 'cbcrumb-item divider'}, '. . .'));
|
||||
}
|
||||
// add to component container
|
||||
elchild(this.dom.list, item);
|
||||
// show ... and hide item
|
||||
if ( this.dom.list.children.length > this.leftItems + this.rightItems + 1 ) {
|
||||
this.dom.list.children[this.leftItems].style.display = 'table-cell';
|
||||
this.dom.list.children[this.dom.list.children.length - this.rightItems - 1].style.display = 'none';
|
||||
}
|
||||
|
||||
// show or hide title
|
||||
this.DrawName();
|
||||
|
||||
return item;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Extracts the last item
|
||||
* @return {Node}
|
||||
*/
|
||||
CBreadCrumb.prototype.Pop = function () {
|
||||
var item = this.dom.list.lastChild;
|
||||
if ( item ) {
|
||||
this.dom.list.removeChild(item);
|
||||
|
||||
// remove divider
|
||||
if ( this.dom.list.children.length === this.leftItems + 1 ) {
|
||||
this.dom.list.removeChild(this.dom.list.lastChild);
|
||||
} else if ( this.dom.list.children.length > this.leftItems + this.rightItems ) {
|
||||
this.dom.list.children[this.dom.list.children.length - this.rightItems].style.display = 'table-cell';
|
||||
}
|
||||
if ( this.dom.list.children.length === this.leftItems + this.rightItems + 1 ) {
|
||||
this.dom.list.children[this.leftItems].style.display = 'none';
|
||||
}
|
||||
|
||||
if ( this.dom.list.lastChild ) {
|
||||
this.dom.list.lastChild.className = 'cbcrumb-item active';
|
||||
}
|
||||
|
||||
// show or hide title
|
||||
this.DrawName();
|
||||
}
|
||||
return item;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the current item
|
||||
* @return {Node}
|
||||
*/
|
||||
CBreadCrumb.prototype.Tip = function () {
|
||||
return this.dom.list.lastChild;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Clears all the items
|
||||
*/
|
||||
CBreadCrumb.prototype.Reset = function () {
|
||||
elclear(this.dom.list);
|
||||
// show or hide title
|
||||
this.DrawName();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Can handle a mouse click
|
||||
*/
|
||||
CBreadCrumb.prototype.onClick = function () {
|
||||
return false;
|
||||
};
|
|
@ -0,0 +1,194 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* Class for button with images panel.
|
||||
* @class CButtonPanel
|
||||
* @constructor
|
||||
* @author Stanislav Kalashnik <sk@infomir.eu>
|
||||
*/
|
||||
function CButtonPanel ( parent ) {
|
||||
// parent constructor
|
||||
CBase.call(this, parent);
|
||||
|
||||
/**
|
||||
* The component inner name
|
||||
* @type {string}
|
||||
*/
|
||||
this.name = 'CButtonPanel';
|
||||
|
||||
/**
|
||||
* CSS class name associated with the component
|
||||
* @type {string}
|
||||
*/
|
||||
this.baseClass = 'cbpanel-main';
|
||||
|
||||
/**
|
||||
* directory with icons
|
||||
* depends on screen resolution
|
||||
* @type {string}
|
||||
*/
|
||||
this.path = '';
|
||||
|
||||
/**
|
||||
* Flag to indicate the component state
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.isActive = true;
|
||||
}
|
||||
|
||||
// extending
|
||||
CButtonPanel.prototype = Object.create(CBase.prototype);
|
||||
CButtonPanel.prototype.constructor = CButtonPanel;
|
||||
|
||||
|
||||
/**
|
||||
* Component initialization with image path set.
|
||||
* Should be called once before use just after constructor invoke and all hooks preparation.
|
||||
* @param {string} path image path dependant on resolution
|
||||
* @param {Node} [handle] component placeholder
|
||||
*/
|
||||
CButtonPanel.prototype.Init = function ( path, handle ) {
|
||||
// global image path
|
||||
this.path = path;
|
||||
|
||||
if ( handle && handle.nodeName ) {
|
||||
// parent call init with placeholder
|
||||
CBase.prototype.Init.call(this, handle);
|
||||
} else {
|
||||
// parent call init with placeholder
|
||||
CBase.prototype.Init.call(this, element('div', {className:'cbpanel-main'}));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Append a new button
|
||||
* @param {number} code keydown code
|
||||
* @param {string} icon file of the button icon
|
||||
* @param {string} text button title
|
||||
* @param {Function} callback click/keyboard handler
|
||||
* @param {boolean} [hidden=false] is button visible
|
||||
* @returns {Node}
|
||||
*/
|
||||
CButtonPanel.prototype.Add = function ( code, icon, text, callback, hidden ) {
|
||||
// prepare text
|
||||
var html = null,
|
||||
self = this,
|
||||
func = function(){
|
||||
if ( self.isActive && typeof callback === 'function' ) {
|
||||
gSTB.HideVirtualKeyboard();
|
||||
callback(code);
|
||||
}
|
||||
};
|
||||
if ( text ) {
|
||||
html = element('div', {className:'cbpanel-text', onclick:func});
|
||||
html.innerHTML = text;
|
||||
}
|
||||
// build button item
|
||||
var item = element('div', {className:'cbpanel-item', data:{code:code, onclick:func}}, [
|
||||
element('img', {className:'cbpanel-icon', onclick:func, src:this.path + '/' + icon}),
|
||||
html
|
||||
]);
|
||||
item.$name = html;
|
||||
// apply visibility option
|
||||
this.Hidden(item, hidden || false);
|
||||
// add to component container
|
||||
elchild(this.handleInner, item);
|
||||
return item;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Manage the given item visibility
|
||||
* @param {Node} item the group element to alter
|
||||
* @param {boolean} state true - set hidden; false - set visible
|
||||
*/
|
||||
CButtonPanel.prototype.Hidden = function ( item, state ) {
|
||||
// valid group object and states are different
|
||||
if ( item && item.nodeName && item.data.hidden !== state ) {
|
||||
// set inner attribute
|
||||
item.data.hidden = state;
|
||||
// actual show/hide
|
||||
item.style.display = state ? 'none' : 'table-cell';
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the given button name
|
||||
* @param {Node} item the group element to alter
|
||||
* @param {string} name new button name
|
||||
*/
|
||||
CButtonPanel.prototype.SetName = function ( item, name ) {
|
||||
var html = null;
|
||||
|
||||
if ( item && item.nodeName && name ) {
|
||||
if ( item.$name ) {
|
||||
this.Rename(item, name);
|
||||
} else {
|
||||
html = element('div', {className:'cbpanel-text', onclick:item.data.onclick});
|
||||
html.innerHTML = name;
|
||||
item.$name = html;
|
||||
item.appendChild(html);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Manage the given button name
|
||||
* @param {Node} item the group element to alter
|
||||
* @param {string} name new button name
|
||||
*/
|
||||
CButtonPanel.prototype.Rename = function ( item, name ) {
|
||||
// valid group object and states are different
|
||||
if ( item && item.nodeName) {
|
||||
// actual rename
|
||||
item.$name.innerHTML = name;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Makes the component active or disable it to start/stop event handling
|
||||
* @param {boolean} [active=true]
|
||||
*/
|
||||
CButtonPanel.prototype.Activate = function ( active ) {
|
||||
this.isActive = active !== false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handle external events.
|
||||
*
|
||||
* @param {Event} event - global event object
|
||||
*/
|
||||
CButtonPanel.prototype.EventHandler = function ( event ) {
|
||||
var index, data, items, length;
|
||||
|
||||
if ( event.stopped === true ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( this.isActive ) {
|
||||
// iterate all items
|
||||
for ( index = 0, items = this.handleInner.children, length = items.length; index < length; index += 1 ) {
|
||||
data = items[index].data;
|
||||
|
||||
// check data, visibility and validate id
|
||||
if ( !data.hidden && data.code === event.code && typeof data.onclick === 'function' ) {
|
||||
if ( data.onclick() ) {
|
||||
// stop event spreading
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Can handle a mouse click
|
||||
*/
|
||||
CButtonPanel.prototype.onClick = function () {
|
||||
return false;
|
||||
};
|
|
@ -0,0 +1,171 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* Class for checkboxes in the portal
|
||||
* @param {CBase} parent Object owner of the component
|
||||
* @param {Object} parameters Initialization parameters
|
||||
* @class CCheckBox
|
||||
* @constructor
|
||||
* @author Igor Kopanev
|
||||
* @example checkbox = new CCheckBox(currCPage, {
|
||||
* element: element // {Node} If checkbox already exist on the page you can init component from him.
|
||||
* parentNode: parent // {Node} parent node for checkbox
|
||||
* attributes: attributes // {Object} attributes that are applied to a check
|
||||
* checked: true // {boolean} shows that the box is checked or not
|
||||
* onchange: onchange // {Function} onchange event function
|
||||
* });
|
||||
* @throw if parentNode of element parameters is not specified
|
||||
*/
|
||||
function CCheckBox ( parent, parameters ){
|
||||
var parentNode,
|
||||
_default = !!parameters.checked,
|
||||
self = this;
|
||||
CBase.call(this, parent || null);
|
||||
this.parameters = parameters || {};
|
||||
this.parameters.attributes = parameters.attributes || {};
|
||||
this.name = parameters.name || 'input';
|
||||
this.type = this.tagName = 'checkbox';
|
||||
this.style = parameters.style || '';
|
||||
this.events = parameters.events || {};
|
||||
// if parentNode not specified get the checkbox parentNode
|
||||
if ((parentNode = parameters.parent) === undefined){
|
||||
// if checkbox not specified to throw exception
|
||||
if (parameters.element === undefined){
|
||||
throw('parentNode or element parameter must be specified');
|
||||
}
|
||||
parentNode = parameters.element.parentNode;
|
||||
}
|
||||
|
||||
this.bind(this.events);
|
||||
|
||||
this.IsChanged = function(){
|
||||
return _default !== self.IsChecked();
|
||||
};
|
||||
|
||||
this.Init(parentNode);
|
||||
}
|
||||
|
||||
// extending
|
||||
CCheckBox.prototype = Object.create(CBase.prototype);
|
||||
CCheckBox.prototype.constructor = CCheckBox;
|
||||
|
||||
/**
|
||||
* Default values for element initialization
|
||||
* @type {Object}
|
||||
*/
|
||||
CCheckBox.DefaultAttributes = {
|
||||
checkbox: {
|
||||
type: 'checkbox',
|
||||
className: 'ccheck-box-input'
|
||||
},
|
||||
label: {
|
||||
className: 'ccheck-box-label'
|
||||
},
|
||||
wrapper: {
|
||||
className: 'ccheck-box-wrapper',
|
||||
tabIndex: '1'
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Callback for CBase.Init function
|
||||
* Checkbox initialize
|
||||
*/
|
||||
CCheckBox.prototype.onInit = function(){
|
||||
if (this.parameters.element !== undefined && this.parameters.element.nodeName === 'INPUT' && this.parameters.element.type === 'checkbox'){
|
||||
this.parameters.element.parentNode.removeChild(this.parameters.element);
|
||||
this.checkbox = this.parameters.element;
|
||||
elattr(this.checkbox, extend(CCheckBox.DefaultAttributes.checkbox, this.parameters.attributes.checkbox, true));
|
||||
}
|
||||
// if checkbox not specified in parameters or specified wrong then generate it
|
||||
if (this.checkbox === undefined){
|
||||
this.checkbox = this.generateCheckbox();
|
||||
}
|
||||
this.wrapper = this.generateWrapper(this.checkbox); // create wrapper
|
||||
this.wrapper.className += ' ' + this.style;
|
||||
if (this.parameters.checked === true) {
|
||||
this.Check(true, true);
|
||||
} // check checkbox if needed
|
||||
elchild(this.handleInner, this.wrapper);
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate input[type=checkbox] element with necessary parameters
|
||||
* @returns {Node}
|
||||
*/
|
||||
CCheckBox.prototype.generateCheckbox = function() {
|
||||
var self = this,
|
||||
attrs = extend(CCheckBox.DefaultAttributes.checkbox, this.parameters.attributes.checkbox, true);
|
||||
if (attrs.id === undefined) {
|
||||
attrs.id = CCheckBox.GenerateId();
|
||||
}
|
||||
attrs.onchange = function(){
|
||||
self.checked = this.checked;
|
||||
self.trigger('onChange', arguments);
|
||||
};
|
||||
attrs.onclick = function() {
|
||||
self.Check(!self.IsChecked());
|
||||
};
|
||||
return element('input', attrs);
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate wrapper for checkbox and label
|
||||
* @param checkbox {Node} current checkbox
|
||||
* @param [label] {Node} label for this checkbox
|
||||
* @returns {Node} new wrapper
|
||||
*/
|
||||
CCheckBox.prototype.generateWrapper = function( checkbox, label ){
|
||||
var self = this;
|
||||
if (this.checkbox === undefined){
|
||||
throw('Checkbox must be created before label');
|
||||
}
|
||||
this.label = element('label', extend({htmlFor: this.checkbox.id}, CCheckBox.DefaultAttributes.label));
|
||||
this.label.onclick = function(){self.Check(!self.IsChecked());};
|
||||
return element('div', CCheckBox.DefaultAttributes.wrapper, [checkbox, label || this.label]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate random id for checkbox
|
||||
* @return {string}
|
||||
*/
|
||||
CCheckBox.GenerateId = function(){
|
||||
return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
|
||||
};
|
||||
|
||||
/**
|
||||
* Check or uncheck the checkbox
|
||||
* @param {boolean} checked true - checked, false - unchecked
|
||||
* @param {boolean} [block_event] true - not trigger onchange event
|
||||
*/
|
||||
CCheckBox.prototype.Check = function(checked, block_event){
|
||||
this.checked = this.checkbox.checked = checked === true;
|
||||
if (block_event !== true) {
|
||||
this.trigger('onChange');
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Return checkbox state
|
||||
* @returns {boolean}
|
||||
*/
|
||||
CCheckBox.prototype.IsChecked = function(){
|
||||
return this.checkbox.checked === true;
|
||||
};
|
||||
|
||||
CCheckBox.prototype.EventHandler = function(event){
|
||||
switch (event.code){
|
||||
case KEYS.LEFT:
|
||||
case KEYS.RIGHT:
|
||||
this.Check(event.code === KEYS.LEFT);
|
||||
event.stopped = true;
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
};
|
||||
|
||||
CCheckBox.prototype.focus = function(){
|
||||
this.wrapper.focus();
|
||||
};
|
||||
|
||||
Events.inject(CCheckBox);
|
|
@ -0,0 +1,296 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* Menu panel with groups module
|
||||
* General use case:
|
||||
* - create CGroupMenu object
|
||||
* - add group/groups
|
||||
* - fill group/groups with items
|
||||
* - switch to the default group
|
||||
* @class CGroupMenu
|
||||
* @constructor
|
||||
* @author Stanislav Kalashnik <sk@infomir.eu>
|
||||
*/
|
||||
function CGroupMenu ( parent ) {
|
||||
// parent constructor
|
||||
CBase.call(this, parent);
|
||||
|
||||
/**
|
||||
* The component inner name
|
||||
* @type {string}
|
||||
*/
|
||||
this.name = 'CGroupMenu';
|
||||
|
||||
/**
|
||||
* CSS class name associated with the component
|
||||
* @type {string}
|
||||
*/
|
||||
this.baseClass = 'cgmenu-main';
|
||||
|
||||
/**
|
||||
* current active group
|
||||
* @type {Node}
|
||||
*/
|
||||
this.activeGroup = null;
|
||||
|
||||
/**
|
||||
* is the component has focus at the moment
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.active = false;
|
||||
|
||||
/**
|
||||
* move focus to the first marked item in the group
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.autoFocus = true;
|
||||
}
|
||||
|
||||
|
||||
// extending
|
||||
CGroupMenu.prototype = Object.create(CBase.prototype);
|
||||
CGroupMenu.prototype.constructor = CGroupMenu;
|
||||
|
||||
|
||||
/**
|
||||
* Component initialization with its placeholder.
|
||||
* Should be called once before use just after constructor invoke and all hooks preparation.
|
||||
* @param {Node} handle component placeholder (it should be an empty div element)
|
||||
*/
|
||||
CGroupMenu.prototype.Init = function ( handle ) {
|
||||
CBase.prototype.Init.call(this, handle);
|
||||
|
||||
this.handle.onclick = function(event){
|
||||
// stop click
|
||||
event.stopPropagation();
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Finds and returns the group data object
|
||||
* @param {string|number} gid unique name of the group
|
||||
* @return {Object|boolean} found group data object or false
|
||||
*/
|
||||
CGroupMenu.prototype.GetGroup = function ( gid ) {
|
||||
// iterate all items
|
||||
for ( var i = 0, items = this.handleInner.children, length = items.length; i < length; i++ ) {
|
||||
// check data and validate id
|
||||
if ( items[i].gid === gid ) {
|
||||
return items[i];
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Add a new group with items
|
||||
* @param {string|number} gid unique name of the group
|
||||
* @param {string} title group visible title
|
||||
* @param {Object} [options] list of the group attributes (onclick and hidden)
|
||||
* @return {Node|boolean} group data object
|
||||
*/
|
||||
CGroupMenu.prototype.AddGroup = function ( gid, title, options ) {
|
||||
var self = this;
|
||||
// valid gid and not already exist
|
||||
if ( gid && !this.GetGroup(gid) ) {
|
||||
// CScrollList placeholder
|
||||
var aleft, aright,
|
||||
hlist = element('div', {className:'cslist-main'});
|
||||
// create html
|
||||
var group = element('table', {
|
||||
gid : gid,
|
||||
slist : new CScrollList(),
|
||||
options : options || {}
|
||||
}, [
|
||||
// title row
|
||||
element('tr', {className:'title'}, [
|
||||
element('td', {className:'side', onclick:function(){
|
||||
self.Switch(self.Next({hidden:false}, true), true);
|
||||
}}, aleft = element('p')), // arrow left
|
||||
element('td', null, title), // group title
|
||||
element('td', {className:'side', onclick:function(){
|
||||
self.Switch(self.Next({hidden:false}), true);
|
||||
}}, aright = element('p')) // arrow right
|
||||
]),
|
||||
// group items row
|
||||
element('tr', {className:'ilist'}, element('td', {colSpan:3}, hlist))
|
||||
]
|
||||
);
|
||||
// correct group attributes if necessary
|
||||
group.options.hidden = group.options.hidden ? true : false;
|
||||
// create scroll list
|
||||
group.slist.Init(hlist);
|
||||
group.aleft = aleft;
|
||||
group.aright = aright;
|
||||
// add to dom
|
||||
elchild(this.handleInner, group);
|
||||
// ok
|
||||
return group;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Manage the given group visibility
|
||||
* @param {Node} group the group element to alter
|
||||
* @param {boolean} state true - set hidden; false - set visible
|
||||
*/
|
||||
CGroupMenu.prototype.Hidden = function ( group, state ) {
|
||||
// valid group object
|
||||
if ( group && group.nodeName ) {
|
||||
// set inner attribute
|
||||
group.options.hidden = state;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Fill the given group with specified item
|
||||
* @param {Object} group group data object
|
||||
* @param {string|number} iid unique name of the group item
|
||||
* @param {string|HTMLElement|Array} body group item title or complex content
|
||||
* @param {Object} [options] group item additional attributes (hidden, marked, focused, disabled)
|
||||
* @return {Node} created and added group item
|
||||
*/
|
||||
CGroupMenu.prototype.AddItem = function ( group, iid, body, options ) {
|
||||
// prepare attributes list
|
||||
options = options || {};
|
||||
// item id
|
||||
options.iid = iid;
|
||||
|
||||
// default handler for each item if not overwritten
|
||||
if ( group.options.onclick && !options.onclick ) {
|
||||
options.onclick = group.options.onclick;
|
||||
}
|
||||
if ( options.icon ) {
|
||||
body = element('div', {className:'short'}, body);
|
||||
body.style.backgroundImage = 'url("' + options.icon + '")';
|
||||
body.style.backgroundPosition = 'right';
|
||||
body.style.backgroundRepeat = 'no-repeat';
|
||||
}
|
||||
// add item to the group list
|
||||
return group.slist.Add(body, options);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Apply visual styles for group arrows
|
||||
* depending on the neighbour groups activity
|
||||
*/
|
||||
CGroupMenu.prototype.ApplyArrows = function () {
|
||||
this.activeGroup.aright.className = this.Next({hidden:false}) ? 'active' : '';
|
||||
this.activeGroup.aleft.className = this.Next({hidden:false}, true) ? 'active' : '';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Switch to the given group (hides the current one)
|
||||
* @param {Object} group new active group
|
||||
* @param {boolean} [activate] flag: true - make new visible group focused; false - skip activation (default)
|
||||
* @return {boolean} operation status: true - successfully switched
|
||||
*/
|
||||
CGroupMenu.prototype.Switch = function ( group, activate ) {
|
||||
// validate group object
|
||||
if ( group && group.gid && group.nodeName ) {
|
||||
// hide the previous one
|
||||
if ( this.activeGroup ) {
|
||||
this.activeGroup.slist.Activate(false);
|
||||
this.activeGroup.style.display = 'none';
|
||||
}
|
||||
// set global active flag
|
||||
this.activeGroup = group;
|
||||
// show it
|
||||
this.activeGroup.style.display = 'table';
|
||||
// set focus and active state if necessary
|
||||
if ( activate ) {
|
||||
this.activeGroup.slist.Activate();
|
||||
// sync focus to marked item
|
||||
if ( this.autoFocus && Array.isArray(this.activeGroup.slist.states.marked) ) {
|
||||
// get the first marked and focus it
|
||||
this.activeGroup.slist.Focused(this.activeGroup.slist.states.marked[0], true);
|
||||
}
|
||||
}
|
||||
// apply visual styles for group arrows
|
||||
this.ApplyArrows();
|
||||
// ok
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set active group
|
||||
* @param {boolean} [active=true] set active or deactivate
|
||||
* @param {boolean} [setFocus=true] focus handling mode: true - set/remove focus accordingly, false - manual focus management
|
||||
* @return {boolean} operation status
|
||||
*/
|
||||
CGroupMenu.prototype.Activate = function ( active, setFocus ) {
|
||||
var status = false;
|
||||
if ( this.activeGroup !== null ) {
|
||||
status = this.activeGroup.slist.Activate(active, setFocus);
|
||||
}
|
||||
// sync focus to marked item
|
||||
if ( this.autoFocus && Array.isArray(this.activeGroup.slist.states.marked) ) {
|
||||
// get the first marked and focus it
|
||||
this.activeGroup.slist.Focused(this.activeGroup.slist.states.marked[0], true);
|
||||
}
|
||||
return status;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the next/previous item from the current item
|
||||
* according to the given filter and search direction
|
||||
* searching for a closest next item by default
|
||||
* @param {Object} [filter=this.defaultFilter] list of attributes for searching
|
||||
* @param {boolean} [reverse=false] to invert search direction (true - return previous, false - next)
|
||||
* @return {Node|null} found item or null if there are no suitable ones
|
||||
*/
|
||||
CGroupMenu.prototype.Next = function ( filter, reverse ) {
|
||||
// preparing
|
||||
var match, // flag for items comparison
|
||||
pointer = this.activeGroup; // the floating current item for processing
|
||||
// there is a starting item
|
||||
if ( pointer ) {
|
||||
// iterate from the current position till the edge of the list
|
||||
while ( (pointer = (reverse ? pointer.previousSibling : pointer.nextSibling)) ) {
|
||||
// suitable by default
|
||||
match = true;
|
||||
// check all the filter attributes (all should match)
|
||||
for ( var attr in filter ) {
|
||||
if ( filter.hasOwnProperty(attr) ) {
|
||||
match = match && (pointer.options[attr] === filter[attr]);
|
||||
}
|
||||
}
|
||||
// suitable item is found
|
||||
if ( match ) {
|
||||
return pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handle external events
|
||||
* @param {Event} event global event object
|
||||
*/
|
||||
CGroupMenu.prototype.EventHandler = function ( event ) {
|
||||
// moving directions
|
||||
switch ( event.code ) {
|
||||
case KEYS.LEFT:
|
||||
this.Switch(this.Next({hidden:false}, true), true);
|
||||
break;
|
||||
case KEYS.RIGHT:
|
||||
this.Switch(this.Next({hidden:false}), true);
|
||||
break;
|
||||
default:
|
||||
// delegate everything else to scroll list
|
||||
this.activeGroup.slist.EventHandler(event);
|
||||
}
|
||||
};
|
|
@ -0,0 +1,400 @@
|
|||
'use strict';
|
||||
|
||||
var CINPUT_IMG_PATH = PATH_IMG_PUBLIC;
|
||||
|
||||
function CInput ( parent, parameters ) {
|
||||
var _default = '', self = this;
|
||||
this.parameters = parameters;
|
||||
/**
|
||||
* Default values for element initialization
|
||||
* @type {Object}
|
||||
*/
|
||||
this.attributes = {
|
||||
input : {
|
||||
className: 'cinput-input',
|
||||
type: parameters.type || 'text'
|
||||
},
|
||||
hint : {
|
||||
className: 'cinput-hint cinput-input'
|
||||
},
|
||||
icon : {
|
||||
className: 'cinput-icon'
|
||||
},
|
||||
wrapper : {
|
||||
className: 'cinput-wrapper'
|
||||
},
|
||||
'text-wrapper': {
|
||||
className: 'cinput-text-wrapper'
|
||||
}
|
||||
};
|
||||
|
||||
this.SetAutocomplete(parameters.autocomplete);
|
||||
|
||||
this.FoldedClass = 'folded';
|
||||
|
||||
if ( parameters.attributes !== undefined ) {
|
||||
extend(this.attributes, parameters.attributes);
|
||||
}
|
||||
if ( parameters.folded === true ) {
|
||||
this.attributes.wrapper.className += ' ' + this.FoldedClass;
|
||||
}
|
||||
if ( parameters.style !== undefined ) {
|
||||
this.attributes.wrapper.className += ' ' + parameters.style;
|
||||
}
|
||||
|
||||
this.events = parameters.events || {};
|
||||
|
||||
CBase.call(this, parent || null);
|
||||
|
||||
this.bind(this.events);
|
||||
|
||||
this.type = 'input';
|
||||
|
||||
this.name = parameters.name || 'input';
|
||||
|
||||
if ( !(this.parentNode = parameters.parent) ) {
|
||||
if ( parameters.input === undefined ){
|
||||
throw('parent or input parameter must be specified');
|
||||
}
|
||||
this.parentNode = parameters.input.parentNode;
|
||||
}
|
||||
this.parameters.icons = this.parameters.icons || [];
|
||||
|
||||
this.icons = {};
|
||||
|
||||
self.IsChanged = function () {
|
||||
return _default !== self.GetValue();
|
||||
};
|
||||
|
||||
self.GetDefault = function () {
|
||||
return _default;
|
||||
};
|
||||
|
||||
self.SetDefault = function ( def ){
|
||||
_default = def;
|
||||
};
|
||||
|
||||
this.Init(this.GenerateHandle());
|
||||
|
||||
elchild(this.parentNode, this.handle);
|
||||
}
|
||||
|
||||
// extending
|
||||
CInput.prototype = Object.create(CBase.prototype);
|
||||
CInput.prototype.constructor = CInput;
|
||||
|
||||
CInput.prototype.GenerateHandle = function () {
|
||||
var i, icon, attrs, changed,
|
||||
self = this;
|
||||
this.row = element('tr');
|
||||
if ( this.parameters.hint !== undefined ) {
|
||||
this.DefaultHint = this.attributes.hint.value = this.parameters.hint;
|
||||
this.hint = element('input', extend(this.attributes.input, this.attributes.hint, false));
|
||||
}
|
||||
if ( this.parameters.input === undefined ) {
|
||||
if ( this.parameters.value !== undefined ){
|
||||
this.attributes.input.value = this.parameters.value;
|
||||
this.SetDefault(this.parameters.value);
|
||||
}
|
||||
elchild(this.row, element('td', this.attributes['text-wrapper'], [this.input = element('input', this.attributes.input), this.hint || '']));
|
||||
} else {
|
||||
this.input = this.parameters.input;
|
||||
this.input.parentNode.removeChild(this.input);
|
||||
this.input.className += this.attributes.input.className;
|
||||
elchild(this.row, element('td', {}, element('div', this.attributes['text-wrapper'], [this.input, this.hint || ''])));
|
||||
if ( this.input.value !== '' ){
|
||||
this.ShowHint(false);
|
||||
this.SetDefault(this.input.value);
|
||||
}
|
||||
}
|
||||
this.input.oninput = function ( e ) {
|
||||
changed = false;
|
||||
self.trigger('onChange', e);
|
||||
if ( self.GetValue() !== '' ) {
|
||||
self.ShowHint(false);
|
||||
} else {
|
||||
self.SetHint(self.DefaultHint);
|
||||
self.ShowHint(true);
|
||||
}
|
||||
};
|
||||
this.input.onblur = function () {
|
||||
if ( self.GetValue() !== '' ) {
|
||||
self.ShowHint(changed);
|
||||
} else {
|
||||
self.SetHint(self.DefaultHint);
|
||||
self.ShowHint(true);
|
||||
}
|
||||
};
|
||||
|
||||
if ( this.parameters.icons !== undefined ) {
|
||||
for ( i = 0; i < this.parameters.icons.length; i++ ) {
|
||||
icon = this.parameters.icons[i];
|
||||
attrs = extend({}, this.attributes.icon);
|
||||
attrs.className += ' ' + (icon.style || '');
|
||||
this.AddIcon(icon, attrs);
|
||||
}
|
||||
}
|
||||
return element('table', this.attributes.wrapper, this.row);
|
||||
|
||||
};
|
||||
|
||||
CInput.prototype.SetAutocomplete = function ( autocomplete ){
|
||||
this.autocomplete = autocomplete;
|
||||
};
|
||||
|
||||
CInput.prototype.onInit = function () {
|
||||
|
||||
};
|
||||
|
||||
CInput.prototype.ShowIcon = function ( name, show ) {
|
||||
if ( this.icons[name] !== undefined ) {
|
||||
this.icons[name].parentNode.style.display = show === true ? 'table-cell' : 'none';
|
||||
}
|
||||
};
|
||||
|
||||
CInput.prototype.ApplyHint = function () {
|
||||
this.SetValue(this.GetHint());
|
||||
};
|
||||
|
||||
CInput.prototype.AddIcon = function ( icon, attrs ) {
|
||||
var self = this, iconFile = null,
|
||||
attributes = extend(icon.attributes, {}),
|
||||
remoteControlButtonsImagesPath = configuration.newRemoteControl ? PATH_IMG_SYSTEM + 'buttons/new/' : PATH_IMG_SYSTEM + 'buttons/old/';
|
||||
|
||||
if ( icon.src !== undefined ) {
|
||||
iconFile = icon.src;
|
||||
} else {
|
||||
iconFile = icon.name ? icon.name + '.png' : null;
|
||||
}
|
||||
|
||||
if (iconFile === 'f4.png') {
|
||||
if ( iconFile ) {
|
||||
attributes.src = (this.parameters.icons_path || remoteControlButtonsImagesPath) + iconFile;
|
||||
}
|
||||
} else {
|
||||
if ( iconFile ) {
|
||||
attributes.src = (this.parameters.icons_path || CINPUT_IMG_PATH) + iconFile;
|
||||
}
|
||||
}
|
||||
|
||||
element('td', attrs || this.attributes.icon, this.icons[icon.name] = element('img', attributes));
|
||||
if ( icon.type === 'left' ) {
|
||||
this.row.insertBefore(this.icons[icon.name].parentNode, this.row.firstChild);
|
||||
} else {
|
||||
this.row.appendChild(this.icons[icon.name].parentNode);
|
||||
}
|
||||
if ( typeof icon.click === 'function' ) {
|
||||
this.icons[icon.name].onclick = function ( event ) {
|
||||
icon.click.call(self, event);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
CInput.prototype.RemoveIcon = function ( icon_name ) {
|
||||
if ( this.icons[icon_name] !== undefined ) {
|
||||
this.row.removeChild(this.icons[icon_name].parentNode);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param value
|
||||
* @param [siltent]
|
||||
* @constructor
|
||||
*/
|
||||
CInput.prototype.SetValue = function ( value, siltent ) {
|
||||
this.input.value = value;
|
||||
this.ShowHint(value === '');
|
||||
if ( siltent !== true ){
|
||||
this.trigger('onChange');
|
||||
this.input.oninput();
|
||||
}
|
||||
};
|
||||
|
||||
CInput.prototype.GetValue = function () {
|
||||
return this.input.value;
|
||||
};
|
||||
|
||||
CInput.prototype.SetHint = function ( hint ) {
|
||||
if ( this.hint !== undefined ){
|
||||
this.hint.value = hint;
|
||||
return this.hint.value;
|
||||
}
|
||||
};
|
||||
|
||||
CInput.prototype.GetHint = function () {
|
||||
if ( this.hint !== undefined ){
|
||||
return this.hint.value;
|
||||
}
|
||||
};
|
||||
|
||||
CInput.prototype.ShowHint = function ( show ) {
|
||||
if ( this.hint !== undefined ){
|
||||
this.hint.style.display = show === true ? 'block' : 'none';
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param fold
|
||||
* @param [stop_event]
|
||||
* @param [manage_focus]
|
||||
* @constructor
|
||||
*/
|
||||
CInput.prototype.Fold = function ( fold, manage_focus, stop_event ) {
|
||||
if ( fold === true ) {
|
||||
if ( this.handle.className.indexOf('folded') === -1 ) {
|
||||
this.handle.className += ' ' + this.FoldedClass;
|
||||
if ( stop_event !== true ) {
|
||||
this.trigger('onFold');
|
||||
}
|
||||
if ( manage_focus === true ) {
|
||||
this.blur();
|
||||
}
|
||||
}
|
||||
gSTB.HideVirtualKeyboard();
|
||||
} else {
|
||||
this.handle.className = this.handle.className.replace(this.FoldedClass, '');
|
||||
if ( stop_event !== true ) {
|
||||
this.trigger('onUnfold');
|
||||
}
|
||||
if ( manage_focus === true ) {
|
||||
this.focus();
|
||||
gSTB.ShowVirtualKeyboard();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
CInput.prototype.Reset = function () {
|
||||
this.Fold(this.parameters.folded === true, true);
|
||||
this.SetValue(this.GetDefault(), true);
|
||||
};
|
||||
|
||||
CInput.prototype.focus = function () {
|
||||
this.input.focus();
|
||||
};
|
||||
|
||||
CInput.prototype.blur = function () {
|
||||
this.input.blur();
|
||||
};
|
||||
|
||||
CInput.prototype.select = function () {
|
||||
this.input.select();
|
||||
};
|
||||
|
||||
/**
|
||||
* @return {boolean}
|
||||
*/
|
||||
CInput.prototype.IsFolded = function () {
|
||||
return this.handle.className.indexOf(this.FoldedClass) !== -1;
|
||||
};
|
||||
|
||||
/**
|
||||
* @return {boolean}
|
||||
*/
|
||||
CInput.prototype.IsFocused = function () {
|
||||
return this.input === document.activeElement;
|
||||
};
|
||||
|
||||
CInput.prototype.EventHandler = function () {};
|
||||
|
||||
Events.inject(CInput);
|
||||
|
||||
|
||||
/**
|
||||
* Filter input component
|
||||
* @param parent
|
||||
* @param parameters
|
||||
* @constructor
|
||||
* @example weather_location = new CFilterInput(this, {
|
||||
input: document.getElementById("place"),
|
||||
hint: "Enter city name please...",
|
||||
folded: true,
|
||||
events:{
|
||||
onChange: function(){
|
||||
weather.getSuggestsList(this.GetValue());
|
||||
},
|
||||
onUnfold: function(){
|
||||
WeatherPage.$bcrumb.style.display = 'none';
|
||||
},
|
||||
onFold: function(){
|
||||
WeatherPage.$bcrumb.style.display = 'table-cell';
|
||||
},
|
||||
onKey: function(){
|
||||
weather.newLocation();
|
||||
},
|
||||
onExit: function(){
|
||||
WeatherPage.actionExit();
|
||||
}
|
||||
}
|
||||
});
|
||||
*/
|
||||
function CFilterInput ( parent, parameters ) {
|
||||
parameters = extend({
|
||||
style: 'main-filter-input',
|
||||
icons: [
|
||||
{
|
||||
name : 'ico_search',
|
||||
type : 'left',
|
||||
style: 'black_search'
|
||||
},
|
||||
{
|
||||
name : 'ico_search2',
|
||||
type : 'left',
|
||||
style: 'white_search'
|
||||
},
|
||||
{
|
||||
name : 'f4',
|
||||
click: function () {
|
||||
this.Fold(!this.IsFolded(), true);
|
||||
this.trigger('onKey');
|
||||
}
|
||||
}
|
||||
]
|
||||
}, parameters);
|
||||
CInput.call(this, parent || null, parameters);
|
||||
}
|
||||
|
||||
// extending
|
||||
CFilterInput.prototype = Object.create(CInput.prototype);
|
||||
CFilterInput.prototype.constructor = CFilterInput;
|
||||
|
||||
// true - stop next events
|
||||
/**
|
||||
* @return {boolean}
|
||||
*/
|
||||
CFilterInput.prototype.EventHandler = function ( event ) {
|
||||
eventPrepare(event, true, 'CFilterInput');
|
||||
var event_res = false;
|
||||
if ( this.trigger('onEvent', event)[0] === true ) {
|
||||
return true;
|
||||
}
|
||||
switch ( event.code ) {
|
||||
case KEYS.F4:
|
||||
this.Fold(!this.IsFolded(), true);
|
||||
event_res = this.trigger('onKey')[0];
|
||||
break;
|
||||
case KEYS.EXIT:
|
||||
if ( !this.IsFolded() ) {
|
||||
this.Fold(true, true);
|
||||
this.trigger('onExit');
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case KEYS.OK:
|
||||
if ( !this.IsFolded() ) {
|
||||
event_res = this.trigger('onEnter')[0];
|
||||
echo(event_res, 'onEnter');
|
||||
this.Fold(true, true);
|
||||
event.preventDefault();
|
||||
}
|
||||
break;
|
||||
case KEYS.CHANNEL_NEXT:
|
||||
case KEYS.CHANNEL_PREV:
|
||||
event.preventDefault();
|
||||
break;
|
||||
}
|
||||
if ( this.IsFolded() !== true || event_res === true ) {
|
||||
return true;
|
||||
}
|
||||
};
|
|
@ -0,0 +1,860 @@
|
|||
/**
|
||||
* Item list without native scroll navigation module
|
||||
* @author Roman Stoian
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @class CList
|
||||
* @param parent
|
||||
* @param {Object} customParams
|
||||
* @constructor
|
||||
*/
|
||||
function CList ( parent, customParams ) {
|
||||
// parent constructor
|
||||
CBase.call( this, parent );
|
||||
|
||||
if ( !customParams ) {
|
||||
customParams = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* The component inner name
|
||||
* @type {string}
|
||||
*/
|
||||
this.name = 'CList';
|
||||
|
||||
/**
|
||||
* CSS class name associated with the component
|
||||
* @type {string}
|
||||
*/
|
||||
this.baseClass = 'clist-main';
|
||||
/**
|
||||
* Shows the possibility of multiple selection
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.multipleSelection = true;
|
||||
|
||||
/**
|
||||
* the current selected item
|
||||
* @type {Node}
|
||||
*/
|
||||
this.activeItem = null;
|
||||
this.items = [];
|
||||
this.list = [];
|
||||
this.itemIndex = 0;
|
||||
this.listIndex = 0;
|
||||
this.pageIndex = 0;
|
||||
this.itemsHandle = null;
|
||||
this.scrollHandle = null;
|
||||
this.scrollThumb = null;
|
||||
|
||||
/**
|
||||
* List of items for each state flag
|
||||
* Example: {focused:[item], marked:[item,item]}
|
||||
* @type {Object}
|
||||
*/
|
||||
this.states = {};
|
||||
|
||||
/**
|
||||
* Default settings for focus management in list
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.manageFocus = false;
|
||||
|
||||
/**
|
||||
* default item attribute values
|
||||
* used for an item initialization
|
||||
* @namespace
|
||||
* @property {boolean} hidden display or not
|
||||
* @property {boolean} marked initial checked state
|
||||
* @property {boolean} disabled can be focused or not
|
||||
* @property {boolean} focused initial focused state
|
||||
*/
|
||||
this.defaultParams = {
|
||||
hidden: false, // display or not
|
||||
marked: false, // initial checked state
|
||||
disabled: false, // can be focused or not
|
||||
focused: false, // initial focused state
|
||||
self: this, // back link to the component itself
|
||||
index: -1, // index of list elements
|
||||
data: null,
|
||||
// flag to manage focus handling
|
||||
manageFocus: false,
|
||||
// right mouse click (suppress the context menu)
|
||||
oncontextmenu: EMULATION? null : function () {
|
||||
return false;
|
||||
},
|
||||
// mouse click on the item or Ok/Enter key
|
||||
onclick: function () {
|
||||
// activate item
|
||||
this.self.OnMouseover( this );
|
||||
this.self.pressOK( this );
|
||||
return false;
|
||||
},
|
||||
onmouseover: function () {
|
||||
// activate item
|
||||
this.self.OnMouseover( this );
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
if ( customParams.defaultParams ) {
|
||||
this.defaultParams = extend ( this.defaultParams, customParams.defaultParams );
|
||||
}
|
||||
|
||||
this.onPage = customParams.onPage ? customParams.onPage : (function () {
|
||||
var count = {
|
||||
480: 5,
|
||||
576: 6,
|
||||
720: 7,
|
||||
1080: 7
|
||||
};
|
||||
return count[ screen.height ];
|
||||
})();
|
||||
|
||||
this.fillsName = customParams.fillsName ? customParams.fillsName : {
|
||||
text: 'text',
|
||||
data: 'data'
|
||||
};
|
||||
|
||||
/**
|
||||
* default item filter values
|
||||
* used for focus handling
|
||||
* @type {Object}
|
||||
*/
|
||||
this.defaultFilter = {
|
||||
hidden: false, // visible
|
||||
disabled: false // enabled
|
||||
};
|
||||
|
||||
/**
|
||||
* buffer for added items in the bulk mode
|
||||
* @type {DocumentFragment}
|
||||
*/
|
||||
this.fragment = document.createDocumentFragment();
|
||||
}
|
||||
|
||||
// extending
|
||||
CList.prototype = Object.create ( CBase.prototype );
|
||||
CList.prototype.constructor = CList;
|
||||
|
||||
|
||||
/**
|
||||
* set list.
|
||||
* @param {Array} list
|
||||
*/
|
||||
CList.prototype.SetList = function ( list ) {
|
||||
if ( Array.isArray ( list ) ) {
|
||||
this.list = list;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* set list.
|
||||
* @param {Array} arr list elements
|
||||
* @param {number} start position in list
|
||||
* @param {number} deleteCount count delete elements in list
|
||||
*/
|
||||
CList.prototype.InsertToList = function ( arr, start, deleteCount ) {
|
||||
var lastIndex = 0,
|
||||
activeItem = this.list[ this.listIndex ];
|
||||
if ( Array.isArray( arr ) && this.list.length !== 0 ) {
|
||||
lastIndex = ( this.pageIndex + 1 ) * this.onPage - 1;
|
||||
if ( !start && start !== 0 ) {
|
||||
start = this.list.length;
|
||||
}
|
||||
if ( !deleteCount ) {
|
||||
deleteCount = 0;
|
||||
} else {
|
||||
this.list.splice ( start, deleteCount );
|
||||
}
|
||||
for ( var i = 0; i < arr.length; i++ ) {
|
||||
this.list.splice ( start + i, 0, arr[ i ] );
|
||||
}
|
||||
if ( lastIndex >= start ) {
|
||||
if ( start <= this.listIndex ) {
|
||||
this.listIndex = this.FindByElement( activeItem );
|
||||
this.RefreshPageIndex();
|
||||
}
|
||||
this.FillItems();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* set list.
|
||||
* @param {Array} arr list elements
|
||||
*/
|
||||
CList.prototype.DeleteFromList = function ( arr ) {
|
||||
var index = -1,
|
||||
listIndex = this.listIndex,
|
||||
activeItem = this.list[ this.listIndex ];
|
||||
if ( Array.isArray( arr ) && this.list.length !== 0 ) {
|
||||
for ( var i = 0; i < arr.length; i++ ){
|
||||
index = this.list.indexOf( arr[ i ] );
|
||||
if( index !== -1 ) {
|
||||
this.list.splice( index, 1 );
|
||||
if ( index < listIndex ) {
|
||||
listIndex--;
|
||||
}
|
||||
}
|
||||
}
|
||||
index = this.list.indexOf( activeItem );
|
||||
if ( index === -1 ) {
|
||||
this.listIndex = listIndex;
|
||||
if ( this.listIndex >= this.list.length ){
|
||||
this.listIndex = this.list.length - 1;
|
||||
}
|
||||
} else {
|
||||
this.listIndex = index;
|
||||
}
|
||||
this.RefreshPageIndex();
|
||||
this.FillItems();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* refresh page index.
|
||||
* @return {boolean} change page
|
||||
*/
|
||||
CList.prototype.RefreshPageIndex = function () {
|
||||
var pageIndex = this.pageIndex;
|
||||
|
||||
this.pageIndex = Math.floor( this.listIndex / this.onPage );
|
||||
this.itemIndex = this.listIndex % this.onPage;
|
||||
|
||||
return pageIndex !== this.pageIndex;
|
||||
};
|
||||
|
||||
/**
|
||||
* find by element in list.
|
||||
* @param {Object} obj list elements
|
||||
* @return {number} index of element in list
|
||||
*/
|
||||
CList.prototype.FindByElement = function ( obj ) {
|
||||
return this.list.indexOf( obj );
|
||||
};
|
||||
|
||||
/**
|
||||
* find by fild of element in list.
|
||||
* @param {*} value
|
||||
* @param {string} fild of list element
|
||||
* @return {number} index of element in list
|
||||
*/
|
||||
CList.prototype.FindByFild = function ( value, fild ) {
|
||||
var index = -1,
|
||||
map = [];
|
||||
if ( value !== undefined ) {
|
||||
if ( !fild ) {
|
||||
fild = this.fillsName.data;
|
||||
}
|
||||
map = this.list.map( function ( item ) {
|
||||
return item[ fild ];
|
||||
});
|
||||
index = map.indexOf( value );
|
||||
}
|
||||
return index;
|
||||
};
|
||||
|
||||
/**
|
||||
* Component initialization with its placeholder.
|
||||
* Should be called once before use just after constructor invoke and all hooks preparation.
|
||||
* @param {Node} handle component placeholder (it should be an empty div element)
|
||||
*/
|
||||
CList.prototype.Init = function ( handle ) {
|
||||
// parent call init with placeholder
|
||||
CBase.prototype.Init.call( this, handle );
|
||||
var self = this,
|
||||
table = null;
|
||||
table = element ( 'table', {className: 'maxh maxw'},
|
||||
element ( 'tr', {}, [
|
||||
this.itemsHandle = element ( 'td', {className: 'list'} ),
|
||||
element ('td', {className: 'scroll'},
|
||||
this.scrollHandle = element ( 'div' )
|
||||
)
|
||||
])
|
||||
);
|
||||
elchild ( this.scrollHandle, this.scrollThumb = element ( 'div', {className: 'thumb'} ) );
|
||||
elchild ( this.handleInner, table );
|
||||
this.handleInner.onmousewheel = function (event) {
|
||||
// direction and new focused item
|
||||
var direction = event.wheelDeltaY > 0;
|
||||
var found = direction ? self.Prev() : self.Next();
|
||||
// apply
|
||||
if (found) {
|
||||
self.Focused(found, true);
|
||||
}
|
||||
event.stopPropagation();
|
||||
// prevent
|
||||
return false;
|
||||
};
|
||||
this.RenderBody();
|
||||
};
|
||||
|
||||
/**
|
||||
* On show event
|
||||
*/
|
||||
CList.prototype.onShow = function () {
|
||||
this.setScroll();
|
||||
};
|
||||
|
||||
/**
|
||||
* Add all the added items to the DOM
|
||||
*/
|
||||
CList.prototype.RenderBody = function () {
|
||||
if (this.itemsHandle.children.length === 0 || this.items.length === 0) { //generate dome elements if this need
|
||||
this.items = [];
|
||||
for (var i = 0; i < this.onPage; i++) {
|
||||
this.items[i] = this.fragment.appendChild( element ( 'div', this.defaultParams ));
|
||||
elchild(this.items[i],this.RenderItem());
|
||||
}
|
||||
this.itemsHandle.appendChild( this.fragment );
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate body item
|
||||
*/
|
||||
CList.prototype.RenderItem = function () {
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* set scroll position
|
||||
*/
|
||||
CList.prototype.setScroll = function () {
|
||||
var margin = 0,
|
||||
percent = 0;
|
||||
percent = Math.ceil ( this.onPage / this.list.length * 100 );
|
||||
if (percent >= 100) {
|
||||
percent = 0;
|
||||
}
|
||||
margin = Math.ceil ( this.scrollHandle.offsetHeight / Math.ceil( this.list.length / this.onPage ) * this.pageIndex );
|
||||
this.scrollThumb.style.height = percent + '%';
|
||||
this.scrollThumb.style.marginTop = margin + 'px';
|
||||
};
|
||||
|
||||
/**
|
||||
* Fill items
|
||||
* @params {boolean} noFocus don't set focus
|
||||
*/
|
||||
CList.prototype.FillItems = function ( noFocus ) {
|
||||
var startPos = this.onPage * this.pageIndex,
|
||||
listLength = this.list.length,
|
||||
active = null,
|
||||
list = [];
|
||||
for (var i = 0; i < this.onPage; i++) {
|
||||
if ( listLength > i + startPos ) {
|
||||
this.items[ i ].innerHTML = this.list[ i + startPos ][ this.fillsName.text ];
|
||||
list.push( this.list[ i + startPos ] );
|
||||
if ( this.list[ i + startPos ][ this.fillsName.data ] ) {
|
||||
this.items[ i ].data = this.list[ i + startPos ][ this.fillsName.data ];
|
||||
}
|
||||
this.items[ i ].index = i + startPos;
|
||||
this.Hidden( this.items[ i ], false );
|
||||
if ( i + startPos === this.listIndex ) {
|
||||
active = this.items[ i ];
|
||||
}
|
||||
} else {
|
||||
this.items[ i ].index = -1;
|
||||
this.items[ i ].data = null;
|
||||
this.Hidden( this.items[ i ], true );
|
||||
}
|
||||
}
|
||||
this.Reset ( active );
|
||||
if ( !noFocus ) {
|
||||
this.Focused( active, true );
|
||||
}
|
||||
this.setScroll();
|
||||
if ( typeof this.onFillItems === 'function' ) {
|
||||
this.onFillItems( active, list );
|
||||
}
|
||||
return active;
|
||||
};
|
||||
|
||||
/**
|
||||
* Fill items
|
||||
* @params {Object} active item
|
||||
* @param {Array} list items on page
|
||||
*/
|
||||
CList.prototype.onFillItems = null;
|
||||
|
||||
/**
|
||||
* Reset and clear all items and options.
|
||||
* This will make the component ready for a new filling.
|
||||
*/
|
||||
CList.prototype.Clear = function () {
|
||||
// cleaning all items
|
||||
this.list = [];
|
||||
this.pageIndex = 0;
|
||||
this.itemIndex = 0;
|
||||
this.listIndex = 0;
|
||||
// vars
|
||||
this.activeItem = null;
|
||||
this.states = {};
|
||||
this.FillItems();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Reset only the given item to the default state
|
||||
* @param {Node} item the element to be processed
|
||||
*/
|
||||
CList.prototype.Reset = function ( item ) {
|
||||
// valid html element given
|
||||
if ( item && item.nodeName ) {
|
||||
// apply flags and decoration
|
||||
this.Hidden( item, this.defaultParams.hidden );
|
||||
this.Marked( item, this.defaultParams.marked );
|
||||
this.Disabled( item, this.defaultParams.disabled );
|
||||
// clear focus pointer if necessary
|
||||
if ( item === this.activeItem && !item.focused ) {
|
||||
this.activeItem = null;
|
||||
}
|
||||
this.Focused( item, this.defaultParams.focused );
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Getter for list length
|
||||
* @return {number}
|
||||
*/
|
||||
CList.prototype.Length = function () {
|
||||
return this.list.length;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set inner item flags and decoration
|
||||
* @param {Node} item the element to be processed
|
||||
* @param {string} option item inner flag name
|
||||
* @param {boolean} state flag of the operation (true if change is made)
|
||||
* @return {boolean} operation status
|
||||
*/
|
||||
CList.prototype.SetState = function ( item, option, state ) {
|
||||
state = Boolean( state );
|
||||
// current and new states are different
|
||||
if ( item[ option ] !== state ) {
|
||||
// check if exist
|
||||
if ( !this.states[ option ] ) {
|
||||
this.states[ option ] = [];
|
||||
}
|
||||
var index = this.states[ option ].indexOf( item );
|
||||
// update internal list
|
||||
if ( state ) {
|
||||
// add to the list
|
||||
if ( index === -1 ) {
|
||||
this.states[ option ].push( item );
|
||||
}
|
||||
} else {
|
||||
// remove
|
||||
if ( index !== -1 ) {
|
||||
this.states[ option ].splice( index, 1 );
|
||||
}
|
||||
}
|
||||
var oldVal = item[ option ];
|
||||
// flag
|
||||
item[ option ] = state;
|
||||
// decoration
|
||||
if ( state ) {
|
||||
// add the corresponding class
|
||||
item.classList.add( option );
|
||||
} else {
|
||||
// remove the corresponding class
|
||||
item.classList.remove( option );
|
||||
}
|
||||
// call user hook
|
||||
if ( typeof this.onStateChange === 'function' ) {
|
||||
this.onStateChange( item, option, oldVal, state );
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// nothing has changed
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handle visibility state for the given item
|
||||
* also correct check/focus state if hiding
|
||||
* @param {Node} item the element to be processed
|
||||
* @param {boolean} state flag of the state
|
||||
* @return {boolean} operation status
|
||||
*/
|
||||
CList.prototype.Hidden = function ( item, state ) {
|
||||
state = Boolean( state );
|
||||
// valid html element given
|
||||
if ( item && item.nodeName ) {
|
||||
// flag and decoration
|
||||
var changed = this.SetState( item, 'hidden', state );
|
||||
// operation ok and the item is hidden
|
||||
if (changed && state) {
|
||||
// clear internal cursor if necessary
|
||||
if ( item.focused ) {
|
||||
this.activeItem = null;
|
||||
}
|
||||
// uncheck and remove focus
|
||||
this.SetState( item, 'marked', false );
|
||||
this.SetState( item, 'focused', false );
|
||||
}
|
||||
// operation status
|
||||
return changed;
|
||||
}
|
||||
// failure
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handle checked state for the given item
|
||||
* @param {Node} item the element to be processed
|
||||
* @param {boolean} state flag of the state
|
||||
* @return {boolean} operation status
|
||||
*/
|
||||
CList.prototype.Marked = function ( item, state ) {
|
||||
var self = this;
|
||||
state = Boolean( state );
|
||||
// valid html element given, enabled and visible
|
||||
if ( item && item.nodeName && !item.disabled && !item.hidden ) {
|
||||
if ( this.multipleSelection === false ) {
|
||||
( this.states.marked || [] ).forEach( function ( marked ) {
|
||||
self.SetState( marked, 'marked', false );
|
||||
});
|
||||
}
|
||||
// operation status
|
||||
return this.SetState( item, 'marked', state );
|
||||
}
|
||||
// failure
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handle enable/disable state for the given item
|
||||
* @param {Node} item the element to be processed
|
||||
* @param {boolean} state flag of the state
|
||||
* @return {boolean} operation status
|
||||
*/
|
||||
CList.prototype.Disabled = function ( item, state ) {
|
||||
state = Boolean( state );
|
||||
// valid html element given
|
||||
if ( item && item.nodeName ) {
|
||||
// flag and decoration
|
||||
var changed = this.SetState( item, 'disabled', state );
|
||||
// operation ok and the item is disabled
|
||||
if ( changed && state ) {
|
||||
// clear internal cursor if necessary
|
||||
if ( item.focused ) {
|
||||
this.activeItem = null;
|
||||
}
|
||||
// uncheck and remove focus
|
||||
this.SetState( item, 'marked', false );
|
||||
this.SetState( item, 'focused', false );
|
||||
}
|
||||
// operation status
|
||||
return changed;
|
||||
}
|
||||
// failure
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle focus state for the given item on mouseover
|
||||
* also removes the focus from the previously focused item
|
||||
* @param {Node} item the element to be processed
|
||||
*/
|
||||
CList.prototype.OnMouseover = function ( item ) {
|
||||
if ( this.listIndex === item.index ) {
|
||||
return false;
|
||||
}
|
||||
this.listIndex = item.index;
|
||||
this.RefreshPageIndex();
|
||||
this.Focused( item, true );
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handle focus state for the given item
|
||||
* also removes the focus from the previously focused item
|
||||
* @param {Node} item the element to be processed
|
||||
* @param {boolean} [state=true] flag of the state
|
||||
* @param {boolean} [manageFocus=true] flag to manage focus handling
|
||||
* @return {boolean} operation status
|
||||
*/
|
||||
CList.prototype.Focused = function ( item, state, manageFocus ) {
|
||||
var changed = false,
|
||||
prevent = false;
|
||||
state = state !== false;
|
||||
|
||||
if ( manageFocus === undefined ) {
|
||||
manageFocus = this.manageFocus;
|
||||
}
|
||||
// valid html element given, enabled and visible
|
||||
if ( item && item.nodeName && !item.disabled && !item.hidden ) {
|
||||
// states differ
|
||||
if ( state !== item.focused ) {
|
||||
if ( state ) {
|
||||
// different items (not currently active item)
|
||||
if ( item !== this.activeItem ) {
|
||||
// call user hook which can prevent further processing
|
||||
if ( typeof this.onFocus === 'function' ) {
|
||||
prevent = this.onFocus( item, this.activeItem );
|
||||
}
|
||||
// block or not
|
||||
if ( !prevent ) {
|
||||
// flag and decoration
|
||||
changed = this.SetState( item, 'focused', state );
|
||||
// clear the previously focused item
|
||||
this.Focused( this.activeItem, false, manageFocus );
|
||||
// global flag
|
||||
this.activeItem = item;
|
||||
// set actual focus if necessary
|
||||
if ( manageFocus !== false ) {
|
||||
this.activeItem.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// flag and decoration
|
||||
changed = this.SetState( item, 'focused', state );
|
||||
// focus removed if necessary
|
||||
if ( manageFocus !== false ) {
|
||||
this.activeItem.blur();
|
||||
}
|
||||
this.activeItem = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
// operation status
|
||||
return changed;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Make the whole component active, set focused item and give actual focus
|
||||
* give a focus to the appropriate item (last focused or the first one)
|
||||
* @param {boolean} [state=true] set active or deactivate
|
||||
* @param {boolean} [manageFocus=true] focus handling mode: true - set/remove focus accordingly, false - manual focus management
|
||||
* @return {boolean} operation status
|
||||
*/
|
||||
CList.prototype.Activate = function ( state, manageFocus ) {
|
||||
if ( manageFocus === undefined ) {
|
||||
manageFocus = this.manageFocus;
|
||||
}
|
||||
// parent call
|
||||
CBase.prototype.Activate.call( this, state );
|
||||
if ( this.isActive ) {
|
||||
// get the first good one
|
||||
this.activeItem = this.activeItem || this.ActiveItem();
|
||||
// still no active item
|
||||
if ( this.activeItem === null ) {
|
||||
return false;
|
||||
}
|
||||
// flag and decoration
|
||||
this.itemIndex = this.activeItem.index;
|
||||
if ( this.RefreshPageIndex() ) {
|
||||
this.FillItems();
|
||||
} else {
|
||||
this.SetState( this.activeItem, 'focused', true );
|
||||
}
|
||||
// make it focused
|
||||
if ( manageFocus === true ) {
|
||||
this.activeItem.focus();
|
||||
}
|
||||
} else {
|
||||
// remove focus if there is an element
|
||||
if ( this.activeItem ) {
|
||||
this.activeItem.blur();
|
||||
}
|
||||
}
|
||||
// all is ok
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get active item
|
||||
* @return {Node|null} found item or null
|
||||
*/
|
||||
CList.prototype.ActiveItem = function () {
|
||||
return this.items[ this.itemIndex ] || null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get active items
|
||||
* @return {Array} found items
|
||||
*/
|
||||
CList.prototype.ActiveItems = function () {
|
||||
var list = [];
|
||||
this.items.forEach( function ( item ) {
|
||||
if ( !item.disabled && !item.hidden ) {
|
||||
list.push( item );
|
||||
}
|
||||
});
|
||||
return list;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the next item from the current focused item
|
||||
* can go to the next page
|
||||
* @param {count} [count=1] count steps next
|
||||
* @return {Node|null} found item or null if there are no suitable ones
|
||||
*/
|
||||
CList.prototype.Next = function ( count ) {
|
||||
var next = null,
|
||||
endList = false;
|
||||
count = count ? count > 0 ? count : 1 : 1;
|
||||
this.itemIndex += count;
|
||||
this.listIndex += count;
|
||||
if ( this.listIndex >= this.list.length ) {
|
||||
this.listIndex = this.list.length - 1;
|
||||
endList = true;
|
||||
}
|
||||
if ( this.itemIndex >= this.onPage || endList ) {
|
||||
if ( this.RefreshPageIndex() ) {
|
||||
next = this.FillItems( false );
|
||||
} else {
|
||||
next = this.items[ this.itemIndex ];
|
||||
}
|
||||
} else {
|
||||
next = this.items[ this.itemIndex ];
|
||||
}
|
||||
this.Focused( next, true );
|
||||
return next;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the previous item from the current focused item
|
||||
* can go to the previous page
|
||||
* @param {count} [count=1] count steps next
|
||||
* @return {Node|null} found item or null if there are no suitable ones
|
||||
*/
|
||||
CList.prototype.Prev = function ( count ) {
|
||||
var prev = null;
|
||||
count = count ? count > 0 ? count : 1 : 1;
|
||||
this.itemIndex -= count;
|
||||
this.listIndex -= count;
|
||||
if ( this.listIndex < 0 ) {
|
||||
this.listIndex = 0;
|
||||
}
|
||||
if ( this.itemIndex < 0 ) {
|
||||
if ( this.RefreshPageIndex() ) {
|
||||
prev = this.FillItems( false );
|
||||
} else {
|
||||
prev = this.items[ this.itemIndex ];
|
||||
}
|
||||
} else {
|
||||
prev = this.items[ this.itemIndex ];
|
||||
}
|
||||
this.Focused( prev, true );
|
||||
return prev;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set position some list element
|
||||
* @param {Object|Node} item of list or dome
|
||||
* @param {boolean} [manageFocus] - set actual focus
|
||||
*/
|
||||
CList.prototype.SetPosition = function ( item, makeFocused ) {
|
||||
var index = this.items.indexOf( item );
|
||||
if ( index === -1 ) {
|
||||
index = this.list.indexOf( item );
|
||||
} else {
|
||||
index = item.index;
|
||||
}
|
||||
if ( index === -1 || index === undefined ) {
|
||||
return false;
|
||||
}
|
||||
this.listIndex = index;
|
||||
if ( this.RefreshPageIndex() ) {
|
||||
this.FillItems();
|
||||
} else if ( makeFocused ) {
|
||||
this.Focused( this.items[ this.itemIndex ], true );
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle external events
|
||||
* @param {Event} event global event object
|
||||
* @param {Object} [filter=this.defaultFilter] list of attributes for searching
|
||||
* @param {boolean} [manageFocus=this.manageFocus]
|
||||
*/
|
||||
CList.prototype.EventHandler = function ( event, filter, manageFocus ) {
|
||||
var found = null;
|
||||
if ( manageFocus === undefined ) {
|
||||
manageFocus = this.manageFocus;
|
||||
}
|
||||
if ( event.stopped === true ) {
|
||||
return;
|
||||
}
|
||||
// moving direction
|
||||
switch ( event.code ) {
|
||||
case KEYS.PAGE_UP:
|
||||
this.Prev( this.onPage );
|
||||
break;
|
||||
case KEYS.PAGE_DOWN:
|
||||
this.Next( this.onPage );
|
||||
break;
|
||||
case KEYS.LEFT:
|
||||
case KEYS.HOME:
|
||||
this.Prev( this.list.length );
|
||||
break;
|
||||
case KEYS.RIGHT:
|
||||
case KEYS.END:
|
||||
this.Next( this.list.length );
|
||||
break;
|
||||
case KEYS.UP:
|
||||
this.Prev();
|
||||
break;
|
||||
case KEYS.DOWN:
|
||||
this.Next();
|
||||
break;
|
||||
case KEYS.OK:
|
||||
this.pressOK(this.ActiveItem());
|
||||
event.preventDefault();
|
||||
return;
|
||||
default:
|
||||
// suppress everything else and exit
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
// make focused the first item if not found
|
||||
this.Focused( found || this.ActiveItem(), true, manageFocus );
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook method on focus item change
|
||||
* should be declared in child to invoke
|
||||
* @param {Node} item the new focused item
|
||||
*/
|
||||
CList.prototype.pressOK = function () {};
|
||||
|
||||
/**
|
||||
* Hook method on focus item change
|
||||
* should be declared in child to invoke
|
||||
* @param {Node} item the new focused item
|
||||
* @param {Node} previous the old focused item
|
||||
* @return {boolean} true - prevent focus from changing, false|undefined - usual behaviour
|
||||
*/
|
||||
CList.prototype.onFocus = null;
|
||||
|
||||
|
||||
/**
|
||||
* Hook method on item internal states change
|
||||
* should be declared in child to invoke
|
||||
* @param {Node} item the new focused item
|
||||
* @param {string} option affected item state name
|
||||
* @param {string|boolean} oldVal previous state value
|
||||
* @param {string|boolean} newVal new state value
|
||||
*/
|
||||
CList.prototype.onStateChange = null;
|
|
@ -0,0 +1,55 @@
|
|||
'use strict';
|
||||
|
||||
var CLog = function(parent, options){
|
||||
var self = this;
|
||||
CBase.call(this, parent || null);
|
||||
this.newest = false;
|
||||
CLog.parameters.forEach(function(option){
|
||||
if (options[option] !== undefined) {
|
||||
self[option] = options[option];
|
||||
}
|
||||
});
|
||||
this.bind(options.events || {});
|
||||
this.Init(element('div', {className: 'clog'}, this.$log = element('ul', {})));
|
||||
};
|
||||
|
||||
CLog.parameters = ['time', 'autofocus', 'parentNode', 'events', 'defaultType', 'newest', 'isVisible'];
|
||||
|
||||
// extending
|
||||
CLog.prototype = Object.create(CBase.prototype);
|
||||
CLog.prototype.constructor = CLog;
|
||||
|
||||
CLog.prototype.onInit = function(){
|
||||
elchild(this.parentNode, this.handle);
|
||||
};
|
||||
|
||||
CLog.prototype.onShow = function(){
|
||||
if (this.autofocus === true && this.$log.childElementCount !== 0){
|
||||
this.$log.lastElementChild.focus();
|
||||
}
|
||||
this.trigger('onShow');
|
||||
};
|
||||
|
||||
CLog.prototype.onHide = function(){
|
||||
this.trigger('onHide');
|
||||
};
|
||||
|
||||
CLog.prototype.Add = function( message, type){
|
||||
if (this.newest === false || (this.newest === true && this._lastMessage !== message)){
|
||||
this._lastMessage = message;
|
||||
var currTime = new Date(),
|
||||
$data = [element('span', {className: 'message'}, message)];
|
||||
if (this.time === true) {
|
||||
$data.push(element('span', {className: 'time'}, ('0' + currTime.getHours()).slice(-2) + ':' + ('0' + currTime.getMinutes()).slice(-2) + ':' + ('0' + currTime.getSeconds()).slice(-2)));
|
||||
}
|
||||
elchild(this.$log,
|
||||
element('li', {className: type || this.defaultType, tabIndex: '1'}, $data)
|
||||
);
|
||||
if (this.autofocus === true) {
|
||||
this.$log.lastElementChild.focus();
|
||||
}
|
||||
this.trigger('onAdd');
|
||||
}
|
||||
};
|
||||
|
||||
Events.inject(CLog);
|
|
@ -0,0 +1,115 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* Base class for Google Map
|
||||
* @class CMap
|
||||
* @constructor
|
||||
* @author Denys Vasylyev
|
||||
*/
|
||||
function CMap ( parent, parameters ) {
|
||||
/**
|
||||
* Map object
|
||||
* @type {Object}
|
||||
*/
|
||||
this.map = null;
|
||||
|
||||
/**
|
||||
* Map marker object
|
||||
* @type {Object}
|
||||
*/
|
||||
this.marker = null;
|
||||
|
||||
/**
|
||||
* Map container
|
||||
* @type {Node}
|
||||
*/
|
||||
this.container = parameters.container;
|
||||
|
||||
/**
|
||||
* Events object
|
||||
* @type {Object}
|
||||
*/
|
||||
this.events = parameters.events || {};
|
||||
|
||||
/**
|
||||
* Global variable determines whether the map is loaded
|
||||
* @type {boolean}
|
||||
*/
|
||||
window.googleMapsApiScriptLoaded = window.googleMapsApiScriptLoaded || false;
|
||||
|
||||
CBase.call(this, parent || null);
|
||||
|
||||
this.bind(this.events);
|
||||
}
|
||||
|
||||
// extending
|
||||
CMap.prototype = Object.create(CBase.prototype);
|
||||
CMap.prototype.constructor = CMap;
|
||||
|
||||
|
||||
/**
|
||||
* Load Google Maps Api script method with the callback function in url
|
||||
* @param {string} objTitle string with the name of the object in which the callback function will be called
|
||||
* @param {string} language code of the displayed map
|
||||
*/
|
||||
CMap.prototype.LoadGoogleMapsApiScript = function ( objTitle, language ) {
|
||||
loadScript('https://maps.googleapis.com/maps/api/js?callback=' + objTitle + '.GoogleMapsApiScriptCallback&language=' + language);
|
||||
};
|
||||
|
||||
/**
|
||||
* Function call when the script is loaded
|
||||
*/
|
||||
CMap.prototype.GoogleMapsApiScriptCallback = function () {
|
||||
window.googleMapsApiScriptLoaded = true;
|
||||
this.trigger('onGoogleMapsApiScriptCallback');
|
||||
};
|
||||
|
||||
/**
|
||||
* Build google map method
|
||||
* @param {Object} mapOptions object with properties to create or modify map
|
||||
* @param {boolean} useMarker show/hide map marker
|
||||
* @param {boolean} showMapIfExists show/hide map if map object already exists
|
||||
*/
|
||||
CMap.prototype.BuildMap = function ( mapOptions, useMarker, showMapIfExists ) {
|
||||
var self = this;
|
||||
|
||||
if ( this.map ) {
|
||||
this.map.setOptions(mapOptions);
|
||||
if ( showMapIfExists ) {
|
||||
this.Visible(true);
|
||||
}
|
||||
} else {
|
||||
this.map = new google.maps.Map(this.container, mapOptions);
|
||||
}
|
||||
|
||||
if ( useMarker ) {
|
||||
if ( this.marker ) {
|
||||
this.marker.setPosition(mapOptions.center);
|
||||
} else {
|
||||
this.marker = new google.maps.Marker({
|
||||
position: mapOptions.center,
|
||||
map: this.map
|
||||
});
|
||||
}
|
||||
|
||||
this.marker.setVisible(true);
|
||||
} else {
|
||||
if ( this.marker ) {
|
||||
this.marker.setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
google.maps.event.addListenerOnce(this.map, 'tilesloaded', function() {
|
||||
self.trigger('onGoogleMapReady');
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Set visibility mode
|
||||
* @param {boolean} [mode=false] show/hide the component
|
||||
*/
|
||||
CMap.prototype.Visible = function ( mode ) {
|
||||
this.container.style.visibility = Boolean(mode) ? 'visible' : 'hidden';
|
||||
};
|
||||
|
||||
Events.inject(CMap);
|
|
@ -0,0 +1,470 @@
|
|||
/**
|
||||
* Modal windows of different types
|
||||
* CModal
|
||||
* CModalBox
|
||||
* CModalHint
|
||||
* CModalAlert
|
||||
* CModalConfirm
|
||||
* @author Stanislav Kalashnik <sk@infomir.eu>
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
// default icon images path
|
||||
var CMODAL_IMG_PATH = window.configuration && configuration.newRemoteControl ? PATH_IMG_SYSTEM + 'buttons/new/' : PATH_IMG_SYSTEM + 'buttons/old/';
|
||||
|
||||
|
||||
/**
|
||||
* Class for modal windows and messages.
|
||||
* Default use case:
|
||||
* - create
|
||||
* - set title/content/footer
|
||||
* - init (after the DOM is ready)
|
||||
* - show
|
||||
* - hide/destroy
|
||||
* @class CModal
|
||||
* @constructor
|
||||
* @author Stanislav Kalashnik <sk@infomir.eu>
|
||||
*/
|
||||
function CModal ( parent ) {
|
||||
// parent constructor
|
||||
CPage.call(this, parent);
|
||||
|
||||
/**
|
||||
* The component inner name
|
||||
* @type {string}
|
||||
*/
|
||||
this.name = 'CModal';
|
||||
|
||||
/**
|
||||
* CSS class name associated with the component
|
||||
* @type {string}
|
||||
*/
|
||||
this.baseClass = 'cmodal-main';
|
||||
|
||||
/**
|
||||
* CSS "display" attribute value to make the component visible
|
||||
* to hide the default value is "none"
|
||||
* @type {string}
|
||||
*/
|
||||
this.showAttr = 'table';
|
||||
|
||||
this.focusList = [];
|
||||
this.focusPos = 0;
|
||||
}
|
||||
|
||||
// extending
|
||||
CModal.prototype = Object.create(CPage.prototype);
|
||||
CModal.prototype.constructor = CModal;
|
||||
|
||||
|
||||
/**
|
||||
* Prepare html and all placeholders
|
||||
* @param {Node|string} body window content
|
||||
*/
|
||||
CModal.prototype.Init = function ( body ) {
|
||||
// parent call init with placeholder
|
||||
CPage.prototype.Init.call(this,
|
||||
element('div', {className: this.baseClass},
|
||||
element('div', {className:'cmodal-cell'},
|
||||
body
|
||||
)));
|
||||
// get the node to append to
|
||||
var owner = (this.parent && this.parent.handle && this.parent.handle.nodeName ? this.parent.handle : document.body);
|
||||
// get the upper parent if exist in order to prevent nesting
|
||||
if ( this.parent instanceof CModal && this.parent.parent ) {
|
||||
owner = this.parent.parent.handle;
|
||||
}
|
||||
// fill
|
||||
owner.appendChild(this.handle);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Destroy the window and free resources
|
||||
*/
|
||||
CModal.prototype.Free = function () {
|
||||
// global or local clearing
|
||||
if ( this.handle && this.handle.parentNode ) {
|
||||
this.handle.parentNode.removeChild(this.handle);
|
||||
}
|
||||
elclear(this.handle);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Manage the window visibility
|
||||
* also enable/disable parent window event handling
|
||||
* @param {boolean} [visible=true] true - visible; false - hidden
|
||||
* @param {boolean} [manageFocus=true] focus handling mode: true - set/remove focus accordingly, false - manual focus management
|
||||
*/
|
||||
CModal.prototype.Show = function ( visible, manageFocus ) {
|
||||
// parent call
|
||||
CBase.prototype.Show.call(this, visible, manageFocus !== false);
|
||||
|
||||
if ( visible === false ) { // hide
|
||||
window.currCPage = this.parent;
|
||||
} else { // show
|
||||
window.currCPage = this;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Move focus to the previous element from the focusList set
|
||||
*/
|
||||
CModal.prototype.FocusPrev = function ( event, manageVK ) {
|
||||
if ( this.focusList.length > 0 ) {
|
||||
// cycling the index
|
||||
if ( --this.focusPos < 0 ) {
|
||||
this.focusPos = this.focusList.length-1;
|
||||
}
|
||||
// get the next html element in the list
|
||||
var el = this.focusList[this.focusPos];
|
||||
if ( manageVK !== false ) {
|
||||
gSTB.HideVirtualKeyboard();
|
||||
}
|
||||
// set focus
|
||||
el.focus();
|
||||
// skip looping select options elements
|
||||
if ( event && el.tagName === 'INPUT' && el.type === 'text' ) {
|
||||
event.preventDefault();
|
||||
if ( manageVK !== false ) {
|
||||
gSTB.ShowVirtualKeyboard();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Move focus to the next element from the focusList set
|
||||
*/
|
||||
CModal.prototype.FocusNext = function ( event, manageVK ) {
|
||||
if ( this.focusList.length > 0 ) {
|
||||
// cycling the index
|
||||
if ( ++this.focusPos >= this.focusList.length ) {
|
||||
this.focusPos = 0;
|
||||
}
|
||||
// get the next html element in the list
|
||||
var el = this.focusList[this.focusPos];
|
||||
if ( manageVK !== false ) {
|
||||
gSTB.HideVirtualKeyboard();
|
||||
}
|
||||
// set focus
|
||||
el.focus();
|
||||
// skip looping select options elements
|
||||
if ( event && el.tagName === 'INPUT' && el.type === 'text' ) {
|
||||
event.preventDefault();
|
||||
if ( manageVK !== false ) {
|
||||
gSTB.ShowVirtualKeyboard();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
/**
|
||||
* Show small modal info panel which automatically hides in the given time
|
||||
* @class CModalBox
|
||||
* @constructor
|
||||
* @example
|
||||
* var mb = new CModalBox();
|
||||
*/
|
||||
function CModalBox ( parent ) {
|
||||
// parent constructor
|
||||
CModal.call(this, parent);
|
||||
|
||||
/**
|
||||
* The component inner name
|
||||
* @type {string}
|
||||
*/
|
||||
this.name = 'CModalBox';
|
||||
|
||||
/**
|
||||
* html element for window title
|
||||
* @type {Node}
|
||||
*/
|
||||
this.header = element('div', {className:'cmodal-header'});
|
||||
|
||||
/**
|
||||
* html element for window main content
|
||||
* @type {Node}
|
||||
*/
|
||||
this.content = element('div', {className:'cmodal-content'});
|
||||
|
||||
/**
|
||||
* html element for window bottom panel
|
||||
* @type {Node}
|
||||
*/
|
||||
this.footer = element('div', {className:'cmodal-footer'});
|
||||
}
|
||||
|
||||
// extending
|
||||
CModalBox.prototype = Object.create(CModal.prototype);
|
||||
CModalBox.prototype.constructor = CModalBox;
|
||||
|
||||
|
||||
/**
|
||||
* Internal method to update one of the placeholders
|
||||
* makes the inserted node visible
|
||||
* @param {Node|Element|HTMLElement} place placeholder
|
||||
* @param {Node|Element|HTMLElement|Array|string} data some data to set
|
||||
* @return {Node} updated placeholder
|
||||
*/
|
||||
CModalBox.prototype.SetData = function ( place, data ) {
|
||||
// clear
|
||||
elclear(place);
|
||||
// and append
|
||||
if ( data instanceof Node || Array.isArray(data) ) {
|
||||
elchild(place, data);
|
||||
} else {
|
||||
// simple string
|
||||
place.innerHTML = data;
|
||||
}
|
||||
// make sure it visible
|
||||
if ( data && data.nodeName ) {
|
||||
data.style.display = 'block';
|
||||
}
|
||||
// show if there is some data
|
||||
place.style.display = data ? 'block' : 'none';
|
||||
return place;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set window title (alias for SetData)
|
||||
* @param {Node|Array|string} [data] some data to set
|
||||
* @return {Node} updated placeholder
|
||||
*/
|
||||
CModalBox.prototype.SetHeader = function ( data ) { return this.SetData(this.header, data || ''); };
|
||||
|
||||
|
||||
/**
|
||||
* Set window body (alias for SetData)
|
||||
* @param {Node|Array|string} [data] some data to set
|
||||
* @return {Node} updated placeholder
|
||||
*/
|
||||
CModalBox.prototype.SetContent = function ( data ) { return this.SetData(this.content, data || ''); };
|
||||
|
||||
|
||||
/**
|
||||
* Set window footer (alias for SetData)
|
||||
* @param {Node|Array|string} [data] some data to set
|
||||
* @return {Node} updated placeholder
|
||||
*/
|
||||
CModalBox.prototype.SetFooter = function ( data ) { return this.SetData(this.footer, data || ''); };
|
||||
|
||||
|
||||
/**
|
||||
* Prepare html and all placeholders
|
||||
*/
|
||||
CModalBox.prototype.Init = function ( ) {
|
||||
// parent call init with placeholder
|
||||
CModal.prototype.Init.call(this,
|
||||
element('div', {className:'cmodal-body'}, [
|
||||
this.header,
|
||||
this.content,
|
||||
this.footer
|
||||
]
|
||||
));
|
||||
};
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
/**
|
||||
* Show small modal info panel which automatically hides in the given time
|
||||
* @param {CPage|CBase} [parent] object owner (document.body if not set)
|
||||
* @param {string} data info text
|
||||
* @param {Number|boolean} time milliseconds before hiding (not set - manual hide)
|
||||
* @param {boolean} [isForced=false] true do not allow to close this hint till it auto hide
|
||||
* @class CModalHint
|
||||
* @constructor
|
||||
* @example
|
||||
* new CModalHint(CurrentPage, 'some test short info');
|
||||
*/
|
||||
function CModalHint ( parent, data, time, isForced ) {
|
||||
// check input
|
||||
if ( data ) {
|
||||
// parent constructor
|
||||
CModalBox.call(this, parent);
|
||||
|
||||
/**
|
||||
* The component inner name
|
||||
* @type {string}
|
||||
*/
|
||||
this.name = 'CModalHint';
|
||||
|
||||
// for limited scopes
|
||||
var self = this;
|
||||
|
||||
// filling
|
||||
this.SetHeader();
|
||||
this.SetContent(data);
|
||||
this.SetFooter();
|
||||
|
||||
// free resources on hide
|
||||
this.onHide = function(){
|
||||
self.Free();
|
||||
};
|
||||
|
||||
// hide on mouse click
|
||||
this.onClick = function() {
|
||||
self.Show(false);
|
||||
};
|
||||
|
||||
// build and display
|
||||
this.Init();
|
||||
this.Show(true);
|
||||
|
||||
// allow to close by user
|
||||
if ( isForced !== true ) {
|
||||
// forward events to button panel
|
||||
this.EventHandler = function ( event ) {
|
||||
// hide
|
||||
self.Show(false);
|
||||
// reset autohide if set
|
||||
if ( self.timer ) {
|
||||
clearTimeout(self.timer);
|
||||
}
|
||||
event.preventDefault();
|
||||
};
|
||||
}
|
||||
|
||||
if ( time ) {
|
||||
// hide in some time
|
||||
this.timer = setTimeout(function(){
|
||||
self.Show(false);
|
||||
}, time || 5000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// extending
|
||||
CModalHint.prototype = Object.create(CModalBox.prototype);
|
||||
CModalHint.prototype.constructor = CModalHint;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
/**
|
||||
* Show modal message box with single button Exit
|
||||
* @param {CPage|CBase} [parent] object owner (document.body if not set)
|
||||
* @param {string} title modal message box caption
|
||||
* @param {string} data modal message box text
|
||||
* @param {string} btnExitTitle exit button caption
|
||||
* @param {Function} [btnExitClick] callback on exit button click
|
||||
* @class CModalAlert
|
||||
* @constructor
|
||||
* @example
|
||||
* new CModalAlert(CurrentPage, 'Some title', 'Some long or short message text', 'Close', function(){alert('exit')});
|
||||
*/
|
||||
function CModalAlert ( parent, title, data, btnExitTitle, btnExitClick ) {
|
||||
// check input
|
||||
if ( data ) {
|
||||
// parent constructor
|
||||
CModalBox.call(this, parent);
|
||||
|
||||
/**
|
||||
* The component inner name
|
||||
* @type {string}
|
||||
*/
|
||||
this.name = 'CModalAlert';
|
||||
|
||||
// for limited scopes
|
||||
var self = this;
|
||||
|
||||
this.bpanel = new CButtonPanel();
|
||||
this.bpanel.Init(CMODAL_IMG_PATH);
|
||||
this.bpanel.btnExit = this.bpanel.Add(KEYS.EXIT, 'exit.png', btnExitTitle || '', function(){
|
||||
if ( typeof btnExitClick === 'function' ) {
|
||||
btnExitClick.call(self);
|
||||
}
|
||||
// hide and destroy
|
||||
self.Show(false);
|
||||
});
|
||||
|
||||
// filling
|
||||
this.SetHeader(title);
|
||||
this.SetContent(data);
|
||||
this.SetFooter(this.bpanel.handle);
|
||||
|
||||
// free resources on hide
|
||||
this.onHide = function(){
|
||||
elclear(self.bpanel.handle);
|
||||
delete self.bpanel;
|
||||
self.Free();
|
||||
};
|
||||
|
||||
// forward events to button panel
|
||||
this.EventHandler = function ( e ) {
|
||||
if ( !eventPrepare(e, true, this.name) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.bpanel.EventHandler(e);
|
||||
};
|
||||
|
||||
// build and display
|
||||
this.Init();
|
||||
this.Show(true);
|
||||
}
|
||||
}
|
||||
|
||||
// extending
|
||||
CModalAlert.prototype = Object.create(CModalBox.prototype);
|
||||
CModalAlert.prototype.constructor = CModalAlert;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
/**
|
||||
* Show modal message box with single button Exit
|
||||
* @param {CPage|CBase} parent object owner (document.body if not set)
|
||||
* @param {string} title modal message box caption
|
||||
* @param {string} data modal message box text
|
||||
* @param {string} btnExitTitle exit button caption
|
||||
* @param {Function} btnExitClick callback on exit button click
|
||||
* @param {string} btnOKTitle ok button caption
|
||||
* @param {Function} btnOKClick callback on ok button click
|
||||
* @class CModalConfirm
|
||||
* @constructor
|
||||
* @example
|
||||
* new CModalConfirm(CurrentPage, 'Some title', 'Some long or short message text', 'Close', function(){alert('exit')}, 'Ok', function(){alert('f2');});
|
||||
*/
|
||||
function CModalConfirm ( parent, title, data, btnExitTitle, btnExitClick, btnOKTitle, btnOKClick ) {
|
||||
// parent constructor
|
||||
CModalAlert.call(this, parent, title, data, btnExitTitle, btnExitClick);
|
||||
|
||||
/**
|
||||
* The component inner name
|
||||
* @type {string}
|
||||
*/
|
||||
this.name = 'CModalConfirm';
|
||||
|
||||
// for limited scopes
|
||||
var self = this;
|
||||
|
||||
// additional button
|
||||
this.bpanel.Add(KEYS.OK, 'ok.png', btnOKTitle, function(){
|
||||
// hide and destroy
|
||||
self.Show(false);
|
||||
|
||||
if ( typeof btnOKClick === 'function' ) {
|
||||
btnOKClick.call(self);
|
||||
// prevent double invoke
|
||||
btnOKClick = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// extending
|
||||
CModalConfirm.prototype = Object.create(CModalAlert.prototype);
|
||||
CModalConfirm.prototype.constructor = CModalConfirm;
|
|
@ -0,0 +1,106 @@
|
|||
/**
|
||||
* System configuration
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/* jshint unused:false */
|
||||
|
||||
var configuration = (function () {
|
||||
var config = {
|
||||
newRemoteControl: false,
|
||||
allowBluetooth: false,
|
||||
allowMulticastUpdate: false,
|
||||
videoOutputMode: [
|
||||
{value: 'Auto', title: 'Auto', translated: false},
|
||||
{value: 'PAL', title: 'PAL (576i)', translated: true},
|
||||
{value: '576p-50', title: '576p-50', translated: true},
|
||||
{value: '720p-50', title: '720p-50', translated: true},
|
||||
{value: '1080i-50', title: '1080i-50', translated: true},
|
||||
{value: '1080p-50', title: '1080p-50', translated: true},
|
||||
{value: 'NTSC', title: 'NTSC (480i)', translated: true},
|
||||
{value: '720p-60', title: '720p-60', translated: true},
|
||||
{value: '1080i-60', title: '1080i-60', translated: true},
|
||||
{value: '1080p-60', title: '1080p-60', translated: true}
|
||||
]
|
||||
},
|
||||
model = gSTB.GetDeviceModelExt(),
|
||||
control = null;
|
||||
|
||||
switch ( model ) {
|
||||
case 'AuraHD2':
|
||||
case 'AuraHD3':
|
||||
case 'AuraHD8':
|
||||
case 'MAG275':
|
||||
case 'WR320':
|
||||
config.newRemoteControl = true;
|
||||
break;
|
||||
|
||||
// remove 576p-50 video mode
|
||||
// TODO: remove this code when MAG256 fix this video mode
|
||||
case 'MAG256':
|
||||
case 'MAG257':
|
||||
config.videoOutputMode = [
|
||||
{value: 'Auto', title: 'Auto', translated: false},
|
||||
{value: 'PAL', title: 'PAL (576i)', translated: true},
|
||||
{value: '720p-50', title: '720p-50', translated: true},
|
||||
{value: '1080i-50', title: '1080i-50', translated: true},
|
||||
{value: '1080p-50', title: '1080p-50', translated: true},
|
||||
{value: 'NTSC', title: 'NTSC (480i)', translated: true},
|
||||
{value: '720p-60', title: '720p-60', translated: true},
|
||||
{value: '1080i-60', title: '1080i-60', translated: true},
|
||||
{value: '1080p-60', title: '1080p-60', translated: true}
|
||||
];
|
||||
break;
|
||||
|
||||
case 'MAG351':
|
||||
case 'MAG352':
|
||||
case 'MAG420':
|
||||
case 'MAG422':
|
||||
case 'MAG424':
|
||||
case 'MAG425':
|
||||
case 'IM4410WV':
|
||||
case 'IM4411':
|
||||
case 'IM4411V':
|
||||
case 'IM4411WV':
|
||||
case 'IM4412':
|
||||
config.videoOutputMode = [
|
||||
{value: 'Auto', title: 'Auto', translated: false},
|
||||
{value: 'PAL', title: 'PAL (576i)', translated: true},
|
||||
{value: '576p-50', title: '576p-50', translated: true},
|
||||
{value: '720p-50', title: '720p-50', translated: true},
|
||||
{value: '1080i-50', title: '1080i-50', translated: true},
|
||||
{value: '1080p-50', title: '1080p-50', translated: true},
|
||||
{value: 'NTSC', title: 'NTSC (480i)', translated: true},
|
||||
{value: '720p-60', title: '720p-60', translated: true},
|
||||
{value: '1080i-60', title: '1080i-60', translated: true},
|
||||
{value: '1080p-60', title: '1080p-60', translated: true},
|
||||
{value: '3840x2160p50', title: '3840x2160p-50', translated: true},
|
||||
{value: '3840x2160p60', title: '3840x2160p-60', translated: true}
|
||||
];
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// enable bluetooth only for these devices
|
||||
if ( ['MAG424', 'IM4410', 'IM4410WV', 'IM4412'].indexOf(model) > -1 ) {
|
||||
config.allowBluetooth = true;
|
||||
}
|
||||
|
||||
try{
|
||||
control = JSON.parse(gSTB.GetEnv('{"varList":["controlModel"]}'));
|
||||
if ( !control.errMsg && control.result && control.result.controlModel ) {
|
||||
if ( control.result.controlModel === 'SRC4513' ) {
|
||||
config.newRemoteControl = true;
|
||||
} else {
|
||||
config.newRemoteControl = false;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
echo(e ,'controlModel parse')
|
||||
}
|
||||
|
||||
return config;
|
||||
})();
|
|
@ -0,0 +1,98 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* Class for main pages of the portal.
|
||||
* Each page should be created from this class.
|
||||
* @class CPage
|
||||
* @constructor
|
||||
* @author Stanislav Kalashnik <sk@infomir.eu>
|
||||
*/
|
||||
function CPage ( parent ) {
|
||||
// parent constructor
|
||||
CBase.call(this, parent || null);
|
||||
|
||||
/**
|
||||
* The component inner name
|
||||
* @type {string}
|
||||
*/
|
||||
this.name = 'CPage';
|
||||
|
||||
/**
|
||||
* the object that will become visible after this one is hidden
|
||||
* @type {CPage|CBase}
|
||||
*/
|
||||
this.previous = null;
|
||||
|
||||
/**
|
||||
* default visibility state
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.isVisible = false;
|
||||
|
||||
}
|
||||
|
||||
// extending
|
||||
CPage.prototype = Object.create(CBase.prototype);
|
||||
CPage.prototype.constructor = CPage;
|
||||
|
||||
|
||||
/**
|
||||
* Manage the page visibility
|
||||
* @param {boolean} [visible=true] component visibility: true - visible, false - hidden
|
||||
* @param {CPage|CBase} [previous=null] the page to return to on this page hiding
|
||||
*/
|
||||
CPage.prototype.Show = function ( visible, previous ) {
|
||||
// custom action
|
||||
if ( visible === false ) { // hide
|
||||
// turn off events
|
||||
if ( this.activeChild ) {
|
||||
this.activeChild.Activate(false);
|
||||
}
|
||||
// hide this
|
||||
CBase.prototype.Show.call(this, false, true, true);
|
||||
|
||||
// go to home if not set
|
||||
window.currCPage = this.previous || window.baseCPage;
|
||||
this.previous = null;
|
||||
|
||||
// if set and not itself
|
||||
if ( window.currCPage && window.currCPage !== this ) {
|
||||
// show it
|
||||
CBase.prototype.Show.call(window.currCPage, true, true, true);
|
||||
}
|
||||
} else { // show
|
||||
// if set and not itself
|
||||
this.previous = previous || null;
|
||||
|
||||
if ( window.currCPage && window.currCPage !== this ) {
|
||||
if ( window.currCPage.activeChild ) {
|
||||
window.currCPage.activeChild.Activate(false);
|
||||
}
|
||||
// hide it
|
||||
CBase.prototype.Show.call(window.currCPage, false, true, true);
|
||||
}
|
||||
|
||||
// show this
|
||||
CBase.prototype.Show.call(this, true, true, true);
|
||||
|
||||
// set back route
|
||||
|
||||
window.currCPage = this;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Can handle a mouse click
|
||||
*/
|
||||
CPage.prototype.onClick = function () {
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Should be overwritten in each instance
|
||||
*/
|
||||
CPage.prototype.EventHandler = function () {
|
||||
// ...
|
||||
};
|
|
@ -0,0 +1,31 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* Progressbar component
|
||||
* @class CProgressBar
|
||||
* @param {CBase} parent
|
||||
* @param {HTMLElement} parentNode Элемент в который будет вставлен компонент
|
||||
* @constructor
|
||||
*/
|
||||
var CProgressBar = function (parent, parentNode) {
|
||||
CBase.call(this, parent || null);
|
||||
if (parentNode === undefined) {
|
||||
parentNode = element('div');
|
||||
}
|
||||
elchild(parentNode, this.$bar = element('div', {className: 'cprogressbar'},
|
||||
element('div', {className: 'progressbar_bg'}, [
|
||||
this.$line = element('div', {className: 'progressbar_line'}),
|
||||
this.$digit = element('div', {className: 'progressbar_digit'}, '0 %')
|
||||
])
|
||||
));
|
||||
this.Init(parentNode);
|
||||
};
|
||||
|
||||
// extending
|
||||
CProgressBar.prototype = Object.create(CBase.prototype);
|
||||
CProgressBar.prototype.constructor = CProgressBar;
|
||||
|
||||
CProgressBar.prototype.SetProgress = function (percent) {
|
||||
this.$line.style.width = percent + '%';
|
||||
this.$digit.innerHTML = percent + '%';
|
||||
};
|
|
@ -0,0 +1,263 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* Search bar module
|
||||
* contains a text input and icon/button
|
||||
* @param {Node} parent component
|
||||
* @class CSearchBar
|
||||
* @constructor
|
||||
* @author Stanislav Kalashnik <sk@infomir.eu>
|
||||
*/
|
||||
function CSearchBar ( parent ) {
|
||||
// parent constructor
|
||||
CBase.call(this, parent);
|
||||
|
||||
/**
|
||||
* The component inner name
|
||||
* @type {string}
|
||||
*/
|
||||
this.name = 'CSearchBar';
|
||||
|
||||
/**
|
||||
* CSS class name associated with the component
|
||||
* it is checking on initialization in the placeholder
|
||||
* if not found a wrapper is created
|
||||
* @type {string}
|
||||
*/
|
||||
this.baseClass = 'csbar-main';
|
||||
|
||||
/**
|
||||
* text data for filtering
|
||||
* @type {string}
|
||||
*/
|
||||
this.filter = '';
|
||||
|
||||
/**
|
||||
* directory with icons
|
||||
* depends on screen resolution
|
||||
* @type {string}
|
||||
*/
|
||||
this.path = '';
|
||||
|
||||
/**
|
||||
* list of inner nodes
|
||||
* @type {Object}
|
||||
*/
|
||||
this.items = {};
|
||||
|
||||
/**
|
||||
* numerical ID of the timeout
|
||||
* @type {number}
|
||||
*/
|
||||
this.timer = 0;
|
||||
|
||||
/**
|
||||
* grey hint text line
|
||||
* @type {string}
|
||||
*/
|
||||
this.hint = 0;
|
||||
|
||||
/**
|
||||
* default initial image
|
||||
* @type {string}
|
||||
*/
|
||||
this.icoSearch = 'ico_search2.png';
|
||||
|
||||
/**
|
||||
* image with not empty search
|
||||
* @type {string}
|
||||
*/
|
||||
this.icoFilter = 'ico_filter.png';
|
||||
}
|
||||
|
||||
// extending
|
||||
CSearchBar.prototype = Object.create(CBase.prototype);
|
||||
CSearchBar.prototype.constructor = CSearchBar;
|
||||
|
||||
|
||||
/**
|
||||
* Component initialization with image path set.
|
||||
* Should be called once before use just after constructor invoke and all hooks preparation.
|
||||
* @param {string} path image path dependant on resolution
|
||||
* @param {Node} [handle] component placeholder
|
||||
* @param {string} [hint] default hint text
|
||||
*/
|
||||
CSearchBar.prototype.Init = function ( path, handle, hint ) {
|
||||
// global vars
|
||||
this.path = path;
|
||||
this.hint = hint || '';
|
||||
|
||||
var self = this;
|
||||
var func = function () {
|
||||
self.Activate(!self.isActive);
|
||||
event.stopPropagation();
|
||||
};
|
||||
|
||||
// parent call init with placeholder
|
||||
CBase.prototype.Init.call(this, handle);
|
||||
|
||||
this.items.hint = element('input', {type: 'text', className: 'hint maxw', value:this.hint});
|
||||
this.items.text = element('input', {type: 'text', className: 'text maxw', oninput:function(){if ( self.onChange ) {self.onChange(this.value);} }, onkeydown:function(event){ self.EventHandler(event); }});
|
||||
this.items.span = element('div', {className: 'csbar-item span'});
|
||||
this.items.icon = element('div', {className: 'csbar-item icon', onclick: func}, element('img', {className: '', src: this.path + this.icoSearch}));
|
||||
this.items.button = element('div', {className: 'csbar-item icon', onclick: func}, element('img', {className: '', src: this.path + 'f2.png'}));
|
||||
|
||||
// add to component container
|
||||
this.items.input = element('div', {className: 'csbar-item stext'}, [this.items.hint, this.items.text]);
|
||||
elchild(this.handleInner, [this.items.input, this.items.span, this.items.icon, this.items.button]);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Manage the component search input field visibility
|
||||
* @param {boolean} [visible=true] component visibility: true - visible, false - hidden
|
||||
* @param {boolean} [manageFocus=true] flag to manage focus handling
|
||||
*/
|
||||
CSearchBar.prototype.ShowInput = function ( visible, manageFocus ) {
|
||||
visible = visible !== false;
|
||||
|
||||
if ( !visible ) {
|
||||
this.items.text.blur();
|
||||
gSTB.HideVirtualKeyboard();
|
||||
// return focus to the previous element if necessary and set
|
||||
if ( manageFocus !== false && this.prevFocus ) {
|
||||
this.prevFocus.focus();
|
||||
}
|
||||
}
|
||||
|
||||
this.items.input.style.display = visible ? 'table-cell' : 'none';
|
||||
this.items.span.style.display = visible ? 'none' : 'table-cell';
|
||||
|
||||
if ( visible ) {
|
||||
// save the previous focused element
|
||||
this.prevFocus = document.activeElement;
|
||||
// set focus to input
|
||||
this.items.text.focus();
|
||||
gSTB.ShowVirtualKeyboard();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Apply default parameters
|
||||
*
|
||||
*/
|
||||
CSearchBar.prototype.Reset = function () {
|
||||
this.Activate(false);
|
||||
this.items.text.value = this.items.hint.value = this.hint = this.filter = '';
|
||||
this.items.icon.lastChild.src = this.path + '/' + this.icoSearch;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Search callback
|
||||
* @param {string} value search value
|
||||
*/
|
||||
CSearchBar.prototype.onSearch = null;
|
||||
|
||||
|
||||
/**
|
||||
* Get hint if search value was changed
|
||||
* @param {string} value search value
|
||||
*/
|
||||
CSearchBar.prototype.onHint = null;
|
||||
|
||||
|
||||
/**
|
||||
* Event on search text change
|
||||
* @param {string} value search value
|
||||
*/
|
||||
CSearchBar.prototype.onChange = null;
|
||||
|
||||
|
||||
/**
|
||||
* Apply the hint value to the input element
|
||||
* @param {string} hint search suggest text
|
||||
*/
|
||||
CSearchBar.prototype.SetHint = function ( hint ) {
|
||||
hint = hint || this.items.text.value || this.hint;
|
||||
// apply if there is a hint
|
||||
if ( hint ) {
|
||||
this.items.hint.value = hint;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Apply the given value to the input element
|
||||
* @param {string} text search text
|
||||
*/
|
||||
CSearchBar.prototype.SetValue = function ( text ) {
|
||||
this.filter = text;
|
||||
// apply
|
||||
this.items.text.value = this.filter;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handle external events
|
||||
* @param {Event} event global event object
|
||||
*/
|
||||
CSearchBar.prototype.EventHandler = function ( event ) {
|
||||
switch ( event.code ) {
|
||||
case KEYS.LEFT:
|
||||
case KEYS.RIGHT:
|
||||
case KEYS.UP:
|
||||
case KEYS.DOWN:
|
||||
return;
|
||||
case KEYS.EXIT:
|
||||
this.Activate(false);
|
||||
event.stopPropagation();
|
||||
break;
|
||||
case KEYS.F2:
|
||||
this.Activate(!this.isActive);
|
||||
event.stopPropagation();
|
||||
break;
|
||||
case KEYS.OK:
|
||||
this.Activate(false);
|
||||
// hook
|
||||
if ( typeof this.onSearch === 'function' ) {
|
||||
this.onSearch(this.items.text.value);
|
||||
}
|
||||
// set icon
|
||||
this.items.icon.firstChild.src = this.items.text.value ? this.path + '/' + this.icoFilter : this.path + '/' + this.icoSearch;
|
||||
event.stopPropagation();
|
||||
break;
|
||||
default:
|
||||
// hide or show default hint state
|
||||
this.items.hint.value = this.items.text.value === '' ? this.hint : '';
|
||||
// hint callback set
|
||||
if ( typeof this.onHint === 'function' ) {
|
||||
if ( this.items.hint.value === this.hint ) {
|
||||
this.SetHint(' ');
|
||||
}
|
||||
if ( this.timer ) {
|
||||
clearTimeout(this.timer);
|
||||
}
|
||||
var self = this;
|
||||
this.timer = setTimeout(function(){
|
||||
self.onHint.call(self, self.items.text.value);
|
||||
}, 300);
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set active state of the component
|
||||
* @param {boolean} active state
|
||||
* @param {boolean} [manageFocus=true] flag to manage focus handling
|
||||
*/
|
||||
CSearchBar.prototype.Activate = function ( active, manageFocus ) {
|
||||
if ( !active ) {
|
||||
this.ShowInput(false, manageFocus);
|
||||
}
|
||||
|
||||
// parent call init with placeholder
|
||||
CBase.prototype.Activate.call(this, active);
|
||||
|
||||
if ( active ) {
|
||||
this.ShowInput(true, manageFocus);
|
||||
}
|
||||
};
|
|
@ -0,0 +1,757 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* @param parent
|
||||
* @param parameters
|
||||
* @constructor
|
||||
* @abstract
|
||||
*/
|
||||
function CSimpleSelect ( parent, parameters ) {
|
||||
this.parameters = parameters || {};
|
||||
this.tagName = 'SELECT';
|
||||
/**
|
||||
* Default values for element initialization
|
||||
* @type {Object}
|
||||
*/
|
||||
this.attributes = {
|
||||
content: {
|
||||
className: 'csimple-select-content'
|
||||
},
|
||||
left_arrow: {
|
||||
className: 'csimple-select-arrow left'
|
||||
},
|
||||
right_arrow: {
|
||||
className: 'csimple-select-arrow right'
|
||||
},
|
||||
wrapper: {
|
||||
className: 'csimple-select-wrapper',
|
||||
tabIndex: '1'
|
||||
},
|
||||
content_wrapper: {
|
||||
className: 'csimple-content-wrapper'
|
||||
},
|
||||
hint: {
|
||||
className: 'csimple-select-hint csimple-select-content'
|
||||
}
|
||||
};
|
||||
|
||||
this.style = parameters.style || '';
|
||||
|
||||
this.type = 'select';
|
||||
|
||||
if ( parameters.attributes !== undefined ) {
|
||||
extend(this.attributes, parameters.attributes);
|
||||
}
|
||||
|
||||
this.bind(parameters.events || {});
|
||||
|
||||
CBase.call(this, parent || null);
|
||||
|
||||
if ( !(this.parentNode = parameters.parent) ) {
|
||||
if ( parameters.content === undefined ) {
|
||||
this.parentNode = element('div');
|
||||
} else {
|
||||
this.parentNode = parameters.content.parentNode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// extending
|
||||
CSimpleSelect.prototype = Object.create(CBase.prototype);
|
||||
CSimpleSelect.prototype.constructor = CSimpleSelect;
|
||||
|
||||
CSimpleSelect.prototype.GenerateHandle = function () {
|
||||
var self = this,
|
||||
wrapper,
|
||||
content_wrapper;
|
||||
|
||||
if ( this.parameters.content !== undefined && this.parameters.content.tagName === 'DIV' ) {
|
||||
this.content = this.parameters.content;
|
||||
this.content.parentNode.removeChild(this.content);
|
||||
if ( this.content.className !== undefined )
|
||||
this.attributes.content.className += ' ' + this.content.className;
|
||||
this.content = element('div', this.attributes.content, this.content || '');
|
||||
} else {
|
||||
if ( this.parameters.content !== undefined && this.parameters.content.tagName !== 'DIV' )
|
||||
this.content = this.parameters.content;
|
||||
this.content = element('div', this.attributes.content, this.content || '');
|
||||
}
|
||||
|
||||
content_wrapper = element('div', this.attributes.content_wrapper, [this.content, this.hint = element('div', this.attributes.hint)]);
|
||||
|
||||
if ( this.parameters.leftArrow ) {
|
||||
this.leftArrow = this.parameters.leftArrow;
|
||||
this.leftArrow.parentNode.removeChild(this.leftArrow);
|
||||
this.leftArrow = element('div', this.attributes.left_arrow, this.leftArrow);
|
||||
} else {
|
||||
this.leftArrow = element('div', this.attributes.left_arrow, this.leftArrow || element('p'));
|
||||
}
|
||||
|
||||
if ( this.parameters.rightArrow ) {
|
||||
this.rightArrow = this.parameters.rightArrow;
|
||||
this.rightArrow.parentNode.removeChild(this.rightArrow);
|
||||
this.rightArrow = element('div', this.attributes.right_arrow, this.rightArrow);
|
||||
} else {
|
||||
this.rightArrow = element('div', this.attributes.right_arrow, this.rightArrow || element('p'));
|
||||
}
|
||||
|
||||
wrapper = element('div', this.attributes.wrapper, element('div', {
|
||||
className: 'content-wrapper',
|
||||
onfocus: function () {
|
||||
}
|
||||
}, [this.leftArrow, content_wrapper, this.rightArrow]));
|
||||
|
||||
wrapper.className += ' ' + this.style;
|
||||
|
||||
if ( this.parameters.width !== undefined ) {
|
||||
wrapper.style.width = this.parameters.width;
|
||||
}
|
||||
|
||||
this.leftArrow.onclick = function ( event ) {
|
||||
if ( self.disabled ) return;
|
||||
self.Previous();
|
||||
event.stopPropagation();
|
||||
event.preventDefault()
|
||||
};
|
||||
|
||||
this.rightArrow.onclick = function ( event ) {
|
||||
if ( self.disabled ) return;
|
||||
self.Next();
|
||||
event.stopPropagation();
|
||||
event.preventDefault()
|
||||
};
|
||||
|
||||
return wrapper;
|
||||
};
|
||||
|
||||
CSimpleSelect.prototype.onInit = function () {
|
||||
var self = this;
|
||||
elchild(this.parentNode, this.handle);
|
||||
this.handle.component = this;
|
||||
this.handle.onblur = function () {
|
||||
self.trigger('onBlur');
|
||||
};
|
||||
this.handle.onfocus = function () {
|
||||
self.trigger('onFocus');
|
||||
}
|
||||
};
|
||||
|
||||
CSimpleSelect.prototype.EventHandler = function ( event ) {
|
||||
eventPrepare(event, true, 'CSimpleSelect');
|
||||
if ( this.disabled === true )
|
||||
return;
|
||||
event.stopped = true;
|
||||
switch ( event.code ) {
|
||||
case KEYS.LEFT:
|
||||
this.Previous();
|
||||
break;
|
||||
case KEYS.RIGHT:
|
||||
this.Next();
|
||||
break;
|
||||
default :
|
||||
event.stopped = false;
|
||||
}
|
||||
};
|
||||
|
||||
CSimpleSelect.prototype.SetContent = function ( content, placeholder ) {
|
||||
if ( content === undefined && placeholder ) {
|
||||
content = placeholder;
|
||||
this.content.classList.add('placeholder');
|
||||
}
|
||||
|
||||
this.content.innerHTML = '';
|
||||
this.content.innerHTML = content;
|
||||
};
|
||||
|
||||
CSimpleSelect.prototype.DisableElement = function ( element, disable ) {
|
||||
if ( disable === true ) {
|
||||
if ( element.className.indexOf('disabled') === -1 ) element.className += ' disabled';
|
||||
} else {
|
||||
element.className = element.className.replace(' disabled', '');
|
||||
}
|
||||
};
|
||||
|
||||
CSimpleSelect.prototype.SetHint = function ( hint ) {
|
||||
this.hint.innerHTML = hint;
|
||||
};
|
||||
|
||||
CSimpleSelect.prototype.Next = function () {
|
||||
};
|
||||
|
||||
CSimpleSelect.prototype.Previous = function () {
|
||||
this.autocomplete = '';
|
||||
};
|
||||
|
||||
CSimpleSelect.prototype.focus = function () {
|
||||
this.handle.focus();
|
||||
};
|
||||
|
||||
|
||||
CSimpleSelect.prototype.blur = function () {
|
||||
this.handle.blur();
|
||||
};
|
||||
|
||||
CSimpleSelect.prototype.IsFocused = function () {
|
||||
return document.activeElement === this.handle;
|
||||
};
|
||||
|
||||
CSimpleSelect.prototype.Disable = function ( disable ) {
|
||||
this.DisableElement(this.handle, (this.disabled = disable === true));
|
||||
};
|
||||
|
||||
Events.inject(CSimpleSelect);
|
||||
|
||||
/**
|
||||
*
|
||||
* @param parent
|
||||
* @param parameters
|
||||
* @constructor
|
||||
*/
|
||||
function CSelectBox ( parent, parameters ) {
|
||||
CSimpleSelect.call(this, parent || null, parameters);
|
||||
|
||||
this.attributes.wrapper.className += ' cselect-box';
|
||||
|
||||
this.Init(this.GenerateHandle());
|
||||
|
||||
var _data = parameters.data || [],
|
||||
_selectedIndex,
|
||||
_default,
|
||||
idField = parameters.idField || 'id',
|
||||
nameField = parameters.nameField || 'name',
|
||||
_autocomplete = '',
|
||||
self = this,
|
||||
placeholder = parameters.placeholder || _('No data');
|
||||
|
||||
this.name = parameters.name || 'CSelectBox';
|
||||
|
||||
this.dropdown = new CSelectDropdown(this, {
|
||||
select: this,
|
||||
scrollMode: 2,
|
||||
container: parameters.container
|
||||
});
|
||||
|
||||
this.SetData = function ( data ) {
|
||||
if ( data.length === 0 )
|
||||
throw 'data cannot be empty';
|
||||
_data = data || [];
|
||||
if ( data.length - 1 < _selectedIndex )
|
||||
_selectedIndex = data.length - 1;
|
||||
self.RefreshContent();
|
||||
self.trigger('dataChanged');
|
||||
};
|
||||
|
||||
this.GetNameField = function () {
|
||||
return nameField;
|
||||
};
|
||||
|
||||
this.GetIdField = function () {
|
||||
return idField;
|
||||
};
|
||||
|
||||
this.GetData = function () {
|
||||
return _data;
|
||||
};
|
||||
|
||||
this.Autocomplete = function ( keyCode ) {
|
||||
var find = false, text = '';
|
||||
if ( keyCode === KEYS.BACK ) {
|
||||
text = _autocomplete.slice(0, -1);
|
||||
} else {
|
||||
text = _autocomplete + String.fromCharCode(keyCode).toLowerCase();
|
||||
}
|
||||
if ( text !== '' ) {
|
||||
for ( var i = 0, el; i < _data.length, el = _data[i]; i++ ) {
|
||||
if ( typeof el === 'object' ) {
|
||||
el = el[nameField];
|
||||
}
|
||||
if ( el.toLowerCase().indexOf(text) === 0 ) {
|
||||
this.SetIndex(i);
|
||||
find = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( find === true ) {
|
||||
self.handle.classList.add('autocomplete');
|
||||
_autocomplete = text;
|
||||
}
|
||||
|
||||
text = this.GetLabel();
|
||||
|
||||
if ( text ) {
|
||||
text = text.substr(0, _autocomplete.length);
|
||||
}
|
||||
|
||||
self.SetHint(text);
|
||||
} else {
|
||||
self.ClearAutocomplete();
|
||||
}
|
||||
};
|
||||
|
||||
this.ClearAutocomplete = function () {
|
||||
self.handle.classList.remove('autocomplete');
|
||||
self.SetHint(_autocomplete = '');
|
||||
};
|
||||
|
||||
this.GetSelected = function () {
|
||||
return _data[_selectedIndex];
|
||||
};
|
||||
|
||||
this.Next = function () {
|
||||
if ( _selectedIndex < _data.length - 1 ) {
|
||||
this.SetIndex(++_selectedIndex);
|
||||
}
|
||||
};
|
||||
|
||||
this.Previous = function () {
|
||||
if ( _selectedIndex !== 0 ) {
|
||||
this.SetIndex(--_selectedIndex);
|
||||
}
|
||||
};
|
||||
|
||||
this.GetValue = function () {
|
||||
var selected = self.GetSelected();
|
||||
return typeof selected === 'object' ? selected[idField] : selected;
|
||||
};
|
||||
|
||||
this.GetLabel = function () {
|
||||
var selected = self.GetSelected();
|
||||
return typeof selected === 'object' ? selected[nameField] : selected;
|
||||
};
|
||||
|
||||
this.RefreshContent = function () {
|
||||
self.SetContent(self.GetLabel(), placeholder);
|
||||
self.value = self.GetValue(); // remove after system settings change
|
||||
if ( self.GetData().length === 1 ) {
|
||||
self.DisableElement(self.leftArrow, true);
|
||||
self.DisableElement(self.rightArrow, true);
|
||||
return;
|
||||
}
|
||||
if ( self.GetIndex() === 0 ) {
|
||||
self.DisableElement(self.leftArrow, true);
|
||||
self.DisableElement(self.rightArrow, false);
|
||||
} else if ( self.GetIndex() === self.GetData().length - 1 ) {
|
||||
self.DisableElement(self.rightArrow, true);
|
||||
self.DisableElement(self.leftArrow, false);
|
||||
} else {
|
||||
self.DisableElement(self.leftArrow, false);
|
||||
self.DisableElement(self.rightArrow, false);
|
||||
}
|
||||
};
|
||||
|
||||
this.SelectById = function ( id, quiet ) {
|
||||
for ( var i = 0; i < _data.length; i++ ) {
|
||||
if ( _data[i][idField] === id ) {
|
||||
self.SetIndex(i, quiet);
|
||||
return i;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.IsChanged = function () {
|
||||
return _selectedIndex !== _default;
|
||||
};
|
||||
|
||||
this.GetIndex = function () {
|
||||
return _selectedIndex;
|
||||
};
|
||||
|
||||
this.SetIndex = function ( index, quiet ) {
|
||||
var old = this.GetSelected();
|
||||
self.ClearAutocomplete();
|
||||
if ( index === undefined || index >= _data.length || index < 0 )
|
||||
_selectedIndex = 0;
|
||||
else
|
||||
_selectedIndex = index;
|
||||
self.RefreshContent();
|
||||
if ( quiet !== true )
|
||||
self.trigger('onChange', {'new': self.GetSelected(), old: old});
|
||||
};
|
||||
|
||||
this.SetIndex(parameters.selected, true);
|
||||
|
||||
if ( parameters.selectedId !== undefined )
|
||||
this.SelectById(parameters.selectedId, true);
|
||||
|
||||
_default = _selectedIndex;
|
||||
|
||||
this.EventHandler = function ( event ) {
|
||||
eventPrepare(event);
|
||||
event.stopped = true;
|
||||
switch ( event.code ) {
|
||||
case KEYS.FRAME:
|
||||
this.dropdown.Show(!this.dropdown.isVisible, false);
|
||||
break;
|
||||
case KEYS.LEFT:
|
||||
case KEYS.RIGHT:
|
||||
if ( !this.dropdown.isVisible ) {
|
||||
CSimpleSelect.prototype.EventHandler.call(this, event);
|
||||
this.dropdown.EventHandler(event);
|
||||
}
|
||||
break;
|
||||
case KEYS.OK:
|
||||
case KEYS.UP:
|
||||
case KEYS.DOWN:
|
||||
case KEYS.PAGE_UP:
|
||||
case KEYS.PAGE_DOWN:
|
||||
event.stopped = false;
|
||||
if ( this.dropdown.isVisible ) {
|
||||
this.dropdown.EventHandler(event, null, false);
|
||||
event.stopped = true;
|
||||
}
|
||||
break;
|
||||
case KEYS.EXIT:
|
||||
if ( this.dropdown.isVisible ) {
|
||||
this.dropdown.Show(false, false);
|
||||
} else {
|
||||
event.stopped = false;
|
||||
}
|
||||
break;
|
||||
default :
|
||||
event.stopped = false;
|
||||
}
|
||||
};
|
||||
|
||||
this.handle.onkeypress = function ( event ) {
|
||||
if ( event.ctrlKey !== true && event.altKey !== true )
|
||||
self.Autocomplete(event.charCode);
|
||||
};
|
||||
|
||||
this.dropdown.Refresh();
|
||||
|
||||
this.bind('onBlur', function () {
|
||||
self.ClearAutocomplete();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// extending
|
||||
CSelectBox.prototype = Object.create(CSimpleSelect.prototype);
|
||||
CSelectBox.prototype.constructor = CSelectBox;
|
||||
|
||||
function CSelectDropdown ( parent, options ) {
|
||||
CScrollList.call(this, parent || null, options);
|
||||
var self = this;
|
||||
|
||||
this.isVisible = false;
|
||||
this.manageFocus = false;
|
||||
|
||||
this.multipleSelection = false;
|
||||
|
||||
this.attributes = {
|
||||
wrapper: {
|
||||
className: 'csimple-select-dropdown'
|
||||
},
|
||||
element: {
|
||||
className: 'dropdown-element'
|
||||
}
|
||||
};
|
||||
this.select = null;
|
||||
|
||||
this.baseClass = 'csimple-dropdown-main';
|
||||
|
||||
CSelectDropdown.parameters.forEach(function ( key ) {
|
||||
if ( options[key] !== undefined )
|
||||
self[key] = options[key];
|
||||
});
|
||||
|
||||
this.base = this.select.handle;
|
||||
|
||||
this.Init(element('div', this.attributes.wrapper));
|
||||
|
||||
this.base.parentNode.appendChild(this.handle);
|
||||
|
||||
this.select.bind('dataChanged', function () {
|
||||
self.Refresh();
|
||||
});
|
||||
|
||||
this.select.bind('onChange', function () {
|
||||
if ( self.isVisible ) {
|
||||
var item = self.handleInner.children[this.GetIndex()];
|
||||
self.SetPosition(item, true, false);
|
||||
self.Focused(item, true);
|
||||
self.Marked(item, true);
|
||||
}
|
||||
});
|
||||
|
||||
this.select.bind('onBlur', function () {
|
||||
setTimeout(function () {
|
||||
self.Show(false, self.manageFocus);
|
||||
}, 100);
|
||||
});
|
||||
|
||||
this.select.handle.addEventListener('click', function ( event ) {
|
||||
self.Show(!self.isVisible, self.manageFocus);
|
||||
event.stopPropagation();
|
||||
event.stopImmediatePropagation();
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
this.Show(false, self.manageFocus);
|
||||
}
|
||||
|
||||
// extending
|
||||
CSelectDropdown.prototype = Object.create(CScrollList.prototype);
|
||||
CSelectDropdown.prototype.constructor = CSelectDropdown;
|
||||
|
||||
CSelectDropdown.parameters = ['scrollMode', 'select', 'container'];
|
||||
|
||||
CSelectDropdown.prototype.Refresh = function () {
|
||||
var self = this,
|
||||
data = this.select.GetData(), el, i;
|
||||
this.Clear();
|
||||
for ( i = 0; i < data.length, el = data[i]; i++ ) {
|
||||
if ( typeof el === 'object' ) {
|
||||
el = el[this.select.GetNameField()];
|
||||
}
|
||||
this.Add(element('div', extend(this.attributes.element, {innerHTML: el})), {
|
||||
onclick: function () {
|
||||
self.select.SetIndex(self.activeItem.index);
|
||||
self.Marked(self.activeItem, true);
|
||||
self.Show(false, self.manageFocus);
|
||||
},
|
||||
index: i
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
CSelectDropdown.prototype.onShow = function () {
|
||||
this.UpdateGeometry();
|
||||
var item = this.handleInner.children[this.select.GetIndex()];
|
||||
this.SetPosition(item, true, false);
|
||||
this.Focused(item, true);
|
||||
this.Marked(item, true);
|
||||
};
|
||||
|
||||
CSelectDropdown.prototype.UpdateGeometry = function () {
|
||||
var inputRect = this.base.getBoundingClientRect(),
|
||||
top = (this.base.offsetTop + inputRect.height),
|
||||
height = WINDOW_HEIGHT, containerRect;
|
||||
if ( this.container !== undefined ) {
|
||||
containerRect = this.container.getBoundingClientRect();
|
||||
height = containerRect.top + containerRect.height;
|
||||
}
|
||||
|
||||
if ( inputRect.top + inputRect.height + this.itemHeight * this.itemsPerPage > height ) {
|
||||
top = this.base.offsetTop - this.itemHeight * this.itemsPerPage;
|
||||
}
|
||||
if ( this.scrollMode === 1 )
|
||||
top -= this.itemHeight * Math.abs(Math.floor(this.itemsPerPage / 2 * -1));
|
||||
this.handle.style.top = top + 'px';
|
||||
this.handle.style.left = this.base.offsetLeft + 'px';
|
||||
this.handle.style.width = (inputRect.width - parseInt(document.defaultView.getComputedStyle(this.handle, '')['border-left-width']) * 2) + 'px';
|
||||
};
|
||||
|
||||
Events.inject(CSelectDropdown);
|
||||
|
||||
/**
|
||||
*
|
||||
* @param parent
|
||||
* @param parameters
|
||||
* @constructor
|
||||
*/
|
||||
function CIntervalBox ( parent, parameters ) {
|
||||
var self = this,
|
||||
_value = 0, _default,
|
||||
_inputValue = '', _oldValue;
|
||||
CSimpleSelect.call(this, parent || null, parameters);
|
||||
|
||||
this.inputTimer = {};
|
||||
|
||||
this.attributes.wrapper.className += ' cinterval-box';
|
||||
|
||||
if ( parameters.align === undefined )
|
||||
this.attributes.content.className += ' center';
|
||||
if ( parameters.contentStyle !== undefined ) {
|
||||
this.attributes.content.className += ' ' + parameters.contentStyle;
|
||||
}
|
||||
this.Init(this.GenerateHandle());
|
||||
|
||||
this.name = parameters.name || 'input';
|
||||
|
||||
this.type = 'interval';
|
||||
|
||||
this.parameters = parameters;
|
||||
if ( parameters.align !== undefined ) {
|
||||
this.content.className += ' ' + parameters.align;
|
||||
this.content.style.textAlign = parameters.align;
|
||||
}
|
||||
this.max = parameters.max;
|
||||
this.min = parameters.min;
|
||||
this.interval = parameters.interval || 1;
|
||||
|
||||
if ( parameters.value !== undefined ) {
|
||||
if ( this.max !== undefined && parameters.value > this.max ) {
|
||||
_value = this.max;
|
||||
} else if ( this.min !== undefined && parameters.value < this.min ) {
|
||||
_value = this.min;
|
||||
} else {
|
||||
_value = parameters.value;
|
||||
}
|
||||
} else {
|
||||
if ( this.min !== undefined ) {
|
||||
_value = this.min;
|
||||
} else if ( this.max !== undefined ) {
|
||||
_value = this.max;
|
||||
}
|
||||
}
|
||||
|
||||
_default = _value = parseInt(_value);
|
||||
|
||||
this.SetValue = function ( value ) {
|
||||
_value = parseInt(value);
|
||||
self.SetContent(_value);
|
||||
self.trigger('onChange', _value);
|
||||
};
|
||||
|
||||
this.GetValue = function () {
|
||||
return _value;
|
||||
};
|
||||
|
||||
this.Next = function () {
|
||||
if ( _inputValue.length ) {
|
||||
clearInterval(this.inputTimer);
|
||||
this.inputDone();
|
||||
}
|
||||
CSimpleSelect.prototype.Next.call();
|
||||
if ( self.max !== undefined ) {
|
||||
if ( _value + self.interval <= self.max ) {
|
||||
_value += self.interval;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
_value += self.interval;
|
||||
}
|
||||
self.RefreshContent();
|
||||
self.trigger('onNext', self.GetValue());
|
||||
self.trigger('onChange', self.GetValue());
|
||||
};
|
||||
|
||||
this.Previous = function () {
|
||||
if ( _inputValue.length ) {
|
||||
clearInterval(this.inputTimer);
|
||||
this.inputDone();
|
||||
}
|
||||
CSimpleSelect.prototype.Next.call();
|
||||
if ( self.min !== undefined ) {
|
||||
if ( _value - self.interval >= self.min ) {
|
||||
_value -= self.interval;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
_value -= self.interval;
|
||||
}
|
||||
self.RefreshContent();
|
||||
self.trigger('onPrevious', self.GetValue());
|
||||
self.trigger('onChange', self.GetValue());
|
||||
};
|
||||
this.RefreshContent = function () {
|
||||
this.SetContent(self.GetValue());
|
||||
self.value = self.GetValue(); // remove after system settings change
|
||||
self.DisableElement(self.leftArrow, false);
|
||||
self.DisableElement(self.rightArrow, false);
|
||||
if ( self.min !== undefined && _value - self.interval < self.min ) {
|
||||
self.DisableElement(self.leftArrow, true);
|
||||
}
|
||||
if ( self.max !== undefined && _value + self.interval > self.max ) {
|
||||
self.DisableElement(self.rightArrow, true);
|
||||
}
|
||||
};
|
||||
|
||||
this.onInput = function ( str ) {
|
||||
if ( !_inputValue.length ) {
|
||||
_oldValue = _value;
|
||||
this.DisableElement(this.leftArrow, true);
|
||||
this.DisableElement(this.rightArrow, true);
|
||||
}
|
||||
_inputValue += str;
|
||||
this.SetValue(_inputValue);
|
||||
clearInterval(this.inputTimer);
|
||||
this.inputTimer = window.setTimeout(
|
||||
function () {
|
||||
self.inputDone();
|
||||
},
|
||||
2000
|
||||
);
|
||||
};
|
||||
|
||||
this.inputClear = function () {
|
||||
_inputValue = '';
|
||||
_oldValue = null;
|
||||
this.actionBack();
|
||||
};
|
||||
|
||||
this.inputDone = function () {
|
||||
_inputValue = '';
|
||||
_oldValue = null;
|
||||
_value = Math.floor(_value / this.interval) * this.interval;
|
||||
if ( _value > this.max )
|
||||
_value = this.max;
|
||||
if ( _value < this.min )
|
||||
_value = this.min;
|
||||
this.SetValue(_value);
|
||||
this.RefreshContent();
|
||||
};
|
||||
|
||||
this.actionBack = function () {
|
||||
clearInterval(this.inputTimer);
|
||||
if ( _inputValue.length > 1 ) {
|
||||
_inputValue = _inputValue.slice(0, -1);
|
||||
this.SetValue(_inputValue);
|
||||
this.inputTimer = window.setTimeout(
|
||||
function () {
|
||||
self.inputDone();
|
||||
},
|
||||
2000
|
||||
);
|
||||
} else if ( _oldValue || _oldValue === 0 ) {
|
||||
this.SetValue(_oldValue);
|
||||
}
|
||||
};
|
||||
|
||||
this.EventHandler = function ( event ) {
|
||||
eventPrepare(event, true, 'CSimpleSelect');
|
||||
if ( this.disabled === true )
|
||||
return;
|
||||
event.stopped = true;
|
||||
switch ( event.code ) {
|
||||
case KEYS.LEFT:
|
||||
this.Previous();
|
||||
break;
|
||||
case KEYS.RIGHT:
|
||||
this.Next();
|
||||
break;
|
||||
case KEYS.BACK:
|
||||
this.actionBack();
|
||||
break;
|
||||
case KEYS.NUM0:
|
||||
case KEYS.NUM1:
|
||||
case KEYS.NUM2:
|
||||
case KEYS.NUM3:
|
||||
case KEYS.NUM4:
|
||||
case KEYS.NUM5:
|
||||
case KEYS.NUM6:
|
||||
case KEYS.NUM7:
|
||||
case KEYS.NUM8:
|
||||
case KEYS.NUM9:
|
||||
if ( !this.parameters.noInput )
|
||||
this.onInput(event.code - KEYS.NUM0);
|
||||
break;
|
||||
default :
|
||||
event.stopped = false;
|
||||
}
|
||||
};
|
||||
|
||||
this.IsChanged = function () {
|
||||
return _value !== _default;
|
||||
};
|
||||
|
||||
this.RefreshContent();
|
||||
|
||||
}
|
||||
// extending
|
||||
CIntervalBox.prototype = Object.create(CSimpleSelect.prototype);
|
||||
CIntervalBox.prototype.constructor = CIntervalBox;
|
|
@ -0,0 +1,769 @@
|
|||
/**
|
||||
* Item list navigation module
|
||||
* @author Stanislav Kalashnik <sk@infomir.eu>
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @class CScrollList
|
||||
* @param parent
|
||||
* @constructor
|
||||
*/
|
||||
function CScrollList ( parent ) {
|
||||
// parent constructor
|
||||
CBase.call(this, parent);
|
||||
|
||||
/**
|
||||
* The component inner name
|
||||
* @type {string}
|
||||
*/
|
||||
this.name = 'CScrollList';
|
||||
|
||||
/**
|
||||
* CSS class name associated with the component
|
||||
* @type {string}
|
||||
*/
|
||||
this.baseClass = 'cslist-main';
|
||||
/**
|
||||
* Shows the possibility of multiple selection
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.multipleSelection = true;
|
||||
|
||||
/**
|
||||
* the current selected item
|
||||
* @type {Node}
|
||||
*/
|
||||
this.activeItem = null;
|
||||
|
||||
/**
|
||||
* List of items for each state flag
|
||||
* Example: {focused:[item], marked:[item,item]}
|
||||
* @type {Object}
|
||||
*/
|
||||
this.states = {};
|
||||
|
||||
/**
|
||||
* Default settings for focus management in list
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.manageFocus = true;
|
||||
|
||||
/**
|
||||
* default item attribute values
|
||||
* used for an item initialization
|
||||
* @namespace
|
||||
* @property {boolean} hidden display or not
|
||||
* @property {boolean} marked initial checked state
|
||||
* @property {boolean} disabled can be focused or not
|
||||
* @property {boolean} focused initial focused state
|
||||
*/
|
||||
this.defaultParams = {
|
||||
hidden : false, // display or not
|
||||
marked : false, // initial checked state
|
||||
disabled : false, // can be focused or not
|
||||
focused : false, // initial focused state
|
||||
href : '#', // empty link
|
||||
self : this, // back link to the component itself
|
||||
// flag to manage focus handling
|
||||
manageFocus : true,
|
||||
// right mouse click (suppress the context menu)
|
||||
oncontextmenu: EMULATION ? null : function () { return false; },
|
||||
// mouse click on the item or Ok/Enter key
|
||||
onclick : function() {
|
||||
// activate item
|
||||
this.self.Focused(this, true);
|
||||
return false;
|
||||
},
|
||||
onmouseover : function() {
|
||||
// activate item
|
||||
this.self.Focused(this, true);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* default item filter values
|
||||
* used for focus handling
|
||||
* @type {Object}
|
||||
*/
|
||||
this.defaultFilter = {
|
||||
hidden : false, // visible
|
||||
disabled : false // enabled
|
||||
};
|
||||
|
||||
/**
|
||||
* scrolling method on up/down arrows
|
||||
* 0 - no special way (default browser shift to the center on focus)
|
||||
* 1 - focus is always centered (convenient but redraw the whole container on each step)
|
||||
* 2 - shift by pages (quick and low resources)
|
||||
* @type {number}
|
||||
*/
|
||||
this.scrollMode = 2;
|
||||
|
||||
/**
|
||||
* render mode: one by one
|
||||
* @type {number}
|
||||
*/
|
||||
this.RENDER_MODE_SINGLE = 1;
|
||||
/**
|
||||
* render mode: collect all added to the fragment and in the end render everything at once
|
||||
* @type {number}
|
||||
*/
|
||||
this.RENDER_MODE_BULK = 2;
|
||||
|
||||
/**
|
||||
* current render mode (one by one is the default)
|
||||
* @type {number}
|
||||
*/
|
||||
this.renderMode = this.RENDER_MODE_SINGLE;
|
||||
|
||||
/**
|
||||
* buffer for added items in the bulk mode
|
||||
* @type {DocumentFragment}
|
||||
*/
|
||||
this.fragment = document.createDocumentFragment();
|
||||
}
|
||||
|
||||
// extending
|
||||
CScrollList.prototype = Object.create(CBase.prototype);
|
||||
CScrollList.prototype.constructor = CScrollList;
|
||||
|
||||
|
||||
/**
|
||||
* Component initialization with its placeholder.
|
||||
* Should be called once before use just after constructor invoke and all hooks preparation.
|
||||
* @param {Node} handle component placeholder (it should be an empty div element)
|
||||
*/
|
||||
CScrollList.prototype.Init = function ( handle ) {
|
||||
// parent call init with placeholder
|
||||
CBase.prototype.Init.call(this, handle);
|
||||
|
||||
var self = this;
|
||||
this.handleInner.onmousewheel = function( event ) {
|
||||
// direction and new focused item
|
||||
var direction = event.wheelDeltaY > 0;
|
||||
var found = self.Next(null, direction);
|
||||
// apply
|
||||
if ( found ) {
|
||||
self.MoveNext(direction ? -1 : 1);
|
||||
self.Focused(found, true);
|
||||
}
|
||||
event.stopPropagation();
|
||||
// prevent
|
||||
return false;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Create a new item and add it to the placeholder
|
||||
* visible/enabled/not focused and not checked by default
|
||||
* corresponding css classes (the same names as flags):
|
||||
* hidden - for invisible items
|
||||
* marked - for checked items
|
||||
* disabled - for items that can't be focused or selected
|
||||
* focused - for a single item active at the moment
|
||||
* @param {string|Node|Array} body item content
|
||||
* @param {Object} [attrs] list of element attributes
|
||||
* @return {Node} created item element
|
||||
*/
|
||||
CScrollList.prototype.Add = function ( body, attrs ) {
|
||||
// check input
|
||||
attrs = attrs || {};
|
||||
// new item body
|
||||
var item = element('a', this.defaultParams, body);
|
||||
// mode-specific
|
||||
if ( this.renderMode === this.RENDER_MODE_BULK ) {
|
||||
// add item to buffer
|
||||
this.fragment.appendChild(item);
|
||||
} else {
|
||||
// add item to DOM container
|
||||
this.handleInner.appendChild(item);
|
||||
}
|
||||
// apply flags and decoration
|
||||
if ( attrs.hidden ) { this.Hidden(item, true); }
|
||||
if ( attrs.marked ) { this.Marked(item, true); }
|
||||
if ( attrs.disabled ) { this.Disabled(item, true); }
|
||||
if ( attrs.focused ) { this.Focused(item, true, attrs.manageFocus); }
|
||||
// apply custom attributes with the current defaults
|
||||
for ( var name in attrs ) {
|
||||
if ( attrs.hasOwnProperty(name) ) { item[name] = attrs[name]; }
|
||||
}
|
||||
// result element
|
||||
return item;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Add all the added items to the DOM
|
||||
*/
|
||||
CScrollList.prototype.Render = function () {
|
||||
// add the buffer to DOM container
|
||||
if ( this.fragment.childNodes.length > 0 ) {
|
||||
this.handleInner.appendChild(this.fragment);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Reset and clear all items and options.
|
||||
* This will make the component ready for a new filling.
|
||||
*/
|
||||
CScrollList.prototype.Clear = function () {
|
||||
// cleaning all items
|
||||
this.handleInner.innerHTML = null; // not a life-saver :/
|
||||
// vars
|
||||
this.activeItem = null;
|
||||
this.states = {};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Reset only the given item to the default state
|
||||
* @param {Node} item the element to be processed
|
||||
*/
|
||||
CScrollList.prototype.Reset = function ( item ) {
|
||||
// valid html element given
|
||||
if ( item && item.nodeName ) {
|
||||
// apply flags and decoration
|
||||
this.Hidden(item, this.defaultParams.hidden);
|
||||
this.Marked(item, this.defaultParams.marked);
|
||||
this.Disabled(item, this.defaultParams.disabled);
|
||||
this.Focused(item, this.defaultParams.focused);
|
||||
// clear focus pointer if necessary
|
||||
if ( item === this.activeItem && !item.focused ) { this.activeItem = null; }
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes the given elements and reposition the focus
|
||||
* @param {[Node]} items list of elements to be processed
|
||||
*/
|
||||
CScrollList.prototype.DeleteAll = function ( items ) {
|
||||
var self = this,
|
||||
curPos = null;
|
||||
// collect affected items
|
||||
// there are some
|
||||
if ( Array.isArray(items) && items.length > 0 ) {
|
||||
// clear focus (for future refocus)
|
||||
if ( document.activeElement !== null && document.activeElement.parentNode === this.handleInner ) { document.activeElement.blur(); }
|
||||
// cursor position
|
||||
if ( items.indexOf(this.Current()) === -1 ) {
|
||||
// not intersect
|
||||
curPos = this.Current();
|
||||
} else {
|
||||
// get the next good (scan down)
|
||||
curPos = this.Next({marked:false, hidden:false, disabled:false});
|
||||
// not found or the last in the list
|
||||
if ( curPos === null || curPos === this.Current() ) {
|
||||
// scan up
|
||||
curPos = this.Next({marked:false, hidden:false, disabled:false}, true);
|
||||
}
|
||||
}
|
||||
// apply
|
||||
items.forEach(function ( item ) {
|
||||
self.Delete(item);
|
||||
});
|
||||
// the nearest available item
|
||||
if ( curPos !== null ) {
|
||||
this.Focused(curPos, true);
|
||||
this.SetPosition(curPos, true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Remove the given item and clear inner states if necessary
|
||||
* @param {Node} item the element to be processed
|
||||
*/
|
||||
CScrollList.prototype.Delete = function ( item ) {
|
||||
// valid html element given
|
||||
if ( item && item.nodeName && item.parentNode === this.handleInner ) {
|
||||
// clear states
|
||||
for ( var name in this.states ) {
|
||||
if ( this.states.hasOwnProperty(name) ) {
|
||||
// find
|
||||
var index = this.states[name].indexOf(item);
|
||||
// remove
|
||||
if ( index !== -1 ) { this.states[name].splice(index, 1); }
|
||||
}
|
||||
}
|
||||
// clear focus pointer if necessary
|
||||
if ( item === this.activeItem ) { this.activeItem = null; }
|
||||
// delete dom element
|
||||
this.handleInner.removeChild(item);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Getter for currently focused element
|
||||
* @return {Node} or null if there is no such item
|
||||
*/
|
||||
CScrollList.prototype.Current = function () {
|
||||
return this.activeItem;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Getter for element total number (actual items + buffered and not yet rendered)
|
||||
* @return {number}
|
||||
*/
|
||||
CScrollList.prototype.Length = function () {
|
||||
return this.handleInner.children.length + this.fragment.childNodes.length;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set inner item flags and decoration
|
||||
* @param {Node} item the element to be processed
|
||||
* @param {string} option item inner flag name
|
||||
* @param {boolean} state flag of the operation (true if change is made)
|
||||
* @return {boolean} operation status
|
||||
*/
|
||||
CScrollList.prototype.SetState = function ( item, option, state ) {
|
||||
state = Boolean(state);
|
||||
// current and new states are different
|
||||
if ( item[option] !== state ) {
|
||||
// check if exist
|
||||
if ( !this.states[option] ) { this.states[option] = []; }
|
||||
var index = this.states[option].indexOf(item);
|
||||
// update internal list
|
||||
if ( state ) {
|
||||
// add to the list
|
||||
if ( index === -1 ) { this.states[option].push(item); }
|
||||
} else {
|
||||
// remove
|
||||
if ( index !== -1 ) { this.states[option].splice(index, 1); }
|
||||
}
|
||||
var oldVal = item[option];
|
||||
// flag
|
||||
item[option] = state;
|
||||
// decoration
|
||||
if ( state ) {
|
||||
// add the corresponding class
|
||||
item.classList.add(option);
|
||||
} else {
|
||||
// remove the corresponding class
|
||||
item.classList.remove(option);
|
||||
}
|
||||
// call user hook
|
||||
if ( typeof this.onStateChange === 'function' ) { this.onStateChange(item, option, oldVal, state); }
|
||||
return true;
|
||||
}
|
||||
// nothing has changed
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handle visibility state for the given item
|
||||
* also correct check/focus state if hiding
|
||||
* @param {Node} item the element to be processed
|
||||
* @param {boolean} state flag of the state
|
||||
* @return {boolean} operation status
|
||||
*/
|
||||
CScrollList.prototype.Hidden = function ( item, state ) {
|
||||
state = Boolean(state);
|
||||
// valid html element given
|
||||
if ( item && item.nodeName ) {
|
||||
// flag and decoration
|
||||
var changed = this.SetState(item, 'hidden', state);
|
||||
// operation ok and the item is hidden
|
||||
if ( changed && state ) {
|
||||
// clear internal cursor if necessary
|
||||
if ( item.focused ) { this.activeItem = null; }
|
||||
// uncheck and remove focus
|
||||
this.SetState(item, 'marked', false);
|
||||
this.SetState(item, 'focused', false);
|
||||
}
|
||||
// operation status
|
||||
return changed;
|
||||
}
|
||||
// failure
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handle checked state for the given item
|
||||
* @param {Node} item the element to be processed
|
||||
* @param {boolean} state flag of the state
|
||||
* @return {boolean} operation status
|
||||
*/
|
||||
CScrollList.prototype.Marked = function ( item, state ) {
|
||||
var self = this;
|
||||
state = Boolean(state);
|
||||
// valid html element given, enabled and visible
|
||||
if ( item && item.nodeName && !item.disabled && !item.hidden ) {
|
||||
if (this.multipleSelection === false) {
|
||||
(this.states.marked || []).forEach(function(marked){
|
||||
self.SetState(marked, 'marked', false);
|
||||
});
|
||||
}
|
||||
// operation status
|
||||
return this.SetState(item, 'marked', state);
|
||||
}
|
||||
// failure
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handle enable/disable state for the given item
|
||||
* @param {Node} item the element to be processed
|
||||
* @param {boolean} state flag of the state
|
||||
* @return {boolean} operation status
|
||||
*/
|
||||
CScrollList.prototype.Disabled = function ( item, state ) {
|
||||
state = Boolean(state);
|
||||
// valid html element given
|
||||
if ( item && item.nodeName ) {
|
||||
// flag and decoration
|
||||
var changed = this.SetState(item, 'disabled', state);
|
||||
// operation ok and the item is disabled
|
||||
if ( changed && state ) {
|
||||
// clear internal cursor if necessary
|
||||
if ( item.focused ) { this.activeItem = null; }
|
||||
// uncheck and remove focus
|
||||
this.SetState(item, 'marked', false);
|
||||
this.SetState(item, 'focused', false);
|
||||
}
|
||||
// operation status
|
||||
return changed;
|
||||
}
|
||||
// failure
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handle focus state for the given item
|
||||
* also removes the focus from the previously focused item
|
||||
* @param {Node} item the element to be processed
|
||||
* @param {boolean} [state=true] flag of the state
|
||||
* @param {boolean} [manageFocus=true] flag to manage focus handling
|
||||
* @return {boolean} operation status
|
||||
*/
|
||||
CScrollList.prototype.Focused = function ( item, state, manageFocus ) {
|
||||
var changed = false,
|
||||
prevent = false;
|
||||
state = state !== false;
|
||||
if (manageFocus === undefined) { manageFocus = this.manageFocus; }
|
||||
// valid html element given, enabled and visible
|
||||
if ( item && item.nodeName && !item.disabled && !item.hidden ) {
|
||||
// states differ
|
||||
if ( state !== item.focused ) {
|
||||
if ( state ) {
|
||||
// different items (not currently active item)
|
||||
if ( item !== this.activeItem ) {
|
||||
// call user hook which can prevent further processing
|
||||
if ( typeof this.onFocus === 'function' ) { prevent = this.onFocus(item, this.activeItem); }
|
||||
// block or not
|
||||
if ( !prevent ) {
|
||||
// flag and decoration
|
||||
changed = this.SetState(item, 'focused', state);
|
||||
// clear the previously focused item
|
||||
this.Focused(this.activeItem, false, manageFocus);
|
||||
// global flag
|
||||
this.activeItem = item;
|
||||
// set actual focus if necessary
|
||||
if ( manageFocus !== false ) { this.activeItem.focus(); }
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// flag and decoration
|
||||
changed = this.SetState(item, 'focused', state);
|
||||
// focus removed if necessary
|
||||
if ( manageFocus !== false ) { this.activeItem.blur(); }
|
||||
this.activeItem = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
// operation status
|
||||
return changed;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Make the whole component active, set focused item and give actual focus
|
||||
* give a focus to the appropriate item (last focused or the first one)
|
||||
* @param {boolean} [state=true] set active or deactivate
|
||||
* @param {boolean} [manageFocus=true] focus handling mode: true - set/remove focus accordingly, false - manual focus management
|
||||
* @return {boolean} operation status
|
||||
*/
|
||||
CScrollList.prototype.Activate = function ( state, manageFocus ) {
|
||||
if (manageFocus === undefined) { manageFocus = this.manageFocus; }
|
||||
// parent call
|
||||
CBase.prototype.Activate.call(this, state);
|
||||
if ( this.isActive ) {
|
||||
// get the first good one
|
||||
this.activeItem = this.activeItem || this.FindOne();
|
||||
// still no active item
|
||||
if ( this.activeItem === null ) { return false; }
|
||||
// flag and decoration
|
||||
this.SetState(this.activeItem, 'focused', true);
|
||||
// make it focused
|
||||
this.SetPosition(this.activeItem);
|
||||
if ( manageFocus !== false ) { this.activeItem.focus(); }
|
||||
} else {
|
||||
// remove focus if there is an element
|
||||
if ( this.activeItem ) { this.activeItem.blur(); }
|
||||
}
|
||||
// all is ok
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Go through all the items
|
||||
* @param {Function} callback iteration callback function
|
||||
*/
|
||||
CScrollList.prototype.Each = function ( callback ) {
|
||||
Array.prototype.forEach.call(this.handleInner.children, callback);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get item list according to the given filter conditions and amount limitation
|
||||
* @param {Object} [filter=this.defaultFilter] list of attributes for searching
|
||||
* @param {number} [limit=0] amount of items to get (0 - all possible)
|
||||
* @param {boolean} [reverse=false] to invert search direction (true - search backwards, false - from first to last)
|
||||
* @return {Node[]} found items
|
||||
*/
|
||||
CScrollList.prototype.Find = function ( filter, limit, reverse ) {
|
||||
// preparing
|
||||
var match, // flag for items comparison
|
||||
found = [], // result item list
|
||||
items = this.handleInner.children, // all list items
|
||||
itlen = items.length, // item list amount
|
||||
citem = null; // current item pointer
|
||||
// use default if not set
|
||||
filter = filter || this.defaultFilter;
|
||||
// iterate all items till all items are found
|
||||
for ( var i = 0; i < itlen; i++ ) {
|
||||
// floating pointer depends on direction
|
||||
citem = items[reverse ? itlen-i-1 : i];
|
||||
// init state
|
||||
match = true;
|
||||
// check all the filter attributes (all should match)
|
||||
for ( var attr in filter ) {
|
||||
if ( filter.hasOwnProperty(attr) ) { match = match && (citem[attr] === filter[attr]); }
|
||||
}
|
||||
// matched item
|
||||
if ( match ) {
|
||||
// add to the result list
|
||||
found.push(citem);
|
||||
// check limit and exit if set and enough
|
||||
if ( limit && found.length >= limit ) { break; }
|
||||
}
|
||||
}
|
||||
return found;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the first item matching the given filter conditions
|
||||
* @param {Object} [filter=this.defaultFilter] list of attributes for searching
|
||||
* @param {boolean} [reverse=false] to invert search direction (true - search backwards, false - from first to last)
|
||||
* @return {Node|null} found item or null
|
||||
*/
|
||||
CScrollList.prototype.FindOne = function ( filter, reverse ) {
|
||||
return this.Find(filter, 1, reverse).pop() || null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the next/previous item from the current focused item
|
||||
* according to the given filter and search direction
|
||||
* searching for a closest next item by default
|
||||
* can go to the next/previous page with nskip = items-per-page
|
||||
* @param {Object} [filter=this.defaultFilter] list of attributes for searching
|
||||
* @param {boolean} [reverse=false] to invert search direction (true - return previous, false - next)
|
||||
* @param {number} [nskip=0] amount of items to skip
|
||||
* @param {boolean} [toend=false] correction for tiled list
|
||||
* @return {Node|null} found item or null if there are no suitable ones
|
||||
*/
|
||||
CScrollList.prototype.Next = function ( filter, reverse, nskip, toend ) {
|
||||
// preparing
|
||||
var match, // flag for items comparison
|
||||
suitable = this.activeItem, // the last found matching item (starting from the current)
|
||||
pointer = this.activeItem, // the floating current item for processing
|
||||
skipcount = 0; // counter of found items per page
|
||||
// amount of items to skip
|
||||
nskip = nskip || 0;
|
||||
// there is a starting item
|
||||
if ( pointer ) {
|
||||
// use default if not set
|
||||
filter = filter || this.defaultFilter;
|
||||
// iterate from the current position till the edge of the list
|
||||
while ( (pointer = (reverse ? pointer.previousSibling : pointer.nextSibling)) ) {
|
||||
// suitable by default
|
||||
match = true;
|
||||
// check all the filter attributes (all should match)
|
||||
for ( var attr in filter ) {
|
||||
if ( filter.hasOwnProperty(attr) ) { match = match && (pointer[attr] === filter[attr]); }
|
||||
}
|
||||
// count only visible items
|
||||
if ( !pointer.hidden ) { skipcount++; }
|
||||
// suitable item is found
|
||||
if ( match ) {
|
||||
// matching becomes the current
|
||||
if (toend !== false) { suitable = pointer; }
|
||||
// skip item correction if necessary
|
||||
if ( nskip === 0 || (nskip > 0 && skipcount >= nskip) ) { return pointer; }
|
||||
}
|
||||
}
|
||||
}
|
||||
return suitable;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set scroll position relatively some list element
|
||||
* @param {Object} item
|
||||
* @param {boolean} [makeFocused] - apply all attributes and corresponding actions
|
||||
* @param {boolean} [manageFocus] - set actual focus
|
||||
*/
|
||||
CScrollList.prototype.SetPosition = function ( item, makeFocused, manageFocus ) {
|
||||
var index, page;
|
||||
if ( makeFocused ) {
|
||||
if ( manageFocus === undefined ) { manageFocus = this.manageFocus; }
|
||||
this.Focused(item || this.FindOne(), true, manageFocus);
|
||||
}
|
||||
if ( this.activeItem !== null ) {
|
||||
index = this.activeItem.offsetTop / this.itemHeight;
|
||||
page = Math.floor(index / this.itemsPerPage);
|
||||
// different methods to scroll
|
||||
if ( this.scrollMode === 1 ) {
|
||||
this.handleInner.scrollTop = (index - Math.floor((this.itemsPerPage - 1) / 2)) * this.itemHeight;
|
||||
} else if ( this.scrollMode === 2 ) {
|
||||
this.handleInner.scrollTop = (this.itemsPerPage * this.itemHeight * page);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handle external events
|
||||
* @param {Event} event global event object
|
||||
* @param {Object} [filter=this.defaultFilter] list of attributes for searching
|
||||
* @param {boolean} [manageFocus=this.manageFocus]
|
||||
*/
|
||||
CScrollList.prototype.EventHandler = function ( event, filter, manageFocus ) {
|
||||
var found = null;
|
||||
if (manageFocus === undefined) { manageFocus = this.manageFocus; }
|
||||
if (event.stopped === true) { return; }
|
||||
// moving direction
|
||||
switch ( event.code ) {
|
||||
case KEYS.PAGE_UP:
|
||||
case KEYS.PAGE_DOWN:
|
||||
// jump to the next/previous item skipping page amount of items
|
||||
found = this.Next(filter, event.code !== KEYS.PAGE_DOWN, this.itemsPerPage);
|
||||
// correct visible view
|
||||
this.handleInner.scrollTop = this.handleInner.scrollTop + (event.code === KEYS.PAGE_UP ? -1 : 1 ) * this.itemsPerPage * this.itemHeight;
|
||||
break;
|
||||
case KEYS.LEFT:
|
||||
case KEYS.RIGHT:
|
||||
case KEYS.HOME:
|
||||
case KEYS.END:
|
||||
// look for a single item from the beginning or end of the list
|
||||
found = this.FindOne(filter, event.code === KEYS.RIGHT || event.code === KEYS.END);
|
||||
break;
|
||||
case KEYS.UP:
|
||||
case KEYS.DOWN:
|
||||
// jump to the next/previous item
|
||||
found = this.Next(filter, event.code === KEYS.UP);
|
||||
// there is a selection
|
||||
this.MoveNext(event.code === KEYS.UP ? -1 : 1);
|
||||
break;
|
||||
case KEYS.OK:
|
||||
// blank but necessary to prevent suppression
|
||||
// !exit!
|
||||
if ( this.activeItem ) { this.activeItem.onclick(); } // commented to prevent double invoke
|
||||
event.preventDefault();
|
||||
return;
|
||||
default:
|
||||
// suppress everything else and exit
|
||||
//event.preventDefault();
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
// make focused the first item if not found
|
||||
this.Focused(found || this.FindOne(filter), true, manageFocus);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Scroll by page if needed
|
||||
* @param direction scroll direction
|
||||
* @constructor
|
||||
*/
|
||||
CScrollList.prototype.MoveNext = function (direction){
|
||||
if ( this.activeItem !== null ) {
|
||||
// different methods to scroll
|
||||
if ( this.scrollMode === 1 ) {
|
||||
// focus is always centered (convenient but redraw the whole container on each step)
|
||||
this.handleInner.scrollTop = this.activeItem.offsetTop - Math.floor((this.itemsPerPage-1)/2) * this.itemHeight + direction * this.itemHeight;
|
||||
} else if ( this.scrollMode === 2 ) {
|
||||
// shift by pages (quick and low resources)
|
||||
if ( direction === -1 ) {
|
||||
if ( this.activeItem.offsetTop === this.handleInner.scrollTop ) {
|
||||
this.handleInner.scrollTop = this.handleInner.scrollTop - this.itemsPerPage * this.itemHeight;
|
||||
}
|
||||
} else {
|
||||
if ( this.activeItem.offsetTop - this.handleInner.scrollTop === (this.itemsPerPage-1) * this.itemHeight ) {
|
||||
this.handleInner.scrollTop = this.handleInner.scrollTop + this.itemsPerPage * this.itemHeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Hook method on focus item change
|
||||
* should be declared in child to invoke
|
||||
* @param {Node} item the new focused item
|
||||
* @param {Node} previous the old focused item
|
||||
* @return {boolean} true - prevent focus from changing, false|undefined - usual behaviour
|
||||
*/
|
||||
CScrollList.prototype.onFocus = null;
|
||||
|
||||
|
||||
/**
|
||||
* Hook method on item internal states change
|
||||
* should be declared in child to invoke
|
||||
* @param {Node} item the new focused item
|
||||
* @param {string} option affected item state name
|
||||
* @param {string|boolean} oldVal previous state value
|
||||
* @param {string|boolean} newVal new state value
|
||||
*/
|
||||
CScrollList.prototype.onStateChange = null;
|
||||
|
||||
|
||||
Object.defineProperty(CScrollList.prototype, 'itemsPerPage', {
|
||||
get: function () {
|
||||
if ( this._itemsPerPage ) {
|
||||
return this._itemsPerPage;
|
||||
} else {
|
||||
return (this._itemsPerPage = Math.round(this.handleInner.offsetHeight / this.handleInner.firstChild.offsetHeight));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Object.defineProperty(CScrollList.prototype, 'itemHeight', {
|
||||
get: function () {
|
||||
if ( this.handleInner.firstChild ) {
|
||||
return this.handleInner.firstChild.offsetHeight;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
});
|
|
@ -0,0 +1,164 @@
|
|||
/**
|
||||
* Item tree list navigation module
|
||||
* @class CTreeList
|
||||
* @extends CScrollList
|
||||
* @constructor
|
||||
* @author Kopanev Igor
|
||||
*/
|
||||
|
||||
function CTreeList ( parent, options ){
|
||||
|
||||
this.openedClass = "opened";
|
||||
this.branchClass = "branch";
|
||||
this.arrowClassName = "arrow";
|
||||
this.placeholderClassName = "placeholder";
|
||||
|
||||
if (options !== undefined){
|
||||
CTreeList.parameters.forEach(function(name){
|
||||
if (options[name] !== undefined)
|
||||
self[name] = options[name];
|
||||
});
|
||||
}
|
||||
|
||||
this.tree || (this.tree = []);
|
||||
CScrollList.call(this, parent);
|
||||
this.baseClass += " ctreelist-main";
|
||||
}
|
||||
|
||||
// extends
|
||||
CTreeList.prototype = Object.create(CScrollList.prototype);
|
||||
CTreeList.prototype.constructor = CTreeList;
|
||||
|
||||
CTreeList.parameters = ["openedClass", "folderClass", "arrowClassName", "placeholderClassName", "contentField", "titleField", "data"];
|
||||
|
||||
CTreeList.prototype.SetTree = function ( tree , refresh ){
|
||||
this.tree = tree;
|
||||
if (refresh === true)
|
||||
this.Refresh();
|
||||
};
|
||||
|
||||
CTreeList.prototype.Refresh = function (){
|
||||
var el, i;
|
||||
this.Clear();
|
||||
for( i = 0; i < this.tree.length; i++ ){
|
||||
el = this.tree[i];
|
||||
el.item = this.Add({data: el, level: 0});
|
||||
if (Array.isArray(el[this.contentField])){
|
||||
this.RenderLevel(el[this.contentField], el.item, 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
CTreeList.prototype.RenderLevel = function ( data, parent, level ){
|
||||
var el, i, attrs = {};
|
||||
for ( i = 0; i < data.length; i++ ){
|
||||
el = data[i];
|
||||
el.parent = parent;
|
||||
if (parent.hidden === true || parent.data.opened === false){
|
||||
attrs.hidden = true;
|
||||
}
|
||||
el.item = this.Add({data: el, level: level, attrs: attrs});
|
||||
if (Array.isArray(el[this.contentField])){
|
||||
this.RenderLevel(el[this.contentField], el.item, level + 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
CTreeList.prototype.Add = function ( options ) {
|
||||
var item, items = [], i,
|
||||
attrs = options.attrs || {};
|
||||
if (options.level !== undefined){
|
||||
for( i = 0; i < options.level; i++ ){
|
||||
items.push(element('div', {className: this.placeholderClassName}));
|
||||
}
|
||||
}
|
||||
attrs.data = options.data || {};
|
||||
attrs.data.level = options.level;
|
||||
items.push(options.data[this.titleField]);
|
||||
item = CScrollList.prototype.Add.call(this, items, attrs);
|
||||
if (Array.isArray(options.data[this.contentField])){
|
||||
item.classList.add(this.branchClass);
|
||||
if (options.data.opened === true){
|
||||
item.classList.add(this.openedClass);
|
||||
}
|
||||
}
|
||||
return item;
|
||||
};
|
||||
|
||||
CTreeList.prototype.OpenBranch = function ( item, open, forced, subBranch ){
|
||||
var self = this;
|
||||
if (open === true){
|
||||
item.classList.add(this.openedClass);
|
||||
}else{
|
||||
item.classList.remove(this.openedClass);
|
||||
}
|
||||
if (Array.isArray(item.data[this.contentField])){
|
||||
item.data[this.contentField].forEach(function( element ){
|
||||
if (element.item.visible !== false){
|
||||
if (Array.isArray(element[self.contentField])){
|
||||
if ((element.opened === true && open) || !open || forced)
|
||||
self.OpenBranch(element.item, open, forced, true);
|
||||
}
|
||||
self.Hidden(element.item, !open);
|
||||
}
|
||||
});
|
||||
if (forced || subBranch !== true) {
|
||||
item.data.opened = open;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
CTreeList.prototype.OpenAll = function ( open ){
|
||||
for ( var i = 0, item; i < this.tree.length, item = this.tree[i]; i++ ){
|
||||
if (item.type === MEDIA_TYPE_HELP_FOLDER){
|
||||
this.OpenBranch(item.item, open, true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
CTreeList.prototype.Filter = function ( text ){
|
||||
var count, word,
|
||||
self = this,
|
||||
words = text.split(' '),
|
||||
leafs = [];
|
||||
this.Each(function(item){
|
||||
count = 0;
|
||||
if (item.data[this.contentField] === undefined){
|
||||
for (var i = 0; i < words.length, word = words[i]; i++){
|
||||
if (item.data.sentence.toLowerCase().indexOf(word) !== -1){
|
||||
count++;
|
||||
}
|
||||
}
|
||||
if (count === words.length){
|
||||
leafs.push(item);
|
||||
}
|
||||
}
|
||||
item.visible = false;
|
||||
self.Hidden(item, true);
|
||||
});
|
||||
leafs.forEach(function(leaf){
|
||||
self.ShowLeaf(leaf, true);
|
||||
});
|
||||
this.Focused(this.FirstMatch(this.filterText), true);
|
||||
};
|
||||
|
||||
CTreeList.prototype.ShowAll = function(){
|
||||
var self = this;
|
||||
this.Each(function(item){
|
||||
self.Hidden(item, false);
|
||||
item.visible = true;
|
||||
});
|
||||
this.Focused(this.FindOne(), true);
|
||||
};
|
||||
|
||||
CTreeList.prototype.ShowLeaf = function( leaf, show ){
|
||||
var parent = leaf, self = this;
|
||||
if (leaf.data.type === MEDIA_TYPE_HELP_ARTICLE){
|
||||
while (parent = parent.data.parent){
|
||||
self.Hidden(parent, show !== true);
|
||||
parent.visible = show;
|
||||
}
|
||||
}
|
||||
leaf.visible = show;
|
||||
self.Hidden(leaf, show !== true);
|
||||
};
|
|
@ -0,0 +1,529 @@
|
|||
'use strict';
|
||||
|
||||
var MODAL_IMG_PATH = configuration.newRemoteControl ? PATH_IMG_SYSTEM + 'buttons/new/' : PATH_IMG_SYSTEM + 'buttons/old/',
|
||||
|
||||
/**
|
||||
* @param parent
|
||||
* @param options
|
||||
* @constructor
|
||||
* Phrases that should be translated:
|
||||
* 'Retry', 'Cancel', 'Show log', 'Update', 'Status', 'Check image', 'Exit', 'Current version',
|
||||
* 'New version', 'Description', 'Updating to version', 'Update status', 'Hide log', 'List of changes'
|
||||
* '<span class='alert'>Warning!</span> Device will be rebooted after update'
|
||||
*/
|
||||
CUpdateModal = function ( parent, options ) {
|
||||
var self = this;
|
||||
|
||||
CModalBox.call(this, parent || null);
|
||||
|
||||
this.isVisible = false;
|
||||
this.name = 'CUpdateModal';
|
||||
this.baseClass = 'cmodal-main cupdate-modal';
|
||||
this.$status = element('td', {className: 'right'}); // status element
|
||||
|
||||
this.F1_func = null;
|
||||
this.Info_func = null;
|
||||
|
||||
this.image_info = {}; // image info cache
|
||||
|
||||
CUpdateModal.parameters.forEach(function ( option ) {
|
||||
if ( options[option] !== undefined ) {self[option] = options[option];}
|
||||
});
|
||||
|
||||
this.bind(this.events);
|
||||
|
||||
this.bpanel = new CButtonPanel();
|
||||
this.bpanel.Init(MODAL_IMG_PATH);
|
||||
|
||||
this.Exit_btn = this.bpanel.Add(KEYS.EXIT, 'exit.png', _('Cancel'), function () {
|
||||
self.Update.Clear();
|
||||
self.Show(false);
|
||||
}, this.auto === true);
|
||||
|
||||
this.Info_btn = this.bpanel.Add(KEYS.INFO, 'info.png', _('Show log'), function () {
|
||||
self.Info_func();
|
||||
}, this.log !== true);
|
||||
|
||||
this.OK_btn = this.bpanel.Add(KEYS.F1, 'f1.png', _('Update'), function () {
|
||||
self.F1_func();
|
||||
}, true);
|
||||
|
||||
this.ProgressBar = new CProgressBar(this, element('div'));
|
||||
|
||||
this.SetHeader(this.header_text);
|
||||
this.SetFooter(this.bpanel.handle);
|
||||
|
||||
this.EventHandler = function ( event ) {
|
||||
eventPrepare(event);
|
||||
if ( self.selectBox !== undefined && !self.selectBox.disabled && typeof self.selectBox.EventHandler === 'function' ) {
|
||||
self.selectBox.EventHandler(event);
|
||||
}
|
||||
self.bpanel.EventHandler(event);
|
||||
};
|
||||
|
||||
this.Init();
|
||||
};
|
||||
|
||||
CUpdateModal.parameters = ['update_url', 'check', 'auto', 'images', 'log', 'select', 'header_text', 'info', 'events', 'warning']; // parameters list
|
||||
|
||||
// extending
|
||||
CUpdateModal.prototype = Object.create(CModalBox.prototype);
|
||||
CUpdateModal.prototype.constructor = CUpdateModal;
|
||||
|
||||
/**
|
||||
* Trigger onHide event
|
||||
*/
|
||||
CUpdateModal.prototype.onHide = function () {
|
||||
this.trigger('onHide');
|
||||
};
|
||||
|
||||
/**
|
||||
* Trigger onShow event
|
||||
*/
|
||||
CUpdateModal.prototype.onShow = function () {
|
||||
this.trigger('onShow');
|
||||
};
|
||||
|
||||
|
||||
CUpdateModal.prototype.onInit = function () {
|
||||
var self = this;
|
||||
|
||||
this.Update = new Update({
|
||||
onReady: function () {
|
||||
echo('CUpdateModal.Update: onReady listener');
|
||||
self.image_info = self.Update.GetImageInfo(); // get Updating image info
|
||||
self.image_description = self.image_info.description;
|
||||
self.image_date = self.image_info.date;
|
||||
if ( self.auto === true ) { // check autoupdate
|
||||
self.UpdateStart(); // if auto start update immediately
|
||||
} else {
|
||||
self.ImageSelect(); // else show conformation window
|
||||
}
|
||||
self.trigger('onReady');
|
||||
},
|
||||
onCheck: function ( data ) {
|
||||
echo('CUpdateModal.Update: onCheck listener');
|
||||
self._addLogMessage(data.logMessage); // add log message
|
||||
self.$status.innerHTML = data.statusMessage; // show current state of check
|
||||
self.$status.className = 'right';
|
||||
self.trigger('onCheck', data); // trigger onCheck event
|
||||
self.bpanel.Rename(self.OK_btn, _('Update'));
|
||||
},
|
||||
onError: function ( data ) {
|
||||
echo('CUpdateModal.Update: onError listener');
|
||||
self._addLogMessage(data.logMessage, 'error'); // add log message
|
||||
// Show buttons
|
||||
self.bpanel.Hidden(self.OK_btn, false); // show OK button
|
||||
self.bpanel.Hidden(self.Exit_btn, false); // show Exit button
|
||||
self.bpanel.Rename(self.OK_btn, _('Retry'));
|
||||
self.bpanel.Rename(self.Exit_btn, _('Exit'));
|
||||
self.$status.innerHTML = data.errorMessage; // show Error message in status field
|
||||
self.$status.classList.add('error');
|
||||
if ( self.selectBox ) {
|
||||
self.selectBox.disabled = false;
|
||||
}
|
||||
self.trigger('onError', data);
|
||||
},
|
||||
onProgress: function ( data ) {
|
||||
echo('CUpdateModal.Update: onProgress listener');
|
||||
self._addLogMessage(data.logMessage); // add log message
|
||||
self.$status.innerHTML = data.statusMessage; // show current update state
|
||||
self.ProgressBar.SetProgress(data.percent); // set progress bar percent
|
||||
self.trigger('onProgress', data);
|
||||
},
|
||||
onStart: function () {
|
||||
echo('CUpdateModal.Update: onStart listener');
|
||||
if ( self.selectBox ) {
|
||||
console.log(self.selectBox);
|
||||
self.selectBox.disabled = true;
|
||||
}
|
||||
self.UpdateStart();
|
||||
CModalBox.prototype.Show.call(self, true);
|
||||
}
|
||||
});
|
||||
|
||||
if ( this.log === true ) { // check if log activated
|
||||
this.log = new CLog(document.body, {
|
||||
autofocus: true,
|
||||
time: true,
|
||||
newest: true,
|
||||
defaultType: 'success',
|
||||
isVisible: false,
|
||||
events: {
|
||||
onShow: function () {
|
||||
self.$status.parentNode.style.display = 'none';
|
||||
},
|
||||
onHide: function () {
|
||||
self.$status.parentNode.style.display = '';
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.curr_info = this.Update.GetCurrentImageInfo(); // get info about current image
|
||||
|
||||
if ( typeof this.update_url === 'string' ) { // if update_url specified in params
|
||||
this.check = true; // always need check that url
|
||||
} else if ( Array.isArray(this.images) ) { // if specified images array
|
||||
this.check = false; // check url not necessary
|
||||
this.image_description = cutTextWithEllipsis(this.images[0].descr, 200);
|
||||
this.image_date = new Date(this.images[0].date);
|
||||
if ( this.select === true && this.images.length > 1 ) {
|
||||
this.selectBox = new CSelectBox(this, {
|
||||
parent: this.new_version = element('div'),
|
||||
data: this.images,
|
||||
nameField: 'title',
|
||||
idField: 'url',
|
||||
events: {
|
||||
onChange: function () {
|
||||
self.$curr_update_descr.innerHTML = cutTextWithEllipsis(this.GetSelected().descr, 200);
|
||||
self.update_url = this.GetValue();
|
||||
self.$curr_update_date.innerHTML = new Date(this.GetSelected().date).toDateString() + ' ' + new Date(this.GetSelected().date).toLocaleTimeString();
|
||||
}
|
||||
}
|
||||
});
|
||||
this.update_url = this.selectBox.GetValue();
|
||||
} else { // if select not specified or images.length = 1
|
||||
this.new_version = this.images[0].title; // select component not needed
|
||||
this.new_version_date = new Date(this.images[0].date);
|
||||
this.update_url = this.images[0].url;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* If log component exist add message
|
||||
* @param {string} message log message
|
||||
* @param {string} type message type
|
||||
* @private
|
||||
*/
|
||||
CUpdateModal.prototype._addLogMessage = function ( message, type ) {
|
||||
if ( this.log !== undefined ) { this.log.Add(message, type); }
|
||||
};
|
||||
|
||||
/**
|
||||
* If log component exist adds it in elements array
|
||||
* @param {Array} elements that will be displayed on the page
|
||||
* @private
|
||||
*/
|
||||
CUpdateModal.prototype._addLog = function ( elements ) {
|
||||
var self = this;
|
||||
if ( typeof this.log !== 'undefined' ) {
|
||||
elements.push(
|
||||
element('tr', {className: 'row'}, [element('td', {className: 'log', colSpan: 2}, this.log.handle)])
|
||||
);
|
||||
this.Info_func = function () {
|
||||
self.log.Show(!self.log.isVisible);
|
||||
this.bpanel.Rename(this.Info_btn, self.log.isVisible ? _('Hide log') : _('Show log'));
|
||||
self.content.querySelector('table').classList.toggle('hidden');
|
||||
};
|
||||
this.bpanel.Rename(this.Info_btn, self.log.isVisible ? _('Hide log') : _('Show log'));
|
||||
this.bpanel.Hidden(this.Info_btn, false);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Manage the window visibility
|
||||
* @param {boolean} show show or hide window
|
||||
*/
|
||||
CUpdateModal.prototype.Show = function ( show ) {
|
||||
echo('CUpdateModal.prototype.Show:show? ' + show + ', auto? ' + this.auto + ', check? ' + this.check);
|
||||
if ( show !== false ) {
|
||||
if ( this.auto === true ) {
|
||||
this.Update.Start(this.update_url);
|
||||
return;
|
||||
}
|
||||
if ( this.check === true ) {
|
||||
this.CheckStatus();
|
||||
} else {
|
||||
this.ImageSelect();
|
||||
}
|
||||
if ( this.log && this.log.isVisible ) {
|
||||
this.log.Show(false);
|
||||
}
|
||||
}
|
||||
CModalBox.prototype.Show.call(this, show);
|
||||
};
|
||||
|
||||
/**
|
||||
* Show check status content in the update window
|
||||
*/
|
||||
CUpdateModal.prototype.CheckStatus = function () {
|
||||
echo('CUpdateModal.CheckStatus');
|
||||
this.layer = this.CheckStatus;
|
||||
var self = this,
|
||||
elements = [
|
||||
element('tr', {className: 'row'},
|
||||
[
|
||||
element('td', {className: 'left'}, _('Status') + ':'),
|
||||
this.$status = element('td', {className: 'right'})
|
||||
])
|
||||
];
|
||||
this._addLog(elements);
|
||||
this.SetHeader(_('Check image'));
|
||||
this.SetContent(element('table', {className: 'cmodal-update status'}, elements));
|
||||
this.F1_func = function () {
|
||||
self.$status.innerHtml = status;
|
||||
self.$status.className = 'right';
|
||||
self.bpanel.Hidden(this.OK_btn, true);
|
||||
self.bpanel.Rename(this.Exit_btn, _('Cancel'));
|
||||
self.Update.CheckUpdate(self.update_url);
|
||||
self.trigger('onCheckStatus');
|
||||
};
|
||||
self.Update.CheckUpdate(self.update_url);
|
||||
};
|
||||
|
||||
/**
|
||||
* Show image select content in the update window
|
||||
*/
|
||||
CUpdateModal.prototype.ImageSelect = function () {
|
||||
this.layer = this.ImageSelect;
|
||||
var self = this, $new_version,
|
||||
elements = [
|
||||
element('tr', {className: 'row info'},
|
||||
[
|
||||
element('td', {className: 'left'}, _('Current version') + ':'),
|
||||
element('td', {className: 'right', innerHTML: this.curr_info.version})
|
||||
]),
|
||||
element('tr', {className: 'row info'},
|
||||
[
|
||||
element('td', {className: 'left'}, _('Description') + ':'),
|
||||
element('td', {className: 'right'}, element('div', {className:'ellipsis', innerHTML: this.curr_info.description || ''}))
|
||||
]),
|
||||
element('tr', {className: 'row info'},
|
||||
[
|
||||
element('td', {className: 'left'}, _('Date') + ':'),
|
||||
element('td', {
|
||||
className: 'right',
|
||||
innerHTML: isNaN(this.curr_info.date) ? 'n/a' : this.curr_info.date.toDateString() + ' ' + this.curr_info.date.toLocaleTimeString()
|
||||
})
|
||||
]),
|
||||
element('tr', {className: 'row padding info'},
|
||||
[
|
||||
element('td', {className: 'left'}, _('New version') + ':'),
|
||||
element('td', {className: 'right'}, $new_version = element('div'))
|
||||
]),
|
||||
element('tr', {className: 'row info'},
|
||||
[
|
||||
element('td', {className: 'left'}, _('Description') + ':'),
|
||||
element('td', {className: 'right'}, self.$curr_update_descr = element('div', {
|
||||
className: 'ellipsis',
|
||||
innerHTML: this.image_description || ''
|
||||
}))
|
||||
]),
|
||||
element('tr', {className: 'row info'},
|
||||
[
|
||||
element('td', {className: 'left'}, _('Date') + ':'),
|
||||
self.$curr_update_date = element('td', {
|
||||
className: 'right',
|
||||
innerHTML: this.image_date.toDateString() + ' ' + this.image_date.toLocaleTimeString()
|
||||
})
|
||||
])
|
||||
];
|
||||
|
||||
if ( this.warning !== false ) {
|
||||
elements.unshift(element('tr', {className: 'row info'},
|
||||
[
|
||||
element('td', {
|
||||
className: 'center',
|
||||
colSpan: 2,
|
||||
innerHTML: _('<span class=\"alert\">Warning!</span> Device will be rebooted after update')
|
||||
})
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
this.bpanel.Hidden(this.Info_btn, this.info !== true); // hide info button if necessary
|
||||
|
||||
this._addLog(elements);
|
||||
|
||||
this.SetContent(element('table', {className: 'cmodal-update'}, elements));
|
||||
|
||||
if ( this.new_version !== undefined ) {
|
||||
if ( typeof this.new_version === 'string' ) {
|
||||
$new_version.innerHTML = this.new_version;
|
||||
self.$curr_update_date.innerHTML = this.new_version_date.toDateString() + ' ' + this.new_version_date.toLocaleTimeString();
|
||||
} else {
|
||||
elchild($new_version, this.new_version);
|
||||
}
|
||||
} else {
|
||||
$new_version.innerHTML = this.image_info.version;
|
||||
self.$curr_update_date.innerHTML = this.image_info.date.toDateString() + ' ' + this.image_info.date.toLocaleTimeString();
|
||||
}
|
||||
|
||||
// Set header
|
||||
this.SetHeader(this.header_text || '');
|
||||
|
||||
if ( this.info === true ) {
|
||||
this.Info_func = function () {
|
||||
self.ShowInfoWindow();
|
||||
};
|
||||
this.bpanel.Rename(this.Info_btn, _('Change log'));
|
||||
}
|
||||
|
||||
this.F1_func = function () {
|
||||
self.Update.Start(this.update_url);
|
||||
};
|
||||
|
||||
this.bpanel.Hidden(this.OK_btn, false);
|
||||
this.bpanel.Hidden(this.Exit_btn, false);
|
||||
|
||||
if ( this._binded !== true ) {
|
||||
this.bind('onShow', function () {
|
||||
if ( this.layer === this.ImageSelect && this.selectBox && this.selectBox.focus ) {
|
||||
this.selectBox.focus();
|
||||
}
|
||||
});
|
||||
this._binded = true;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Show elements after update start
|
||||
*/
|
||||
CUpdateModal.prototype.UpdateStart = function () {
|
||||
this.layer = this.UpdateStart;
|
||||
var self = this,
|
||||
elements = [
|
||||
element('tr', {className: 'row info'},
|
||||
[
|
||||
element('td', {
|
||||
className: 'center',
|
||||
colSpan: 2,
|
||||
innerHTML: _('<span class=\"alert\">Warning!</span> Device will be rebooted after update')
|
||||
})
|
||||
]),
|
||||
element('tr', {className: 'row info'},
|
||||
[
|
||||
element('td', {className: 'left'}, _('Current version') + ':'),
|
||||
element('td', {className: 'right', innerHTML: this.curr_info.version})
|
||||
]),
|
||||
element('tr', {className: 'row info'},
|
||||
[
|
||||
element('td', {className: 'left'}, _('Description') + ':'),
|
||||
element('td', {className: 'right'}, element('div', {
|
||||
className: 'ellipsis',
|
||||
innerHTML: this.curr_info.description || ''
|
||||
}))
|
||||
]),
|
||||
element('tr', {className: 'row info'},
|
||||
[
|
||||
element('td', {className: 'left'}, _('Date') + ':'),
|
||||
element('td', {
|
||||
className: 'right',
|
||||
innerHTML: isNaN(this.curr_info.date) ? 'n/a' : this.curr_info.date.toDateString() + ' ' + this.curr_info.date.toLocaleTimeString()
|
||||
})
|
||||
]),
|
||||
element('tr', {className: 'row info padding'},
|
||||
[
|
||||
element('td', {className: 'left'}, _('New version') + ':'),
|
||||
element('td', {className: 'right', innerHTML: this.image_info.version})
|
||||
]),
|
||||
element('tr', {className: 'row info'},
|
||||
[
|
||||
element('td', {className: 'left'}, _('Description') + ':'),
|
||||
element('td', {className: 'right'}, self.$curr_update_descr = element('div', {
|
||||
className: 'ellipsis',
|
||||
innerHTML: this.image_description || ''
|
||||
}))
|
||||
]),
|
||||
element('tr', {className: 'row info'},
|
||||
[
|
||||
element('td', {className: 'left'}, _('Date') + ':'),
|
||||
element('td', {
|
||||
className: 'right',
|
||||
innerHTML: this.image_info.date.toDateString() + ' ' + this.image_info.date.toLocaleTimeString()
|
||||
})
|
||||
]),
|
||||
element('tr', {className: 'row padding'},
|
||||
[
|
||||
element('td', {className: 'left'}, _('Update status') + ':'),
|
||||
this.$status
|
||||
])
|
||||
];
|
||||
|
||||
this.bpanel.Hidden(this.Info_btn, true); // hide info button
|
||||
this.bpanel.Hidden(self.Exit_btn, true); // hide Exit button
|
||||
|
||||
this._addLog(elements);
|
||||
|
||||
elements.push(
|
||||
element('tr', {className: 'row'},
|
||||
[
|
||||
element('td', {className: 'center', colSpan: 2}, this.ProgressBar.handle)
|
||||
])
|
||||
);
|
||||
|
||||
this.SetContent(element('table', {className: 'cmodal-update'}, elements));
|
||||
this.bpanel.Hidden(this.OK_btn, true);
|
||||
this.bpanel.Hidden(this.Exit_btn, true);
|
||||
if ( self.log.isVisible ) {
|
||||
self.content.querySelector('table').classList.add('hidden');
|
||||
} else {
|
||||
self.content.querySelector('table').classList.remove('hidden');
|
||||
}
|
||||
|
||||
this.trigger('onShowUpdateStart');
|
||||
|
||||
this.F1_func = function () {
|
||||
self.Update.Start(self.update_url, self.$status.classList.contains('error')); // if error then force restart
|
||||
self.$status.innerHtml = '';
|
||||
self.$status.classList.remove('error');
|
||||
self.bpanel.Hidden(this.OK_btn, true);
|
||||
self.bpanel.Hidden(this.Exit_btn, true);
|
||||
};
|
||||
};
|
||||
|
||||
CUpdateModal.prototype.ShowInfoWindow = function () {
|
||||
var modal = new CModalBox(this), $content,
|
||||
self = this;
|
||||
modal.SetHeader(_('Change log'));
|
||||
modal.baseClass = 'cmodal-main image-info-modal';
|
||||
modal.name = 'CModalImageInfo';
|
||||
modal.bpanel = new CButtonPanel();
|
||||
modal.bpanel.Init(MODAL_IMG_PATH);
|
||||
modal.bpanel.Add(KEYS.EXIT, 'exit.png', _('Close'), function () {
|
||||
modal.Show(false);
|
||||
modal.Free();
|
||||
});
|
||||
modal.EventHandler = function ( event ) {
|
||||
switch ( event.code ) {
|
||||
case KEYS.UP:
|
||||
$content.parentNode.scrollByLines(-2);
|
||||
break;
|
||||
case KEYS.DOWN:
|
||||
$content.parentNode.scrollByLines(2);
|
||||
break;
|
||||
case KEYS.PAGE_DOWN:
|
||||
$content.parentNode.scrollByPages(1);
|
||||
break;
|
||||
case KEYS.PAGE_UP:
|
||||
$content.parentNode.scrollByPages(-1);
|
||||
break;
|
||||
}
|
||||
modal.bpanel.EventHandler(event);
|
||||
};
|
||||
modal.SetFooter(modal.bpanel.handle);
|
||||
modal.SetContent(element('div', {className: 'image-info-content'}, $content = element('div', {className: 'info'}, _('Information not found'))));
|
||||
modal.Init();
|
||||
if ( this.image_info[this.update_url] === undefined && this.update_url ) {
|
||||
var name = this.update_url.split('/').pop();
|
||||
ajax('GET', this.update_url.replace(name, '') + 'info/pub/get.php?name=' + name + '&lang=' + getCurrentLanguage(), function ( text, status ) {
|
||||
if ( status === 200 ) {
|
||||
self.image_info[self.update_url] = text;
|
||||
} else {
|
||||
self.image_info[self.update_url] = _('Information not found');
|
||||
}
|
||||
$content.innerHTML = self.image_info[self.update_url];
|
||||
modal.Show(true);
|
||||
$content.focus();
|
||||
});
|
||||
return;
|
||||
}
|
||||
$content.innerHTML = self.image_info[self.update_url];
|
||||
modal.Show(true);
|
||||
$content.focus();
|
||||
};
|
||||
|
||||
Events.inject(CUpdateModal);
|
||||
|
|
@ -0,0 +1,312 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* @param parent
|
||||
* @param parameters
|
||||
* @constructor
|
||||
* @sample
|
||||
* window.webfilter = new CWebInput(document.body, {
|
||||
* input: document.getElementById("web-filter"),
|
||||
* hint: "Enter url or text to search",
|
||||
* events: {
|
||||
* onEnter: function(value, type){
|
||||
* console.log("Value: " + value);
|
||||
* console.log("Type: " + type);
|
||||
* }
|
||||
* },
|
||||
* autocomplete: true
|
||||
* });
|
||||
*/
|
||||
function CWebInput ( parent, parameters ) {
|
||||
var self = this, ac_attrs;
|
||||
parameters = extend({
|
||||
style: "web-input main-filter-input",
|
||||
icons: [
|
||||
{
|
||||
name : "ico_search",
|
||||
type : "left",
|
||||
style: "black_search left"
|
||||
},
|
||||
{
|
||||
name : "ico_web",
|
||||
type : "left",
|
||||
style: "web-ico left"
|
||||
},
|
||||
{
|
||||
name : "ico_load",
|
||||
type : "left",
|
||||
style: "load-ico left",
|
||||
src: "ico_waiting2.png"
|
||||
},
|
||||
{
|
||||
name : "site_favicon",
|
||||
type : "left",
|
||||
style: "favicon left",
|
||||
src: '',
|
||||
attributes: {
|
||||
onload: function() {
|
||||
this.loaded = true;
|
||||
},
|
||||
onerror: function() {
|
||||
this.loaded = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name : "ico_star_fade",
|
||||
click: function ( event ) {
|
||||
this.FillStar(true);
|
||||
this.trigger("onStar");
|
||||
event.stopPropagation();
|
||||
},
|
||||
style: "fade-star"
|
||||
},
|
||||
{
|
||||
name : "ico_star_full",
|
||||
click: function ( event ) {
|
||||
this.FillStar(false);
|
||||
this.trigger("onUnstar");
|
||||
event.stopPropagation();
|
||||
},
|
||||
style: "full-star"
|
||||
},
|
||||
{
|
||||
name : "f4",
|
||||
click: function ( event ) {
|
||||
this.trigger("onKey");
|
||||
event.stopPropagation();
|
||||
}
|
||||
}
|
||||
]
|
||||
}, parameters);
|
||||
|
||||
if (parameters.parent !== undefined){
|
||||
elchild(parameters.parent, parameters.parent = element('div', {className: "cweb-input-wrapper"}));
|
||||
}else if (parameters.input !== undefined){
|
||||
elchild(parameters.input.parentNode, parameters.parent = element('div', {className: "cweb-input-wrapper"}, parameters.input));
|
||||
}
|
||||
|
||||
CInput.call(this, parent || null, parameters);
|
||||
|
||||
this.name = "CWebInput";
|
||||
|
||||
if ( parameters.stared ) {
|
||||
this.FillStar(true);
|
||||
}
|
||||
|
||||
if ( typeof parameters.autocomplete === "object" || parameters.autocomplete === true ) {
|
||||
if ( parameters.autocomplete === true )
|
||||
parameters.autocomplete = {};
|
||||
ac_attrs = extend(CWebInput.autocomplete_defaults, parameters.autocomplete, true);
|
||||
|
||||
// if there is no function for getting data in parameters then assign default value
|
||||
if ( ac_attrs.data === undefined )
|
||||
ac_attrs.data = function ( res ) {
|
||||
var data = null,
|
||||
result = [];
|
||||
|
||||
if ( res ) {
|
||||
try {
|
||||
// format data for better look
|
||||
data = JSON.parse(unescape(res))[1];
|
||||
|
||||
data.sort(function( a ){
|
||||
return a[0].indexOf(self.GetValue()) === 0 ? -1 : 0;
|
||||
});
|
||||
|
||||
if ( data ) {
|
||||
data.forEach(function ( el ) {
|
||||
result.push({
|
||||
title: el[0],
|
||||
hint: _("Search in Google") // add hints for google data
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if ( validateUrl(self.GetValue()) ) // if valid link entered add appropriate item to the beginning of the list
|
||||
result.unshift({title: self.GetValue(), type: 'url', hint: _("Open link")});
|
||||
else if (result[0] === undefined || result[0].title.indexOf(self.GetValue()) === -1)
|
||||
result.unshift({title: self.GetValue(), type: 'search', hint: _("Search in Google")});
|
||||
} catch ( error ) {
|
||||
echo(error);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
ac_attrs.base = this.handle;
|
||||
ac_attrs.input = this.input;
|
||||
if (ac_attrs.events === undefined) ac_attrs.events = {};
|
||||
|
||||
parameters.autocomplete = new CAutocomplete(document.body, ac_attrs);
|
||||
}
|
||||
|
||||
this.SetAutocomplete(parameters.autocomplete);
|
||||
|
||||
this.SetState('search');
|
||||
|
||||
elchild(parameters.parent, this.$progress_bar = element('div', {className: "progress-bar"}));
|
||||
|
||||
this.input.oninput = (function(oninput){
|
||||
return function(event){
|
||||
if (self.GetValue() === '')
|
||||
self.SetState('search');
|
||||
if (typeof oninput === 'function') {
|
||||
oninput(event);
|
||||
}
|
||||
}
|
||||
})(this.input.oninput);
|
||||
}
|
||||
|
||||
// extending
|
||||
CWebInput.prototype = Object.create(CInput.prototype);
|
||||
CWebInput.prototype.constructor = CWebInput;
|
||||
|
||||
CWebInput.prototype.SetFavicon = function ( url ){
|
||||
url = parseUri(url);
|
||||
this.icons['site_favicon'].src = url.protocol + "://" + url.authority + "/favicon.ico";
|
||||
};
|
||||
|
||||
CWebInput.prototype.SetAutocomplete = function ( autocomplete ){
|
||||
CInput.prototype.SetAutocomplete.call(this, autocomplete);
|
||||
var title, length, def, self = this;
|
||||
if (typeof autocomplete === 'function' || typeof autocomplete === "object"){
|
||||
this.autocomplete.bind({
|
||||
onChange: function(){
|
||||
title = this.GetValue();
|
||||
def = this.GetDefault();
|
||||
self.SetValue(title, true);
|
||||
if (title.indexOf(def) === 0){
|
||||
length = def.length;
|
||||
self.SetValue(title, true);
|
||||
self.input.selectionStart = length;
|
||||
self.input.selectionEnd = title.length;
|
||||
}
|
||||
self.ShowHint(false);
|
||||
},
|
||||
onEnter: function ( data ){
|
||||
self.trigger("onEnter", data);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Меняет тип инпута ( изменяет левую иконку )
|
||||
* @param {string} type Тип инпута
|
||||
*/
|
||||
CWebInput.prototype.SetState = function ( type ){
|
||||
var self = this;
|
||||
if (CWebInput.states.indexOf(type) === -1) return;
|
||||
CWebInput.states.forEach(function(type){
|
||||
self.handle.classList.remove(type);
|
||||
});
|
||||
this.type = type ||CWebInput.states[0];
|
||||
this.handle.classList.add(type);
|
||||
};
|
||||
|
||||
// Все возможные типы которые можно назначить инпуту
|
||||
CWebInput.states = ['search', 'web', 'load', 'favicon'];
|
||||
|
||||
// Параметры поумолчания для автодополнения
|
||||
CWebInput.autocomplete_defaults = {
|
||||
titleField: "title",
|
||||
hintField: "hint",
|
||||
typeField: "type",
|
||||
size: 5,
|
||||
/**
|
||||
* @tutorial
|
||||
*
|
||||
* If URI will changed and autocomplete stops working, try to use this algorithm to fix:
|
||||
* - open www.google.com
|
||||
* - open preferred network sniffing tool (Wireshark, "Network" tab in Firefox/Chrome Developer tools, ...)
|
||||
* - type something in search field and look at requested URI
|
||||
*/
|
||||
url: "https://www.google.com/complete/search?client=psy-ab&hl=uk&gs_rn=64&gs_ri=psy-ab&cp=1&gs_id=1tc&q="
|
||||
};
|
||||
|
||||
CWebInput.prototype.FillStar = function ( fill ){
|
||||
this.favorite = fill;
|
||||
if (fill === true)
|
||||
this.handle.classList.add("favorite");
|
||||
else
|
||||
this.handle.classList.remove("favorite");
|
||||
};
|
||||
|
||||
CWebInput.prototype.ShowStar = function ( show ){
|
||||
if (show === true)
|
||||
this.handle.classList.add("star");
|
||||
else
|
||||
this.handle.classList.remove("star");
|
||||
};
|
||||
|
||||
CWebInput.prototype.ShowFavIcon = function () {
|
||||
if (this.icons["site_favicon"].loaded === true){
|
||||
this.SetState("favicon");
|
||||
}else{
|
||||
this.SetState("web");
|
||||
}
|
||||
};
|
||||
|
||||
CWebInput.prototype.SetProgress = function ( progress ) {
|
||||
if (progress !== undefined){
|
||||
this.$progress_bar.style.width = progress + "%";
|
||||
}else{
|
||||
this.$progress_bar.style.width = "0px";
|
||||
}
|
||||
};
|
||||
|
||||
// true - stop next events
|
||||
/**
|
||||
* @return {boolean}
|
||||
*/
|
||||
CWebInput.prototype.EventHandler = function ( event ) {
|
||||
eventPrepare(event, true, 'CWebInput');
|
||||
var self = this,
|
||||
event_res = false;
|
||||
if ( this.trigger("onEvent", event)[0] === true ) return true;
|
||||
switch ( event.code ) {
|
||||
case KEYS.F4:
|
||||
if ( this.IsFocused() ){
|
||||
this.blur();
|
||||
}else{
|
||||
this.focus();
|
||||
}
|
||||
event_res = this.trigger("onKey")[0];
|
||||
return true;
|
||||
case KEYS.EXIT:
|
||||
if (this.autocomplete && this.autocomplete.isVisible === true){
|
||||
this.autocomplete.Show(false);
|
||||
return true;
|
||||
}
|
||||
event_res = this.trigger("onExit")[0];
|
||||
return true;
|
||||
case KEYS.OK:
|
||||
if ( this.IsFocused() ){
|
||||
gSTB.HideVirtualKeyboard();
|
||||
if (this.autocomplete.isVisible === true){
|
||||
if (typeof this.autocomplete.EventHandler === 'function') {
|
||||
this.autocomplete.EventHandler(event);
|
||||
}
|
||||
}else{
|
||||
this.autocomplete.Abort();
|
||||
echo("CWebInput:onEnter");
|
||||
this.trigger("onEnter", {text: self.GetValue(), type: validateUrl(self.GetValue()) ? 'url' : 'search'});
|
||||
this.autocomplete.Clear();
|
||||
}
|
||||
event.preventDefault();
|
||||
return true;
|
||||
}
|
||||
case KEYS.RIGHT:
|
||||
if ( this.input.selectionStart !== this.input.selectionEnd) {
|
||||
this.input.oninput();
|
||||
}
|
||||
case KEYS.UP:
|
||||
case KEYS.DOWN:
|
||||
if (this.autocomplete && typeof this.autocomplete.EventHandler === 'function')
|
||||
this.autocomplete.EventHandler(event);
|
||||
return true;
|
||||
}
|
||||
if ( event_res === true ) return true;
|
||||
};
|
|
@ -0,0 +1,256 @@
|
|||
/**
|
||||
* Debug instruments
|
||||
* @author igork
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Dumps the given data (json format), its type and optional title to console
|
||||
* @param data mixed value to be printed
|
||||
* @param {string} [title] optional string for additional info
|
||||
*/
|
||||
var echo = function ( data, title ) {
|
||||
if ( EMULATION ) { console.log(title ? title : 'log:', data); }
|
||||
// console colors
|
||||
var red = '\u001b[31m',
|
||||
bold = '\u001b[1m',
|
||||
cyan = '\u001b[36m',
|
||||
green = '\u001b[32m',
|
||||
reset = '\u001b[0m';
|
||||
// info of the var type
|
||||
var type = Object.prototype.toString.call(data).match(/\s([a-z|A-Z]+)/)[1];
|
||||
// add custom colors (red for errors)
|
||||
if ( type === 'Error' ) {
|
||||
type = red + type + reset;
|
||||
} else {
|
||||
type = green + type + reset;
|
||||
}
|
||||
// prepare
|
||||
if ( data instanceof Object || Array.isArray(data) ) {
|
||||
// complex object
|
||||
data = data.nodeName ? data.outerHTML : JSON.stringify(data, null, 4);
|
||||
}
|
||||
title = title || '';
|
||||
// combine all together and print result
|
||||
gSTB.Debug('[' + type + ']\t' + (title ? bold + title + green + ':\t' + reset : '') + data);
|
||||
// ok
|
||||
return data;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Reloads all css files found on the current page
|
||||
* uses timestamp to make links unique
|
||||
*/
|
||||
function reloadStyles () {
|
||||
var time = +new Date();
|
||||
// get through all css links
|
||||
Array.prototype.slice.call(document.head.getElementsByTagName('link')).forEach(function(tag){
|
||||
// get base name, modify and apply
|
||||
tag.href = tag.href.split('?_')[0] + '?_=' + time;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reload all scripts on page
|
||||
* @param {Array} [filter] Array of strings with parts of the urls scripts which should be exclude from reload
|
||||
*/
|
||||
function reloadAllScripts ( filter ) {
|
||||
var scripts = document.head.getElementsByTagName('script'),
|
||||
filtered = false,
|
||||
script = null,
|
||||
src = '';
|
||||
for ( var i = 0; i < scripts.length; i++ ) {
|
||||
script = scripts[i];
|
||||
filtered = false;
|
||||
src = undefined;
|
||||
if ( script.type === 'text/javascript' && script.attributes.src !== undefined ) {
|
||||
if ( !filtered ) {
|
||||
src = script.src;
|
||||
script.parentNode.removeChild(script);
|
||||
if ( src.indexOf('?_') !== -1 ) {
|
||||
src = src.substr(0, src.indexOf('?_'));
|
||||
}
|
||||
loadScript(src);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reload scripts
|
||||
* @param {string} src Part of the script url
|
||||
*/
|
||||
function reloadScripts ( src ) {
|
||||
var scripts = document.head.getElementsByTagName('script'),
|
||||
curr_time = (new Date()).getTime();
|
||||
for ( var i = 0, script = null, src = ''; i < scripts.length; i++ ) {
|
||||
script = scripts[i];
|
||||
if ( script.type === 'text/javascript' && script.src.indexOf(src) !== -1 ) {
|
||||
src = script.src + curr_time;
|
||||
script.parentNode.removeChild(script);
|
||||
loadScript(src);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reload page with cache ignore
|
||||
*/
|
||||
function reloadPage () {
|
||||
gSTB.Stop();
|
||||
window.location.reload(true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Impose image to the screen
|
||||
* @param {string} image full url of image
|
||||
* @param {number} [opacity=0.5] opacity level of impose image
|
||||
*/
|
||||
function imposeImageToScreen(image, opacity){
|
||||
// prepare image
|
||||
imposeImageToScreen.image = element('div');
|
||||
imposeImageToScreen.image.style.width = WINDOW_WIDTH + 'px';
|
||||
imposeImageToScreen.image.style.height = WINDOW_HEIGHT + 'px';
|
||||
imposeImageToScreen.image.style.position = 'absolute';
|
||||
imposeImageToScreen.image.style.background = 'url("' + image + '") no-repeat';
|
||||
imposeImageToScreen.image.style.opacity = opacity || 1;
|
||||
imposeImageToScreen.image.style.zIndex = 1000;
|
||||
// add to DOM
|
||||
document.body.appendChild(imposeImageToScreen.image);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Monitors all focus changes and dumps focused elements
|
||||
*/
|
||||
function focusTracker () {
|
||||
// state inversion
|
||||
focusTracker.state = !focusTracker.state;
|
||||
|
||||
if ( focusTracker.state ) {
|
||||
// start
|
||||
console.log('focus tracking: started');
|
||||
// backup
|
||||
focusTracker.focus = Element.prototype.focus;
|
||||
focusTracker.blur = Element.prototype.blur;
|
||||
focusTracker.select = HTMLInputElement.prototype.select;
|
||||
// rewrite
|
||||
Element.prototype.focus = function(){
|
||||
console.log('focus', this);
|
||||
// invoke the old native one
|
||||
focusTracker.focus.call(this);
|
||||
};
|
||||
Element.prototype.blur = function(){
|
||||
console.log('blur', this);
|
||||
// invoke the old native one
|
||||
focusTracker.blur.call(this);
|
||||
};
|
||||
HTMLInputElement.prototype.select = function(){
|
||||
console.log('select', this);
|
||||
// invoke the old native one
|
||||
focusTracker.select.call(this);
|
||||
};
|
||||
} else {
|
||||
// stop
|
||||
console.log('focus tracking: stopped');
|
||||
// restore
|
||||
Element.prototype.focus = focusTracker.focus;
|
||||
Element.prototype.blur = focusTracker.blur;
|
||||
HTMLInputElement.prototype.select = focusTracker.select;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set shortcuts for some debug tools
|
||||
* Numpad 1 - reload page ignore caching
|
||||
* Numpad 2 - reload styles
|
||||
* Numpad 3 - reload all scripts on page
|
||||
*/
|
||||
(function(){
|
||||
window.addEventListener('keydown', function developEventListenerKeydown ( event ) {
|
||||
switch ( event.code ) {
|
||||
case 97: // numpad 1
|
||||
echo('reload page');
|
||||
reloadPage();
|
||||
break;
|
||||
case 98: // numpad 2
|
||||
echo('reload CSS');
|
||||
reloadStyles();
|
||||
break;
|
||||
case 99: // numpad 3
|
||||
reloadAllScripts(['target-script-min.js']);
|
||||
break;
|
||||
case 100: // numpad 4
|
||||
echo('toggle grid');
|
||||
// toggle visibility
|
||||
if ( imposeImageToScreen.image ) {
|
||||
// clear
|
||||
document.body.removeChild(imposeImageToScreen.image);
|
||||
delete imposeImageToScreen.image;
|
||||
} else {
|
||||
// add
|
||||
imposeImageToScreen(PATH_SYSTEM + 'grid.' + WINDOW_HEIGHT + '.png');
|
||||
}
|
||||
break;
|
||||
case 103: // numpad 7
|
||||
// SpyJS enable/disable
|
||||
if ( stbStorage.getItem('spyjs.active') !== '1' ) {
|
||||
// suppose proxy is ready
|
||||
//isSpyJs = true;
|
||||
stbStorage.setItem('spyjs.active', 1);
|
||||
console.log('SpyJS: enable');
|
||||
console.log('SpyJS: set proxy to ' + location.hostname + ':' + 3546);
|
||||
|
||||
gSTB.SetWebProxy(location.hostname, 3546, '', '', '');
|
||||
location.reload();
|
||||
} else {
|
||||
//isSpyJs = false;
|
||||
stbStorage.setItem('spyjs.active', 0);
|
||||
gSTB.ResetWebProxy();
|
||||
console.log('SpyJS: disable');
|
||||
location.reload();
|
||||
}
|
||||
break;
|
||||
case 104: // numpad 8
|
||||
// toggle tracking
|
||||
focusTracker();
|
||||
break;
|
||||
case 220: // Print Screen
|
||||
echo('\n\n\n<html><head>\n' + document.head.innerHTML + '\n</head>\n<body>\n' + document.body.innerHTML + '\n</body>\n</html>\n');
|
||||
break;
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
|
||||
if ( !Function.prototype.bind ) {
|
||||
Function.prototype.bind = function ( oThis ) {
|
||||
if ( typeof this !== 'function' ) {
|
||||
// closest thing possible to the ECMAScript 5
|
||||
// internal IsCallable function
|
||||
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
|
||||
}
|
||||
|
||||
var aArgs = Array.prototype.slice.call(arguments, 1),
|
||||
fToBind = this,
|
||||
fNOP = function () {},
|
||||
fBound = function () {
|
||||
return fToBind.apply(this instanceof fNOP && oThis
|
||||
? this
|
||||
: oThis,
|
||||
aArgs.concat(Array.prototype.slice.call(arguments)));
|
||||
};
|
||||
|
||||
fNOP.prototype = this.prototype;
|
||||
fBound.prototype = new fNOP();
|
||||
|
||||
return fBound;
|
||||
};
|
||||
}
|
|
@ -0,0 +1,237 @@
|
|||
/**
|
||||
* @module stb-emitter
|
||||
*
|
||||
* @author Stanislav Kalashnik <sk@infomir.eu>
|
||||
* @license GNU GENERAL PUBLIC LICENSE Version 3
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
|
||||
/**
|
||||
* Base Events Emitter implementation.
|
||||
*
|
||||
* @see http://nodejs.org/api/events.html
|
||||
* @constructor
|
||||
*
|
||||
* @example
|
||||
* var emitter = new Emitter();
|
||||
*/
|
||||
function Emitter () {
|
||||
//if ( DEBUG ) {
|
||||
// if ( typeof this !== 'object' ) { throw new Error(__filename + ': must be constructed via new'); }
|
||||
//}
|
||||
|
||||
/**
|
||||
* Inner hash table for event names and linked callbacks.
|
||||
* Manual editing should be avoided.
|
||||
*
|
||||
* @member {Object.<string, function[]>}
|
||||
*
|
||||
* @example
|
||||
* {
|
||||
* click: [
|
||||
* function click1 () { ... },
|
||||
* function click2 () { ... }
|
||||
* ],
|
||||
* keydown: [
|
||||
* function () { ... }
|
||||
* ]
|
||||
* }
|
||||
**/
|
||||
this.events = {};
|
||||
}
|
||||
|
||||
|
||||
Emitter.prototype = {
|
||||
/**
|
||||
* Bind an event to the given callback function.
|
||||
* The same callback function can be added multiple times for the same event name.
|
||||
*
|
||||
* @param {string} name event identifier
|
||||
* @param {function} callback function to call on this event
|
||||
*
|
||||
* @example
|
||||
* emitter.addListener('click', function ( data ) { ... });
|
||||
* // one more click handler
|
||||
* emitter.addListener('click', function ( data ) { ... });
|
||||
*/
|
||||
addListener: function ( name, callback ) {
|
||||
//if ( DEBUG ) {
|
||||
// if ( arguments.length !== 2 ) { throw new Error(__filename + ': wrong arguments number'); }
|
||||
// if ( typeof name !== 'string' || name.length === 0 ) { throw new Error(__filename + ': wrong or empty name'); }
|
||||
// if ( typeof callback !== 'function' ) { throw new Error(__filename + ': wrong callback type'); }
|
||||
//}
|
||||
|
||||
// initialization may be required
|
||||
this.events[name] = this.events[name] || [];
|
||||
// append this new event to the list
|
||||
this.events[name].push(callback);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Add a one time listener for the event.
|
||||
* This listener is invoked only the next time the event is fired, after which it is removed.
|
||||
*
|
||||
* @param {string} name event identifier
|
||||
* @param {function} callback function to call on this event
|
||||
*
|
||||
* @example
|
||||
* emitter.once('click', function ( data ) { ... });
|
||||
*/
|
||||
once: function ( name, callback ) {
|
||||
// current execution context
|
||||
var self = this;
|
||||
|
||||
//if ( DEBUG ) {
|
||||
// if ( arguments.length !== 2 ) { throw new Error(__filename + ': wrong arguments number'); }
|
||||
// if ( typeof name !== 'string' || name.length === 0 ) { throw new Error(__filename + ': wrong or empty name'); }
|
||||
// if ( typeof callback !== 'function' ) { throw new Error(__filename + ': wrong callback type'); }
|
||||
//}
|
||||
|
||||
// initialization may be required
|
||||
this.events[name] = this.events[name] || [];
|
||||
// append this new event to the list
|
||||
this.events[name].push(function onceWrapper () {
|
||||
callback.apply(this, arguments);
|
||||
self.removeListener(name, onceWrapper);
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Apply multiple listeners at once.
|
||||
*
|
||||
* @param {Object} callbacks event names with callbacks
|
||||
*
|
||||
* @example
|
||||
* emitter.addListeners({
|
||||
* click: function ( data ) {},
|
||||
* close: function ( data ) {}
|
||||
* });
|
||||
*/
|
||||
addListeners: function ( callbacks ) {
|
||||
var name;
|
||||
|
||||
//if ( DEBUG ) {
|
||||
// if ( arguments.length !== 1 ) { throw new Error(__filename + ': wrong arguments number'); }
|
||||
// if ( typeof callbacks !== 'object' ) { throw new Error(__filename + ': wrong callbacks type'); }
|
||||
// if ( Object.keys(callbacks).length === 0 ) { throw new Error(__filename + ': no callbacks given'); }
|
||||
//}
|
||||
|
||||
for ( name in callbacks ) {
|
||||
if ( callbacks.hasOwnProperty(name) ) {
|
||||
this.addListener(name, callbacks[name]);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Remove all instances of the given callback.
|
||||
*
|
||||
* @param {string} name event identifier
|
||||
* @param {function} callback function to remove
|
||||
*
|
||||
* @example
|
||||
* emitter.removeListener('click', func1);
|
||||
*/
|
||||
removeListener: function ( name, callback ) {
|
||||
//if ( DEBUG ) {
|
||||
// if ( arguments.length !== 2 ) { throw new Error(__filename + ': wrong arguments number'); }
|
||||
// if ( typeof name !== 'string' || name.length === 0 ) { throw new Error(__filename + ': wrong or empty name'); }
|
||||
// if ( typeof callback !== 'function' ) { throw new Error(__filename + ': wrong callback type'); }
|
||||
// if ( this.events[name] && !Array.isArray(this.events[name]) ) { throw new Error(__filename + ': corrupted inner data'); }
|
||||
//}
|
||||
|
||||
// the event exists and should have some callbacks
|
||||
if ( this.events[name] ) {
|
||||
// rework the callback list to exclude the given one
|
||||
this.events[name] = this.events[name].filter(function callbacksFilter ( fn ) { return fn !== callback; });
|
||||
// event has no more callbacks so clean it
|
||||
if ( this.events[name].length === 0 ) {
|
||||
// as if there were no listeners at all
|
||||
this.events[name] = undefined;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Remove all callbacks for the given event name.
|
||||
* Without event name clears all events.
|
||||
*
|
||||
* @param {string} [name] event identifier
|
||||
*
|
||||
* @example
|
||||
* emitter.removeAllListeners('click');
|
||||
* emitter.removeAllListeners();
|
||||
*/
|
||||
removeAllListeners: function ( name ) {
|
||||
//if ( DEBUG ) {
|
||||
// if ( arguments.length !== 0 && (typeof name !== 'string' || name.length === 0) ) { throw new Error(__filename + ': wrong or empty name'); }
|
||||
//}
|
||||
|
||||
// check input
|
||||
if ( arguments.length === 0 ) {
|
||||
// no arguments so remove everything
|
||||
this.events = {};
|
||||
} else if ( name ) {
|
||||
//if ( DEBUG ) {
|
||||
// if ( this.events[name] ) { throw new Error(__filename + ': event is not removed'); }
|
||||
//}
|
||||
|
||||
// only name is given so remove all callbacks for the given event
|
||||
// but object structure modification should be avoided
|
||||
this.events[name] = undefined;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Execute each of the listeners in the given order with the supplied arguments.
|
||||
*
|
||||
* @param {string} name event identifier
|
||||
*
|
||||
* @example
|
||||
* emitter.emit('init');
|
||||
* emitter.emit('click', {src: panel1, dst: panel2});
|
||||
* emitter.emit('load', error, data);
|
||||
*
|
||||
* // it's a good idea to emit event only when there are some listeners
|
||||
* if ( this.events['click'] ) {
|
||||
* this.emit('click', {event: event});
|
||||
* }
|
||||
*/
|
||||
emit: function ( name ) {
|
||||
var event = this.events[name],
|
||||
i;
|
||||
|
||||
//if ( DEBUG ) {
|
||||
// if ( arguments.length < 1 ) { throw new Error(__filename + ': wrong arguments number'); }
|
||||
// if ( typeof name !== 'string' || name.length === 0 ) { throw new Error(__filename + ': wrong or empty name'); }
|
||||
//}
|
||||
|
||||
// the event exists and should have some callbacks
|
||||
if ( event ) {
|
||||
//if ( DEBUG ) {
|
||||
// if ( !Array.isArray(event) ) { throw new Error(__filename + ': wrong event type'); }
|
||||
//}
|
||||
|
||||
for ( i = 0; i < event.length; i++ ) {
|
||||
//if ( DEBUG ) {
|
||||
// if ( typeof event[i] !== 'function' ) { throw new Error(__filename + ': wrong event callback type'); }
|
||||
//}
|
||||
|
||||
// invoke the callback with parameters
|
||||
event[i].apply(this, Array.prototype.slice.call(arguments, 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// correct constructor name
|
||||
Emitter.prototype.constructor = Emitter;
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* PO files manipulation (gettext format)
|
||||
* @namespace
|
||||
* @author Stanislav Kalashnik <sk@infomir.eu>
|
||||
*/
|
||||
var gettext = (function(){
|
||||
// declarations
|
||||
var module = {};
|
||||
var data = null; // all localized strings
|
||||
|
||||
|
||||
/**
|
||||
* Prepares and loads the given localization file
|
||||
* @param {Object} options
|
||||
* @param {Function} onReady callback on the given language load or on failure
|
||||
*/
|
||||
module.init = function ( options, onReady ) {
|
||||
// defaults
|
||||
options.name = options.name || 'en';
|
||||
options.ext = options.ext || 'js';
|
||||
options.path = options.path || 'lang';
|
||||
// reset for english
|
||||
data = null;
|
||||
// load localization only for non-english language
|
||||
if ( options.name !== 'en' ) {
|
||||
loadScript(options.path + '/' + options.name + '.' + options.ext, onReady, onReady);
|
||||
} else {
|
||||
// and run callback
|
||||
if ( typeof onReady === 'function' ) {
|
||||
onReady();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Wrapper to receive the incoming data
|
||||
* @param {Object} input
|
||||
*/
|
||||
module.load = function ( input ) {
|
||||
data = input;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Localized string getter
|
||||
* @param {string} key string to localize (in english)
|
||||
* @return {string}
|
||||
*/
|
||||
module.data = function ( key ) {
|
||||
if (data === null) {
|
||||
return key;
|
||||
} else {
|
||||
return data[key] !== undefined ? data[key] : key;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// export
|
||||
return module;
|
||||
|
||||
})();
|
||||
|
||||
|
||||
// short alias
|
||||
var _ = gettext.data;
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,103 @@
|
|||
/**
|
||||
* Global init procedure
|
||||
* should be called in all html files
|
||||
* if no gSTB object found switches to emulation mode
|
||||
* if environment variable debug = 1 add some debug tools
|
||||
* if set environment variable debug_name then add weinre to all pages
|
||||
* @author Stanislav Kalashnik <sk@infomir.eu>
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
// global flags
|
||||
var DEBUG = true,
|
||||
DEBUG_NAME = '', // primary name and primary address
|
||||
DEBUG_SERVER = '', // in case they are not given then env vars and local storage will be used
|
||||
EMULATION = false,
|
||||
|
||||
PATH_MODE_WEB = false, // portal is starting from web by http/https or locally
|
||||
PATH_ROOT = null,
|
||||
PATH_SYSTEM = null,
|
||||
PATH_MEDIA = '/media/',
|
||||
|
||||
WINDOW_WIDTH = screen.width,
|
||||
WINDOW_HEIGHT = screen.height,
|
||||
URL_PARAMS = {}, // query params
|
||||
initFound = false,
|
||||
proxy;
|
||||
|
||||
PATH_ROOT = location.pathname.split('/');
|
||||
PATH_ROOT[PATH_ROOT.length - 1] = '';
|
||||
PATH_ROOT = PATH_ROOT.join('/');
|
||||
PATH_ROOT = location.protocol + '//' + location.host + PATH_ROOT + PATH_BASE;
|
||||
PATH_SYSTEM = PATH_ROOT + 'system/';
|
||||
|
||||
// any query params
|
||||
if ( document.location.search ) {
|
||||
// parse and fill the global list
|
||||
document.location.search.substring(1).split('&').forEach(function(part){
|
||||
part = part.split('=');
|
||||
// valid number on params
|
||||
if ( part.length === 2 ) { URL_PARAMS[part[0]] = decodeURIComponent(part[1]); }
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// native or emulation
|
||||
if ( window.gSTB === undefined ) {
|
||||
|
||||
DEBUG = EMULATION = true;
|
||||
|
||||
// load stored vars
|
||||
DEBUG_SERVER = DEBUG_SERVER || localStorage.getItem('DEBUG_SERVER');
|
||||
DEBUG_NAME = DEBUG_NAME || localStorage.getItem('DEBUG_NAME');
|
||||
|
||||
// resolution correction
|
||||
WINDOW_WIDTH = parseInt(URL_PARAMS.width, 10) || window.outerWidth;
|
||||
WINDOW_HEIGHT = parseInt(URL_PARAMS.height, 10) || window.outerHeight;
|
||||
|
||||
// stb global object is not present so emulation
|
||||
document.write('<script type="text/javascript" src="' + PATH_SYSTEM + 'gstb.js"></script>\n');
|
||||
document.write('<script type="text/javascript" src="' + PATH_SYSTEM + 'debug.js"></script>\n');
|
||||
document.write('<script type="text/javascript" src="http://' + DEBUG_SERVER + ':8800/file/client.js"></script>\n');
|
||||
document.write('<script type="text/javascript">' +
|
||||
'if ( window.proxyClient ) {' +
|
||||
'proxy = new proxyClient();' +
|
||||
'if ( DEBUG_NAME && DEBUG_SERVER ) proxy.init({name:"' + DEBUG_NAME + '", host:"' + DEBUG_SERVER + '"}); }' +
|
||||
'</script>\n');
|
||||
|
||||
// page loading finished
|
||||
window.addEventListener('load', function(){
|
||||
document.body.style.backgroundColor = 'black';
|
||||
});
|
||||
|
||||
} else {
|
||||
|
||||
// new way of string handling
|
||||
// all strings are in UTF-16
|
||||
gSTB.SetNativeStringMode(true);
|
||||
|
||||
// check mode
|
||||
(function(){
|
||||
var vars = JSON.parse(gSTB.GetEnv(JSON.stringify({varList:['debug', 'debug_name', 'debug_server']})));
|
||||
if ( !vars.errMsg && vars.result ) {
|
||||
DEBUG = vars.result.debug === '1';
|
||||
// no errors and valid string
|
||||
if ( DEBUG && !URL_PARAMS.nodebug ) {
|
||||
// general tools
|
||||
document.write('<script type="text/javascript" src="' + PATH_SYSTEM + 'debug.js"></script>\n');
|
||||
// rewrite default
|
||||
DEBUG_NAME = DEBUG_NAME || vars.result.debug_name;
|
||||
DEBUG_SERVER = DEBUG_SERVER || vars.result.debug_server;
|
||||
if ( DEBUG_NAME && DEBUG_SERVER && stbStorage.getItem('spyjs.active') !== '1' ) {
|
||||
// web inspector
|
||||
document.write('<script type="text/javascript" src="http://' + DEBUG_SERVER + ':8000/target/target-script-min.js#' + DEBUG_NAME + '"></script>\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
// additional path to image
|
||||
var PATH_IMG_SYSTEM = PATH_SYSTEM + 'img/' + WINDOW_WIDTH + '/',
|
||||
PATH_IMG_PUBLIC = PATH_ROOT + 'public/img/' + WINDOW_WIDTH + '/';
|
|
@ -0,0 +1,248 @@
|
|||
/**
|
||||
* Json DataBase simple implementation
|
||||
* @author Stanislav Kalashnik <sk@infomir.eu>
|
||||
* @license GNU GENERAL PUBLIC LICENSE Version 3
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Main DataBase object
|
||||
* @constructor
|
||||
* @param {Object} [data={}] initial data provided
|
||||
*/
|
||||
function jsonDb ( data ) {
|
||||
this.data = data || {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Table handler
|
||||
* gets the specified table by its name or creates one
|
||||
* @param {string} name new or existing db table name
|
||||
* @return {jsonDbTable|boolean} newly created table object or false on failure
|
||||
*/
|
||||
jsonDb.prototype.table = function ( name ) {
|
||||
if ( name ) {
|
||||
// data placeholder if not exist
|
||||
if ( typeof this.data[name] !== 'object' ) {
|
||||
this.data[name] = {};
|
||||
}
|
||||
// wrap data with object
|
||||
return new jsonDbTable(this.data[name]);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* DataBase table object
|
||||
* should be called directly only from the jsonDb object
|
||||
* @constructor
|
||||
* @param {Object} [data={}] initial data provided
|
||||
*/
|
||||
function jsonDbTable ( data ) {
|
||||
this.data = data || {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the table inner data structure and sets table field names
|
||||
* @param {string} id the first mandatory column
|
||||
* @param {...String} fields free list of fields
|
||||
* @return {boolean} operation success status
|
||||
* @example
|
||||
* // two columns table creation
|
||||
* table.init('id', 'name');
|
||||
*/
|
||||
jsonDbTable.prototype.init = function ( id, fields ) {
|
||||
if ( arguments.length > 0 ) {
|
||||
// reset all to default (without breaking the link to db)
|
||||
this.data.rows = {}; // all table records in the compact form {id:[fld1, fld2, fld3]}
|
||||
this.data.idName = Array.prototype.shift.call(arguments);
|
||||
this.data.rowIdx = 0; // last used table index (is used by genId method to get unique id)
|
||||
this.data.fldIds = []; // list ['name', 'title']
|
||||
this.data.fldIdx = {}; // hash {name:0, title:1}
|
||||
// apply all other fields names
|
||||
for ( var index = 0; index < arguments.length; index++ ) {
|
||||
// plain list of names
|
||||
this.data.fldIds[index] = arguments[index];
|
||||
// hash map name to index
|
||||
this.data.fldIdx[arguments[index]] = index;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Unique identifier counter
|
||||
* produces an id on each call
|
||||
* @return {string} id for add method
|
||||
*/
|
||||
jsonDbTable.prototype.genId = function () {
|
||||
return (++this.data.rowIdx).toString();
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a new data record with the given id
|
||||
* @param {string|number} id mandatory unique row identifier (converted to string)
|
||||
* @param {...*} data free list of column values
|
||||
* @return {boolean} operation success status
|
||||
* @example
|
||||
* // new record with id 2
|
||||
* table.add(2, 'some name');
|
||||
*/
|
||||
jsonDbTable.prototype.add = function ( id, data ) {
|
||||
var row = [], index;
|
||||
// init new record
|
||||
if ( id && this.data.rows[id] === undefined ) {
|
||||
// iterate all table fields
|
||||
for ( index = 0; index < this.data.fldIds.length; index++ ) {
|
||||
// and get corresponding items from incoming data
|
||||
row[index] = arguments[index+1] !== undefined ? arguments[index+1] : null;
|
||||
}
|
||||
// apply a filled new record
|
||||
this.data.rows[id] = row;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves a data record by the given id
|
||||
* @param {string|number} id mandatory unique row identifier
|
||||
* @param {string} [field] row column name to return
|
||||
* @return {*} row data item, its one field value or false on failure
|
||||
* @example
|
||||
* // get record data with id 2
|
||||
* table.get(2);
|
||||
* // get a single record data field with id 2
|
||||
* table.get(2, 'name');
|
||||
*/
|
||||
jsonDbTable.prototype.get = function ( id, field ) {
|
||||
var row = this.data.rows[id],
|
||||
index, data = {};
|
||||
// existing record
|
||||
if ( row !== undefined ) {
|
||||
// field name is given
|
||||
if ( field !== undefined ) {
|
||||
// and valid
|
||||
if ( field in this.data.fldIdx ) {
|
||||
// single field
|
||||
return row[this.data.fldIdx[field]];
|
||||
}
|
||||
} else {
|
||||
// explicit id return
|
||||
data[this.data.idName] = id.toString();
|
||||
// make a real object from a compact record
|
||||
for ( index = 0; index < row.length; index++ ) {
|
||||
data[this.data.fldIds[index]] = row[index];
|
||||
}
|
||||
// all fields
|
||||
return data;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the record find by id with the given fields
|
||||
* in case there is no such record creates a new one
|
||||
* @param {string|number} id mandatory unique row identifier
|
||||
* @param {Object} data pairs of field names with values
|
||||
* @return {boolean} operation success status
|
||||
* @example
|
||||
* // update record with id 2
|
||||
* table.set(2, {name:'some other name'});
|
||||
*/
|
||||
jsonDbTable.prototype.set = function ( id, data ) {
|
||||
var row = [], index, name;
|
||||
// valid incoming data
|
||||
if ( id && data && typeof data === 'object' ) {
|
||||
// init a new record if necessary
|
||||
if ( this.data.rows[id] === undefined ) {
|
||||
// iterate all table fields and set to null
|
||||
for ( index = 0; index < this.data.fldIds.length; index++ ) {
|
||||
row[index] = null;
|
||||
}
|
||||
// apply a null-filled new record
|
||||
this.data.rows[id] = row;
|
||||
} else {
|
||||
// link to existing item
|
||||
row = this.data.rows[id];
|
||||
}
|
||||
// update the given fields
|
||||
for ( name in data ) {
|
||||
// fill some existing fields
|
||||
if ( name in this.data.fldIdx ) {
|
||||
row[this.data.fldIdx[name]] = data[name];
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Clears a row by the given id
|
||||
* @param {string|number} id mandatory unique row identifier
|
||||
* @return {boolean} operation success status
|
||||
* @example
|
||||
* // remove record with id 2
|
||||
* table.unset(2);
|
||||
*/
|
||||
jsonDbTable.prototype.unset = function ( id ) {
|
||||
return delete this.data.rows[id];
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes all records and resets the index
|
||||
*/
|
||||
jsonDbTable.prototype.clear = function () {
|
||||
this.data.rows = {};
|
||||
this.data.rowIdx = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Collects all records matching the given filter
|
||||
* @param {Object} where filter conditions to check ({} - all records, omitted - no records)
|
||||
* @param {number} [limit=none] positive number of records to return (otherwise - no limitations)
|
||||
* @return {Array} all found rows
|
||||
* @example
|
||||
* // get all records with the specific name
|
||||
* table.find({name:'some name'});
|
||||
* // get only 2 records with the specific name
|
||||
* table.find({name:'some name'}, 2);
|
||||
*/
|
||||
jsonDbTable.prototype.find = function ( where, limit ) {
|
||||
var rows = this.data.rows, item,
|
||||
data = [], id, condition, match, index;
|
||||
// check incoming filter
|
||||
if ( where && typeof where === 'object' ) {
|
||||
// iterate all table rows
|
||||
for ( id in rows ) {
|
||||
// suitable by default
|
||||
match = true;
|
||||
// check all the filter attributes (all should match)
|
||||
for ( condition in where ) {
|
||||
match = match && (rows[id][this.data.fldIdx[condition]] === where[condition]);
|
||||
}
|
||||
// fill result list with matched records
|
||||
if ( match ) {
|
||||
// wrap
|
||||
item = {};
|
||||
// explicit id return
|
||||
item[this.data.idName] = id;
|
||||
// make a real object from a compact record
|
||||
for ( index = 0; index < rows[id].length; index++ ) {
|
||||
item[this.data.fldIds[index]] = rows[id][index];
|
||||
}
|
||||
data.push(item);
|
||||
// get enough check and exit
|
||||
if ( limit !== undefined && limit === data.length ) {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return data;
|
||||
};
|
|
@ -0,0 +1,63 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* Global list of key codes
|
||||
* with shift key pressed +1000
|
||||
* with alt key pressed +2000
|
||||
* @namespace
|
||||
*/
|
||||
var KEYS = {
|
||||
BACK : 8, // Backspace
|
||||
NUM1 : 49,
|
||||
NUM2 : 50,
|
||||
NUM3 : 51,
|
||||
NUM4 : 52,
|
||||
NUM5 : 53,
|
||||
NUM6 : 54,
|
||||
NUM7 : 55,
|
||||
NUM8 : 56,
|
||||
NUM9 : 57,
|
||||
NUM0 : 48,
|
||||
DELETE : 46,
|
||||
CHANNEL_PREV : 1009, // Shift+Tab
|
||||
CHANNEL_NEXT : 9, // Tab
|
||||
OK : 13, // Enter
|
||||
EXIT : 27, // Esc
|
||||
UP : 38, // UP ARROW
|
||||
DOWN : 40, // DOWN ARROW
|
||||
LEFT : 37, // LEFT ARROW
|
||||
RIGHT : 39, // RIGHT ARROW
|
||||
PAGE_UP : 33, // Page Up
|
||||
PAGE_DOWN : 34, // Page Down
|
||||
END : 35,
|
||||
HOME : 36,
|
||||
VOLUME_UP : 107, // NUMPAD +
|
||||
VOLUME_DOWN : 109, // NUMPAD -
|
||||
F1 : 112, // F1
|
||||
F2 : 113, // F2
|
||||
F3 : 114, // F3
|
||||
F4 : 115, // F4
|
||||
REFRESH : 116, // F5
|
||||
FRAME : 117, // F6
|
||||
PHONE : 119, // F8
|
||||
EPG : 119, // F8
|
||||
SET : 120, // F9
|
||||
TV : 121, // F10
|
||||
MENU : 122, // F11
|
||||
WEB : 123, // F12
|
||||
MIC : 2032,
|
||||
REWIND : 2066, // Alt+B
|
||||
FORWARD : 2070, // Alt+F
|
||||
APP : 2076, // Alt+L
|
||||
USB_MOUNTED : 2080, // Alt+P
|
||||
USB_UNMOUNTED: 2081, // Alt+Q
|
||||
PLAY_PAUSE : 2082, // Alt+R
|
||||
STOP : 2083, // Alt+S
|
||||
POWER : 2085, // Alt+U
|
||||
RECORD : 2087, // Alt+W
|
||||
INFO : 2089, // Alt+Y
|
||||
MUTE : 2192,
|
||||
CLOCK : 2032,
|
||||
AUDIO : 2071, // Alt+G
|
||||
KEYBOARD : 2076 // Alt+L
|
||||
};
|
|
@ -0,0 +1,398 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* Global list of time zones
|
||||
* @namespace
|
||||
* TODO: should be removed unused property "offset"
|
||||
*/
|
||||
var TIME_ZONES = [
|
||||
{'id': 'Africa/Accra', 'offset': '+00:00'},
|
||||
{'id': 'Africa/Addis_Ababa', 'offset': '+03:00', 'name': 'Africa/Addis Ababa'},
|
||||
{'id': 'Africa/Algiers', 'offset': '+01:00'},
|
||||
{'id': 'Africa/Asmera', 'offset': '+03:00'},
|
||||
{'id': 'Africa/Bamako', 'offset': '+00:00'},
|
||||
{'id': 'Africa/Bangui', 'offset': '+01:00'},
|
||||
{'id': 'Africa/Banjul', 'offset': '+00:00'},
|
||||
{'id': 'Africa/Bissau', 'offset': '+00:00'},
|
||||
{'id': 'Africa/Blantyre', 'offset': '+02:00'},
|
||||
{'id': 'Africa/Brazzaville', 'offset': '+01:00'},
|
||||
{'id': 'Africa/Bujumbura', 'offset': '+02:00'},
|
||||
{'id': 'Africa/Cairo', 'offset': '+02:00'},
|
||||
{'id': 'Africa/Casablanca', 'offset': '+00:00'},
|
||||
{'id': 'Africa/Ceuta', 'offset': '+01:00'},
|
||||
{'id': 'Africa/Conakry', 'offset': '+00:00'},
|
||||
{'id': 'Africa/Dakar', 'offset': '+00:00'},
|
||||
{'id': 'Africa/Dar_es_Salaam', 'offset': '+03:00', 'name': 'Africa/Dar es Salaam'},
|
||||
{'id': 'Africa/Djibouti', 'offset': '+03:00'},
|
||||
{'id': 'Africa/Douala', 'offset': '+01:00'},
|
||||
{'id': 'Africa/El_Aaiun', 'offset': '+00:00', 'name': 'Africa/El Aaiun'},
|
||||
{'id': 'Africa/Freetown', 'offset': '+00:00'},
|
||||
{'id': 'Africa/Gaborone', 'offset': '+02:00'},
|
||||
{'id': 'Africa/Harare', 'offset': '+02:00'},
|
||||
{'id': 'Africa/Johannesburg', 'offset': '+02:00'},
|
||||
{'id': 'Africa/Kampala', 'offset': '+03:00'},
|
||||
{'id': 'Africa/Khartoum', 'offset': '+03:00'},
|
||||
{'id': 'Africa/Kigali', 'offset': '+02:00'},
|
||||
{'id': 'Africa/Kinshasa', 'offset': '+01:00'},
|
||||
{'id': 'Africa/Lagos', 'offset': '+01:00'},
|
||||
{'id': 'Africa/Libreville', 'offset': '+01:00'},
|
||||
{'id': 'Africa/Lome', 'offset': '+00:00'},
|
||||
{'id': 'Africa/Luanda', 'offset': '+01:00'},
|
||||
{'id': 'Africa/Lubumbashi', 'offset': '+02:00'},
|
||||
{'id': 'Africa/Lusaka', 'offset': '+02:00'},
|
||||
{'id': 'Africa/Malabo', 'offset': '+01:00'},
|
||||
{'id': 'Africa/Maputo', 'offset': '+02:00'},
|
||||
{'id': 'Africa/Maseru', 'offset': '+02:00'},
|
||||
{'id': 'Africa/Mbabane', 'offset': '+02:00'},
|
||||
{'id': 'Africa/Mogadishu', 'offset': '+03:00'},
|
||||
{'id': 'Africa/Monrovia', 'offset': '+00:00'},
|
||||
{'id': 'Africa/Nairobi', 'offset': '+03:00'},
|
||||
{'id': 'Africa/Ndjamena', 'offset': '+01:00'},
|
||||
{'id': 'Africa/Niamey', 'offset': '+01:00'},
|
||||
{'id': 'Africa/Nouakchott', 'offset': '+00:00'},
|
||||
{'id': 'Africa/Ouagadougou', 'offset': '+00:00'},
|
||||
{'id': 'Africa/Porto-Novo', 'offset': '+01:00'},
|
||||
{'id': 'Africa/Sao_Tome', 'offset': '+00:00', 'name': 'Africa/Sao Tome'},
|
||||
{'id': 'Africa/Tripoli', 'offset': '+02:00'},
|
||||
{'id': 'Africa/Tunis', 'offset': '+01:00'},
|
||||
{'id': 'Africa/Windhoek', 'offset': '+01:00'},
|
||||
{'id': 'America/Adak', 'offset': '-10:00'},
|
||||
{'id': 'America/Anchorage', 'offset': '-09:00'},
|
||||
{'id': 'America/Anguilla', 'offset': '-04:00'},
|
||||
{'id': 'America/Antigua', 'offset': '-04:00'},
|
||||
{'id': 'America/Araguaina', 'offset': '-03:00'},
|
||||
{'id': 'America/Argentina/Buenos_Aires', 'offset': '-03:00', 'name': 'America/Argentina/Buenos Aires'},
|
||||
{'id': 'America/Argentina/Catamarca', 'offset': '-03:00'},
|
||||
{'id': 'America/Argentina/Cordoba', 'offset': '-03:00'},
|
||||
{'id': 'America/Argentina/Jujuy', 'offset': '-03:00'},
|
||||
{'id': 'America/Argentina/La_Rioja', 'offset': '-03:00', 'name': 'America/Argentina/La Rioja'},
|
||||
{'id': 'America/Argentina/Mendoza', 'offset': '-03:00'},
|
||||
{'id': 'America/Argentina/Rio_Gallegos', 'offset': '-03:00', 'name': 'America/Argentina/Rio Gallegos'},
|
||||
{'id': 'America/Argentina/San_Juan', 'offset': '-03:00', 'name': 'America/Argentina/San Juan'},
|
||||
{'id': 'America/Argentina/Tucuman', 'offset': '-03:00'},
|
||||
{'id': 'America/Argentina/Ushuaia', 'offset': '-03:00'},
|
||||
{'id': 'America/Aruba', 'offset': '-04:00'},
|
||||
{'id': 'America/Asuncion', 'offset': '-04:00'},
|
||||
{'id': 'America/Bahia', 'offset': '-03:00'},
|
||||
{'id': 'America/Barbados', 'offset': '-04:00'},
|
||||
{'id': 'America/Belem', 'offset': '-03:00'},
|
||||
{'id': 'America/Belize', 'offset': '-06:00'},
|
||||
{'id': 'America/Boa_Vista', 'offset': '-04:00', 'name': 'America/Boa Vista'},
|
||||
{'id': 'America/Bogota', 'offset': '-05:00'},
|
||||
{'id': 'America/Boise', 'offset': '-07:00'},
|
||||
{'id': 'America/Cambridge_Bay', 'offset': '-07:00', 'name': 'America/Cambridge Bay'},
|
||||
{'id': 'America/Campo_Grande', 'offset': '-04:00', 'name': 'America/Campo Grande'},
|
||||
{'id': 'America/Cancun', 'offset': '-06:00'},
|
||||
{'id': 'America/Caracas', 'offset': '-04:30'},
|
||||
{'id': 'America/Cayenne', 'offset': '-03:00'},
|
||||
{'id': 'America/Cayman', 'offset': '-05:00'},
|
||||
{'id': 'America/Chicago', 'offset': '-06:00'},
|
||||
{'id': 'America/Chihuahua', 'offset': '-07:00'},
|
||||
{'id': 'America/Coral_Harbour', 'offset': '-05:00', 'name': 'America/Coral Harbour'},
|
||||
{'id': 'America/Costa_Rica', 'offset': '-06:00', 'name': 'America/Costa Rica'},
|
||||
{'id': 'America/Cuiaba', 'offset': '-04:00'},
|
||||
{'id': 'America/Curacao', 'offset': '-04:00'},
|
||||
{'id': 'America/Danmarkshavn', 'offset': '+00:00'},
|
||||
{'id': 'America/Dawson', 'offset': '-08:00'},
|
||||
{'id': 'America/Dawson_Creek', 'offset': '-07:00', 'name': 'America/Dawson Creek'},
|
||||
{'id': 'America/Denver', 'offset': '-07:00'},
|
||||
{'id': 'America/Detroit', 'offset': '-05:00'},
|
||||
{'id': 'America/Dominica', 'offset': '-04:00'},
|
||||
{'id': 'America/Edmonton', 'offset': '-07:00'},
|
||||
{'id': 'America/Eirunepe', 'offset': '-04:00'},
|
||||
{'id': 'America/El_Salvador', 'offset': '-06:00', 'name': 'America/El Salvador'},
|
||||
{'id': 'America/Fortaleza', 'offset': '-03:00'},
|
||||
{'id': 'America/Glace_Bay', 'offset': '-04:00', 'name': 'America/Glace Bay'},
|
||||
{'id': 'America/Godthab', 'offset': '-03:00'},
|
||||
{'id': 'America/Goose_Bay', 'offset': '-04:00', 'name': 'America/Goose Bay'},
|
||||
{'id': 'America/Grand_Turk', 'offset': '-05:00', 'name': 'America/Grand Turk'},
|
||||
{'id': 'America/Grenada', 'offset': '-04:00'},
|
||||
{'id': 'America/Guadeloupe', 'offset': '-04:00'},
|
||||
{'id': 'America/Guatemala', 'offset': '-06:00'},
|
||||
{'id': 'America/Guayaquil', 'offset': '-05:00'},
|
||||
{'id': 'America/Guyana', 'offset': '-04:00'},
|
||||
{'id': 'America/Halifax', 'offset': '-04:00'},
|
||||
{'id': 'America/Havana', 'offset': '-05:00'},
|
||||
{'id': 'America/Hermosillo', 'offset': '-07:00'},
|
||||
{'id': 'America/Indiana/Indianapolis', 'offset': '-05:00'},
|
||||
{'id': 'America/Indiana/Knox', 'offset': '-06:00'},
|
||||
{'id': 'America/Indiana/Marengo', 'offset': '-05:00'},
|
||||
{'id': 'America/Indiana/Petersburg', 'offset': '-05:00'},
|
||||
{'id': 'America/Indiana/Vevay', 'offset': '-05:00'},
|
||||
{'id': 'America/Indiana/Vincennes', 'offset': '-05:00'},
|
||||
{'id': 'America/Inuvik', 'offset': '-07:00'},
|
||||
{'id': 'America/Iqaluit', 'offset': '-05:00'},
|
||||
{'id': 'America/Juneau', 'offset': '-09:00'},
|
||||
{'id': 'America/Kentucky/Louisville', 'offset': '-05:00'},
|
||||
{'id': 'America/Kentucky/Monticello', 'offset': '-05:00'},
|
||||
{'id': 'America/La_Paz', 'offset': '-04:00', 'name': 'America/La Paz'},
|
||||
{'id': 'America/Lima', 'offset': '-05:00'},
|
||||
{'id': 'America/Los_Angeles', 'offset': '-08:00', 'name': 'America/Los Angeles'},
|
||||
{'id': 'America/Maceio', 'offset': '-03:00'},
|
||||
{'id': 'America/Managua', 'offset': '-06:00'},
|
||||
{'id': 'America/Manaus', 'offset': '-04:00'},
|
||||
{'id': 'America/Martinique', 'offset': '-04:00'},
|
||||
{'id': 'America/Mazatlan', 'offset': '-07:00'},
|
||||
{'id': 'America/Menominee', 'offset': '-06:00'},
|
||||
{'id': 'America/Merida', 'offset': '-06:00'},
|
||||
{'id': 'America/Mexico_City', 'offset': '-06:00', 'name': 'America/Mexico City'},
|
||||
{'id': 'America/Miquelon', 'offset': '-03:00'},
|
||||
{'id': 'America/Moncton', 'offset': '-04:00'},
|
||||
{'id': 'America/Monterrey', 'offset': '-06:00'},
|
||||
{'id': 'America/Montevideo', 'offset': '-03:00'},
|
||||
{'id': 'America/Montreal', 'offset': '-05:00'},
|
||||
{'id': 'America/Montserrat', 'offset': '-04:00'},
|
||||
{'id': 'America/Nassau', 'offset': '-05:00'},
|
||||
{'id': 'America/New_York', 'offset': '-05:00', 'name': 'America/New York'},
|
||||
{'id': 'America/Nipigon', 'offset': '-05:00'},
|
||||
{'id': 'America/Nome', 'offset': '-09:00'},
|
||||
{'id': 'America/Noronha', 'offset': '-02:00'},
|
||||
{'id': 'America/North_Dakota/Center', 'offset': '-06:00', 'name': 'America/North Dakota/Center'},
|
||||
{'id': 'America/Panama', 'offset': '-05:00'},
|
||||
{'id': 'America/Pangnirtung', 'offset': '-05:00'},
|
||||
{'id': 'America/Paramaribo', 'offset': '-03:00'},
|
||||
{'id': 'America/Phoenix', 'offset': '-07:00'},
|
||||
{'id': 'America/Port-au-Prince', 'offset': '-05:00'},
|
||||
{'id': 'America/Port_of_Spain', 'offset': '-04:00', 'name': 'America/Port of Spain'},
|
||||
{'id': 'America/Porto_Velho', 'offset': '-04:00', 'name': 'America/Porto Velho'},
|
||||
{'id': 'America/Puerto_Rico', 'offset': '-04:00', 'name': 'America/Puerto Rico'},
|
||||
{'id': 'America/Rainy_River', 'offset': '-06:00', 'name': 'America/Rainy River'},
|
||||
{'id': 'America/Rankin_Inlet', 'offset': '-06:00', 'name': 'America/Rankin Inlet'},
|
||||
{'id': 'America/Recife', 'offset': '-03:00'},
|
||||
{'id': 'America/Regina', 'offset': '-06:00'},
|
||||
{'id': 'America/Rio_Branco', 'offset': '-05:00', 'name': 'America/Rio Branco'},
|
||||
{'id': 'America/Santiago', 'offset': '-04:00'},
|
||||
{'id': 'America/Santo_Domingo', 'offset': '-04:00', 'name': 'America/Santo Domingo'},
|
||||
{'id': 'America/Sao_Paulo', 'offset': '-03:00', 'name': 'America/Sao Paulo'},
|
||||
{'id': 'America/Scoresbysund', 'offset': '-01:00'},
|
||||
{'id': 'America/Shiprock', 'offset': '-07:00'},
|
||||
{'id': 'America/Swift_Current', 'offset': '-06:00', 'name': 'America/Swift Current'},
|
||||
{'id': 'America/Tegucigalpa', 'offset': '-06:00'},
|
||||
{'id': 'America/Thule', 'offset': '-04:00'},
|
||||
{'id': 'America/Thunder_Bay', 'offset': '-05:00', 'name': 'America/Thunder Bay'},
|
||||
{'id': 'America/Tijuana', 'offset': '-08:00'},
|
||||
{'id': 'America/Toronto', 'offset': '-05:00'},
|
||||
{'id': 'America/Tortola', 'offset': '-04:00'},
|
||||
{'id': 'America/Vancouver', 'offset': '-08:00'},
|
||||
{'id': 'America/Whitehorse', 'offset': '-08:00'},
|
||||
{'id': 'America/Winnipeg', 'offset': '-06:00'},
|
||||
{'id': 'America/Yakutat', 'offset': '-09:00'},
|
||||
{'id': 'America/Yellowknife', 'offset': '-07:00'},
|
||||
{'id': 'Antarctica/Casey', 'offset': '+11:00'},
|
||||
{'id': 'Antarctica/Davis', 'offset': '+07:00'},
|
||||
{'id': 'Antarctica/DumontDUrville', 'offset': '+10:00'},
|
||||
{'id': 'Antarctica/Mawson', 'offset': '+05:00'},
|
||||
{'id': 'Antarctica/McMurdo', 'offset': '+12:00'},
|
||||
{'id': 'Antarctica/Palmer', 'offset': '-04:00'},
|
||||
{'id': 'Antarctica/Rothera', 'offset': '-03:00'},
|
||||
{'id': 'Antarctica/South_Pole', 'offset': '+12:00', 'name': 'Antarctica/South Pole'},
|
||||
{'id': 'Antarctica/Syowa', 'offset': '+03:00'},
|
||||
{'id': 'Antarctica/Vostok', 'offset': '+06:00'},
|
||||
{'id': 'Arctic/Longyearbyen', 'offset': '+01:00'},
|
||||
{'id': 'Asia/Aden', 'offset': '+03:00'},
|
||||
{'id': 'Asia/Almaty', 'offset': '+06:00'},
|
||||
{'id': 'Asia/Amman', 'offset': '+02:00'},
|
||||
{'id': 'Asia/Anadyr', 'offset': '+12:00'},
|
||||
{'id': 'Asia/Aqtau', 'offset': '+05:00'},
|
||||
{'id': 'Asia/Aqtobe', 'offset': '+05:00'},
|
||||
{'id': 'Asia/Ashgabat', 'offset': '+05:00'},
|
||||
{'id': 'Asia/Ashkhabad', 'offset': '+05:00'},
|
||||
{'id': 'Asia/Baghdad', 'offset': '+03:00'},
|
||||
{'id': 'Asia/Bahrain', 'offset': '+03:00'},
|
||||
{'id': 'Asia/Baku', 'offset': '+04:00'},
|
||||
{'id': 'Asia/Bangkok', 'offset': '+07:00'},
|
||||
{'id': 'Asia/Beirut', 'offset': '+02:00'},
|
||||
{'id': 'Asia/Bishkek', 'offset': '+06:00'},
|
||||
{'id': 'Asia/Brunei', 'offset': '+08:00'},
|
||||
{'id': 'Asia/Calcutta', 'offset': '+05:30'},
|
||||
{'id': 'Asia/Choibalsan', 'offset': '+08:00'},
|
||||
{'id': 'Asia/Chongqing', 'offset': '+08:00'},
|
||||
{'id': 'Asia/Colombo', 'offset': '+05:30'},
|
||||
{'id': 'Asia/Damascus', 'offset': '+02:00'},
|
||||
{'id': 'Asia/Dhaka', 'offset': '+06:00'},
|
||||
{'id': 'Asia/Dili', 'offset': '+09:00'},
|
||||
{'id': 'Asia/Dubai', 'offset': '+04:00'},
|
||||
{'id': 'Asia/Dushanbe', 'offset': '+05:00'},
|
||||
{'id': 'Asia/Gaza', 'offset': '+02:00'},
|
||||
{'id': 'Asia/Harbin', 'offset': '+08:00'},
|
||||
{'id': 'Asia/Ho_Chi_Minh', 'offset': '+07:00', 'name': 'Asia/Ho Chi Minh'},
|
||||
{'id': 'Asia/Hong_Kong', 'offset': '+08:00', 'name': 'Asia/Hong Kong'},
|
||||
{'id': 'Asia/Hovd', 'offset': '+07:00'},
|
||||
{'id': 'Asia/Irkutsk', 'offset': '+09:00'},
|
||||
{'id': 'Asia/Istanbul', 'offset': '+02:00'},
|
||||
{'id': 'Asia/Jakarta', 'offset': '+07:00'},
|
||||
{'id': 'Asia/Jayapura', 'offset': '+09:00'},
|
||||
{'id': 'Asia/Jerusalem', 'offset': '+02:00'},
|
||||
{'id': 'Asia/Kabul', 'offset': '+04:30'},
|
||||
{'id': 'Asia/Kamchatka', 'offset': '+12:00'},
|
||||
{'id': 'Asia/Karachi', 'offset': '+05:00'},
|
||||
{'id': 'Asia/Kashgar', 'offset': '+08:00'},
|
||||
{'id': 'Asia/Katmandu', 'offset': '+05:45'},
|
||||
{'id': 'Asia/Krasnoyarsk', 'offset': '+08:00'},
|
||||
{'id': 'Asia/Kuala_Lumpur', 'offset': '+08:00', 'name': 'Asia/Kuala Lumpur'},
|
||||
{'id': 'Asia/Kuching', 'offset': '+08:00'},
|
||||
{'id': 'Asia/Kuwait', 'offset': '+03:00'},
|
||||
{'id': 'Asia/Macau', 'offset': '+08:00'},
|
||||
{'id': 'Asia/Magadan', 'offset': '+12:00'},
|
||||
{'id': 'Asia/Makassar', 'offset': '+08:00'},
|
||||
{'id': 'Asia/Manila', 'offset': '+08:00'},
|
||||
{'id': 'Asia/Muscat', 'offset': '+04:00'},
|
||||
{'id': 'Asia/Nicosia', 'offset': '+02:00'},
|
||||
{'id': 'Asia/Novokuznetsk', 'offset': '+07:00'},
|
||||
{'id': 'Asia/Novosibirsk', 'offset': '+07:00'},
|
||||
{'id': 'Asia/Omsk', 'offset': '+07:00'},
|
||||
{'id': 'Asia/Oral', 'offset': '+05:00'},
|
||||
{'id': 'Asia/Phnom_Penh', 'offset': '+07:00', 'name': 'Asia/Phnom Penh'},
|
||||
{'id': 'Asia/Pontianak', 'offset': '+07:00'},
|
||||
{'id': 'Asia/Pyongyang', 'offset': '+09:00'},
|
||||
{'id': 'Asia/Qatar', 'offset': '+03:00'},
|
||||
{'id': 'Asia/Qyzylorda', 'offset': '+06:00'},
|
||||
{'id': 'Asia/Rangoon', 'offset': '+06:30'},
|
||||
{'id': 'Asia/Riyadh', 'offset': '+03:00'},
|
||||
{'id': 'Asia/Saigon', 'offset': '+07:00'},
|
||||
{'id': 'Asia/Sakhalin', 'offset': '+11:00'},
|
||||
{'id': 'Asia/Samarkand', 'offset': '+05:00'},
|
||||
{'id': 'Asia/Seoul', 'offset': '+09:00'},
|
||||
{'id': 'Asia/Shanghai', 'offset': '+08:00'},
|
||||
{'id': 'Asia/Taipei', 'offset': '+08:00'},
|
||||
{'id': 'Asia/Tashkent', 'offset': '+05:00'},
|
||||
{'id': 'Asia/Tbilisi', 'offset': '+04:00'},
|
||||
{'id': 'Asia/Tehran', 'offset': '+03:30'},
|
||||
{'id': 'Asia/Thimphu', 'offset': '+06:00'},
|
||||
{'id': 'Asia/Tokyo', 'offset': '+09:00'},
|
||||
{'id': 'Asia/Ulaanbaatar', 'offset': '+08:00'},
|
||||
{'id': 'Asia/Urumqi', 'offset': '+08:00'},
|
||||
{'id': 'Asia/Vientiane', 'offset': '+07:00'},
|
||||
{'id': 'Asia/Vladivostok', 'offset': '+11:00'},
|
||||
{'id': 'Asia/Yakutsk', 'offset': '+10:00'},
|
||||
{'id': 'Asia/Yekaterinburg', 'offset': '+06:00'},
|
||||
{'id': 'Asia/Yerevan', 'offset': '+04:00'},
|
||||
{'id': 'Atlantic/Azores', 'offset': '-01:00'},
|
||||
{'id': 'Atlantic/Bermuda', 'offset': '-04:00'},
|
||||
{'id': 'Atlantic/Canary', 'offset': '+00:00'},
|
||||
{'id': 'Atlantic/Cape_Verde', 'offset': '-01:00', 'name': 'Atlantic/Cape Verde'},
|
||||
{'id': 'Atlantic/Faeroe', 'offset': '+00:00'},
|
||||
{'id': 'Atlantic/Jan_Mayen', 'offset': '+01:00', 'name': 'Atlantic/Jan Mayen'},
|
||||
{'id': 'Atlantic/Madeira', 'offset': '+00:00'},
|
||||
{'id': 'Atlantic/Reykjavik', 'offset': '+00:00'},
|
||||
{'id': 'Atlantic/South_Georgia', 'offset': '-02:00', 'name': 'Atlantic/South Georgia'},
|
||||
{'id': 'Atlantic/Stanley', 'offset': '-03:00'},
|
||||
{'id': 'Australia/Adelaide', 'offset': '+09:30'},
|
||||
{'id': 'Australia/Brisbane', 'offset': '+10:00'},
|
||||
{'id': 'Australia/Broken_Hill', 'offset': '+09:30', 'name': 'Australia/Broken Hill'},
|
||||
{'id': 'Australia/Currie', 'offset': '+10:00'},
|
||||
{'id': 'Australia/Darwin', 'offset': '+09:30'},
|
||||
{'id': 'Australia/Hobart', 'offset': '+10:00'},
|
||||
{'id': 'Australia/Lindeman', 'offset': '+10:00'},
|
||||
{'id': 'Australia/Lord_Howe', 'offset': '+10:30', 'name': 'Australia/Lord Howe'},
|
||||
{'id': 'Australia/Melbourne', 'offset': '+10:00'},
|
||||
{'id': 'Australia/Perth', 'offset': '+08:00'},
|
||||
{'id': 'Australia/Sydney', 'offset': '+10:00'},
|
||||
{'id': 'Europe/Amsterdam', 'offset': '+01:00'},
|
||||
{'id': 'Europe/Andorra', 'offset': '+01:00'},
|
||||
{'id': 'Europe/Athens', 'offset': '+02:00'},
|
||||
{'id': 'Europe/Belfast', 'offset': '+00:00'},
|
||||
{'id': 'Europe/Belgrade', 'offset': '+01:00'},
|
||||
{'id': 'Europe/Berlin', 'offset': '+01:00'},
|
||||
{'id': 'Europe/Bratislava', 'offset': '+01:00'},
|
||||
{'id': 'Europe/Brussels', 'offset': '+01:00'},
|
||||
{'id': 'Europe/Bucharest', 'offset': '+02:00'},
|
||||
{'id': 'Europe/Budapest', 'offset': '+01:00'},
|
||||
{'id': 'Europe/Chisinau', 'offset': '+02:00'},
|
||||
{'id': 'Europe/Copenhagen', 'offset': '+01:00'},
|
||||
{'id': 'Europe/Dublin', 'offset': '+00:00'},
|
||||
{'id': 'Europe/Gibraltar', 'offset': '+01:00'},
|
||||
{'id': 'Europe/Guernsey', 'offset': '+00:00'},
|
||||
{'id': 'Europe/Helsinki', 'offset': '+02:00'},
|
||||
{'id': 'Europe/Isle_of_Man', 'offset': '+00:00', 'name': 'Europe/Isle of Man'},
|
||||
{'id': 'Europe/Istanbul', 'offset': '+02:00'},
|
||||
{'id': 'Europe/Jersey', 'offset': '+00:00'},
|
||||
{'id': 'Europe/Kaliningrad', 'offset': '+03:00'},
|
||||
{'id': 'Europe/Kiev', 'offset': '+02:00'},
|
||||
{'id': 'Europe/Lisbon', 'offset': '+00:00'},
|
||||
{'id': 'Europe/Ljubljana', 'offset': '+01:00'},
|
||||
{'id': 'Europe/London', 'offset': '+00:00'},
|
||||
{'id': 'Europe/Luxembourg', 'offset': '+01:00'},
|
||||
{'id': 'Europe/Madrid', 'offset': '+01:00'},
|
||||
{'id': 'Europe/Malta', 'offset': '+01:00'},
|
||||
{'id': 'Europe/Mariehamn', 'offset': '+02:00'},
|
||||
{'id': 'Europe/Minsk', 'offset': '+03:00'},
|
||||
{'id': 'Europe/Monaco', 'offset': '+01:00'},
|
||||
{'id': 'Europe/Moscow', 'offset': '+03:00'},
|
||||
{'id': 'Europe/Nicosia', 'offset': '+02:00'},
|
||||
{'id': 'Europe/Oslo', 'offset': '+01:00'},
|
||||
{'id': 'Europe/Paris', 'offset': '+01:00'},
|
||||
{'id': 'Europe/Podgorica', 'offset': '+01:00'},
|
||||
{'id': 'Europe/Prague', 'offset': '+01:00'},
|
||||
{'id': 'Europe/Riga', 'offset': '+02:00'},
|
||||
{'id': 'Europe/Rome', 'offset': '+01:00'},
|
||||
{'id': 'Europe/Samara', 'offset': '+04:00'},
|
||||
{'id': 'Europe/San_Marino', 'offset': '+01:00', 'name': 'Europe/San Marino'},
|
||||
{'id': 'Europe/Sarajevo', 'offset': '+01:00'},
|
||||
{'id': 'Europe/Simferopol', 'offset': '+02:00'},
|
||||
{'id': 'Europe/Skopje', 'offset': '+01:00'},
|
||||
{'id': 'Europe/Sofia', 'offset': '+02:00'},
|
||||
{'id': 'Europe/Stockholm', 'offset': '+01:00'},
|
||||
{'id': 'Europe/Tallinn', 'offset': '+02:00'},
|
||||
{'id': 'Europe/Tirane', 'offset': '+01:00'},
|
||||
{'id': 'Europe/Tiraspol', 'offset': '+02:00'},
|
||||
{'id': 'Europe/Uzhgorod', 'offset': '+02:00'},
|
||||
{'id': 'Europe/Vaduz', 'offset': '+01:00'},
|
||||
{'id': 'Europe/Vatican', 'offset': '+01:00'},
|
||||
{'id': 'Europe/Vienna', 'offset': '+01:00'},
|
||||
{'id': 'Europe/Vilnius', 'offset': '+02:00'},
|
||||
{'id': 'Europe/Volgograd', 'offset': '+04:00'},
|
||||
{'id': 'Europe/Warsaw', 'offset': '+01:00'},
|
||||
{'id': 'Europe/Zagreb', 'offset': '+01:00'},
|
||||
{'id': 'Europe/Zaporozhye', 'offset': '+02:00'},
|
||||
{'id': 'Europe/Zurich', 'offset': '+01:00'},
|
||||
{'id': 'Indian/Antananarivo', 'offset': '+03:00'},
|
||||
{'id': 'Indian/Chagos', 'offset': '+06:00'},
|
||||
{'id': 'Indian/Christmas', 'offset': '+07:00'},
|
||||
{'id': 'Indian/Cocos', 'offset': '+06:30'},
|
||||
{'id': 'Indian/Comoro', 'offset': '+03:00'},
|
||||
{'id': 'Indian/Kerguelen', 'offset': '+05:00'},
|
||||
{'id': 'Indian/Mahe', 'offset': '+04:00'},
|
||||
{'id': 'Indian/Maldives', 'offset': '+05:00'},
|
||||
{'id': 'Indian/Mauritius', 'offset': '+04:00'},
|
||||
{'id': 'Indian/Mayotte', 'offset': '+03:00'},
|
||||
{'id': 'Indian/Reunion', 'offset': '+04:00'},
|
||||
{'id': 'Pacific/Apia', 'offset': '+13:00'},
|
||||
{'id': 'Pacific/Auckland', 'offset': '+12:00'},
|
||||
{'id': 'Pacific/Chatham', 'offset': '+12:45'},
|
||||
{'id': 'Pacific/Easter', 'offset': '-06:00'},
|
||||
{'id': 'Pacific/Efate', 'offset': '+11:00'},
|
||||
{'id': 'Pacific/Enderbury', 'offset': '+13:00'},
|
||||
{'id': 'Pacific/Fakaofo', 'offset': '+13:00'},
|
||||
{'id': 'Pacific/Fiji', 'offset': '+12:00'},
|
||||
{'id': 'Pacific/Funafuti', 'offset': '+12:00'},
|
||||
{'id': 'Pacific/Galapagos', 'offset': '-06:00'},
|
||||
{'id': 'Pacific/Gambier', 'offset': '-09:00'},
|
||||
{'id': 'Pacific/Guadalcanal', 'offset': '+11:00'},
|
||||
{'id': 'Pacific/Guam', 'offset': '+10:00'},
|
||||
{'id': 'Pacific/Honolulu', 'offset': '-10:00'},
|
||||
{'id': 'Pacific/Johnston', 'offset': '-10:00'},
|
||||
{'id': 'Pacific/Kiritimati', 'offset': '+14:00'},
|
||||
{'id': 'Pacific/Kosrae', 'offset': '+11:00'},
|
||||
{'id': 'Pacific/Kwajalein', 'offset': '+12:00'},
|
||||
{'id': 'Pacific/Majuro', 'offset': '+12:00'},
|
||||
{'id': 'Pacific/Marquesas', 'offset': '-09:30'},
|
||||
{'id': 'Pacific/Midway', 'offset': '-11:00'},
|
||||
{'id': 'Pacific/Nauru', 'offset': '+12:00'},
|
||||
{'id': 'Pacific/Niue', 'offset': '-11:00'},
|
||||
{'id': 'Pacific/Norfolk', 'offset': '+11:30'},
|
||||
{'id': 'Pacific/Noumea', 'offset': '+11:00'},
|
||||
{'id': 'Pacific/Pago_Pago', 'offset': '-11:00', 'name': 'Pacific/Pago Pago'},
|
||||
{'id': 'Pacific/Palau', 'offset': '+09:00'},
|
||||
{'id': 'Pacific/Pitcairn', 'offset': '-08:00'},
|
||||
{'id': 'Pacific/Ponape', 'offset': '+11:00'},
|
||||
{'id': 'Pacific/Port_Moresby', 'offset': '+10:00', 'name': 'Pacific/Port Moresby'},
|
||||
{'id': 'Pacific/Rarotonga', 'offset': '-10:00'},
|
||||
{'id': 'Pacific/Saipan', 'offset': '+10:00'},
|
||||
{'id': 'Pacific/Tahiti', 'offset': '-10:00'},
|
||||
{'id': 'Pacific/Tarawa', 'offset': '+12:00'},
|
||||
{'id': 'Pacific/Tongatapu', 'offset': '+13:00'},
|
||||
{'id': 'Pacific/Truk', 'offset': '+10:00'},
|
||||
{'id': 'Pacific/Wake', 'offset': '+12:00'},
|
||||
{'id': 'Pacific/Wallis', 'offset': '+12:00'}
|
||||
];
|
|
@ -0,0 +1,804 @@
|
|||
/**
|
||||
* Set of base functionality tools
|
||||
* @author Stanislav Kalashnik <sk@infomir.eu>
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/* jshint unused:false */
|
||||
|
||||
// debug print blank placeholder for release builds
|
||||
if ( window.echo === undefined ) { window.echo = function ( data, title ) {}; }
|
||||
|
||||
|
||||
/**
|
||||
* Assigns a list of attribute values to the given object
|
||||
* @param {Node|Element|HTMLElement} obj html element
|
||||
* @param {Object} attr list of attributes with values
|
||||
* @return {Node|Element|HTMLElement} the same as the given one
|
||||
* @example
|
||||
* elattr(myimg, {src:'face.png', className:'main'});
|
||||
*/
|
||||
function elattr ( obj, attr ) {
|
||||
// check if Node and valid attr list
|
||||
if ( obj && obj.nodeType && attr && attr instanceof Object ) {
|
||||
// assign attributes
|
||||
for ( var key in attr ) {
|
||||
if ( attr.hasOwnProperty(key) ) { obj[key] = attr[key]; }
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a DOM element with given options
|
||||
* @param {string} name html element name (a, img, div, ...)
|
||||
* @param {Object} [attr] list of attributes with values
|
||||
* @param {Node|Element|HTMLElement|Array|string|number} [data] inner html value
|
||||
* @return {Node|Element|HTMLElement}
|
||||
* @example
|
||||
* element('div', {}, 'some text');
|
||||
* element('div', {}, [element('span'), element('br')]);
|
||||
* element('link', {rel:'stylesheet', type:'text/css', href:'http://some.url/'});
|
||||
*/
|
||||
function element ( name, attr, data ) {
|
||||
var tag = document.createElement(name);
|
||||
// set attributes
|
||||
elattr(tag, attr);
|
||||
// add to the dom
|
||||
elchild(tag, data);
|
||||
// Node is ready
|
||||
return tag;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds the given value to the obj as a child recursively
|
||||
* @param {Node|Element|HTMLElement} obj element to be appended
|
||||
* @param value data to add (simple text values, Nodes, array of Nodes)
|
||||
* @return {Node|Element|HTMLElement} owner element of all added data
|
||||
* @example
|
||||
* elchild(mydiv, 'Hello world'); // simple text value
|
||||
* elchild(mydiv, someotherdiv); // Node
|
||||
* elchild(mydiv, [div1, div2, div3]); // Node list
|
||||
* elchild(mydiv, [div1, 'hello', 'world']); // combined case
|
||||
*/
|
||||
function elchild ( obj, value ) {
|
||||
// check input
|
||||
if ( obj && value ) {
|
||||
// Node
|
||||
if ( value.nodeType ) { obj.appendChild(value); }
|
||||
// array of Nodes of simple values
|
||||
else if ( Array.isArray(value) ) {
|
||||
for ( var i = 0; i < value.length; i++ ) { elchild(obj, value[i]); }
|
||||
}
|
||||
// simple values
|
||||
else {
|
||||
obj.appendChild(document.createTextNode(value));
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes all child elements from the given object
|
||||
* @param {Node|Element|HTMLElement} obj html element to be updated
|
||||
* @return {Node|Element|HTMLElement} cleared element
|
||||
*/
|
||||
function elclear ( obj ) {
|
||||
// check input
|
||||
if ( obj !== null && obj.nodeName && obj.hasChildNodes() ) {
|
||||
// clearing
|
||||
while ( obj.hasChildNodes() ) { obj.removeChild(obj.firstChild); }
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Loads the given JavaScript file dynamically
|
||||
* head injection method is used
|
||||
* @param {string} src file to be loaded
|
||||
* @param {Function} [onload] optional on ready handler
|
||||
* @param {Function} [onerror] optional on error handler
|
||||
*/
|
||||
function loadScript ( src, onload, onerror ) {
|
||||
// Node init
|
||||
var elem = document.createElement('script');
|
||||
elem.type = 'text/javascript';
|
||||
elem.src = src;
|
||||
// set handlers if given
|
||||
if ( typeof onload === 'function' ) { elem.onload = onload; }
|
||||
if ( typeof onerror === 'function' ) { elem.onerror = onerror; }
|
||||
// push to dom
|
||||
document.head.appendChild(elem);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the active language (from the available lang list)
|
||||
* @param {Array} [langList] the set of available valid language names
|
||||
* @return {string} lowercase lang name
|
||||
*/
|
||||
function getCurrentLanguage ( langList ) {
|
||||
var langEnv = JSON.parse(gSTB.GetEnv('{"varList":["language"]}')),
|
||||
langVal = 'en';
|
||||
// reset valid languages if necessary
|
||||
langList = langList || ['en', 'ru', 'uk', 'de', 'tr'/*, 'el', 'es', 'bg'*/];
|
||||
// no errors and valid string
|
||||
if ( !langEnv.errMsg && langEnv.result && langEnv.result.language && typeof langEnv.result.language === 'string' ) {
|
||||
// make sure it is lowercase
|
||||
langEnv.result.language = langEnv.result.language.toLowerCase();
|
||||
// the lang is from valid list
|
||||
if ( langList.indexOf(langEnv.result.language) !== -1 ) { langVal = langEnv.result.language; }
|
||||
}
|
||||
return langVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of environment variables
|
||||
* @param {Array} list array of variables names
|
||||
* @returns {Object|boolean} return hash if all alright and false if there is some error
|
||||
*/
|
||||
function getEnvList ( list ) {
|
||||
var data;
|
||||
try {
|
||||
data = JSON.parse(gSTB.GetEnv(JSON.stringify({varList: list})));
|
||||
if ( data.errMsg !== '' ) {
|
||||
throw new Error(data.errMsg);
|
||||
}
|
||||
return data.result;
|
||||
} catch ( e ) {
|
||||
echo(e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return specified environment variable
|
||||
* @param {string} name variable name
|
||||
* @returns {Object|boolean} return string with if all alright
|
||||
*/
|
||||
function getEnv ( name ) {
|
||||
var data = getEnvList([name]);
|
||||
if ( data !== false && data[name] !== undefined ) {
|
||||
return data[name];
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set some environment variable
|
||||
* @param {string} key variable name
|
||||
* @param {string|number} value variable value
|
||||
* @returns {boolean} true if all alright and false if there is some error
|
||||
*/
|
||||
function setEnv ( key, value ) {
|
||||
var vars = {};
|
||||
vars[key] = value;
|
||||
return setEnvList(vars);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set several environment variables
|
||||
* @param {Object} hash object where key is the variable name and the value is the variable value
|
||||
* @returns {boolean} true if all alright and false if there is some error
|
||||
*/
|
||||
function setEnvList ( hash ) {
|
||||
try {
|
||||
return gSTB.SetEnv(JSON.stringify(hash));
|
||||
} catch ( e ) {
|
||||
echo(e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare global event object - add the real key code
|
||||
* should be called only once at the beginning of events chain
|
||||
* with shift key pressed +1000
|
||||
* with alt key pressed +2000
|
||||
* @param {Event} event object to be altered
|
||||
* @param {boolean} [stopBubble=true] stops all propagation of the event in the bubbling phase
|
||||
* @param {string} [label] optional text info for debug
|
||||
* @returns {boolean} true - valid event; false - phantom and should be skipped
|
||||
*/
|
||||
function eventPrepare ( event, stopBubble, label ) {
|
||||
// prevent double invoke
|
||||
if ( event.code !== undefined ) { return true; }
|
||||
// determine real key code
|
||||
event.code = event.keyCode || event.which;
|
||||
// filter phantoms
|
||||
if ( event.code === 0 ) { return false; }
|
||||
// apply key modifiers
|
||||
if ( event.shiftKey ) { event.code += 1000; }
|
||||
if ( event.altKey ) { event.code += 2000; }
|
||||
// stop deprecated usb event
|
||||
if ( event.code === KEYS.USB_MOUNTED || event.code === KEYS.USB_UNMOUNTED ) { return false; }
|
||||
// stop bubbling if necessary
|
||||
if ( stopBubble !== false ) { event.stopPropagation(); }
|
||||
// debug
|
||||
echo(event.code + '\t' + event.shiftKey + '\t' + event.ctrlKey + '\t' + event.altKey + '\t' + event.srcElement.id + '\t' + event.target.id + '\t' + (label || ''),
|
||||
'keyDown [code/shift/ctrl/alt/src/target]');
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// global flag to prevent ajax queries
|
||||
ajax.stop = false;
|
||||
|
||||
/**
|
||||
* Ajax request
|
||||
* @param {string} method "post", "get" or "head"
|
||||
* @param {string} url address
|
||||
* @param {Function} callback on
|
||||
* @param {Object} [headers] list of optional headers like "charset", "Content-Type" and so on
|
||||
* @param {string} [type=text] data parsing mode: plain text (default), xml, json
|
||||
* @param {boolean} [async=true] send asynchronous request
|
||||
* @param {number} [abortTimeout=60000] timeout before request abort (ms)
|
||||
*
|
||||
* @return {XMLHttpRequest} request object in case response headers are necessary
|
||||
*
|
||||
* @example
|
||||
* ajax('get', 'https://google.com/', function(data, status){console.info(data, status);}, {charset:'utf-8'})
|
||||
*/
|
||||
function ajax ( method, url, callback, headers, type, async, abortTimeout ) {
|
||||
var jdata = null,
|
||||
timeout = null,
|
||||
xhr = new XMLHttpRequest(),
|
||||
title = 'AJAX ' + method.toUpperCase() + ' ' + url,
|
||||
hname, aborted;
|
||||
|
||||
async = async !== false;
|
||||
abortTimeout = abortTimeout || 60000;
|
||||
|
||||
xhr.onreadystatechange = function () {
|
||||
if ( xhr.readyState === 4 ) {
|
||||
if ( aborted ) {
|
||||
return;
|
||||
}
|
||||
clearTimeout(timeout);
|
||||
if ( ajax.stop ) {
|
||||
echo(xhr.status, title);
|
||||
if ( typeof callback === 'function' ) {
|
||||
callback(null, null, null);
|
||||
}
|
||||
} else {
|
||||
echo('status:' + xhr.status + ', length:' + xhr.responseText.length, title);
|
||||
if ( type === 'json' && xhr.status === 200 ) {
|
||||
try {
|
||||
jdata = JSON.parse(xhr.responseText);
|
||||
} catch ( e ) {
|
||||
echo(e, 'AJAX JSON.parse');
|
||||
jdata = null;
|
||||
}
|
||||
}
|
||||
if ( typeof callback === 'function' ) {
|
||||
callback(type === 'xml' ? xhr.responseXML : (type === 'json' ? jdata : xhr.responseText), xhr.status, xhr);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
xhr.open(method, url, async);
|
||||
|
||||
// set headers if present
|
||||
if ( headers ) {
|
||||
for ( hname in headers ) {
|
||||
if ( headers.hasOwnProperty(hname) ) {
|
||||
xhr.setRequestHeader(hname, headers[hname]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// abort after some time (60s)
|
||||
timeout = setTimeout(function () {
|
||||
echo('ABORT on timeout', title);
|
||||
if ( typeof callback === 'function' ) {
|
||||
callback(null, 0);
|
||||
// no readystatechange event should be dispatched after xhr.abort() (https://xhr.spec.whatwg.org/#dom-xmlhttprequest-abort)
|
||||
// so we need to fix this bug and prevent multiple call of same callback for our stb
|
||||
aborted = true;
|
||||
}
|
||||
xhr.abort();
|
||||
}, abortTimeout);
|
||||
|
||||
xhr.send();
|
||||
echo('sent', title);
|
||||
|
||||
|
||||
return xhr;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Preloads a list of images and executes the given callback at the end
|
||||
* @param {Array} imgList images to load
|
||||
* @param {Function} [callback]
|
||||
*/
|
||||
function imageLoader ( imgList, callback ) {
|
||||
var count = 1, // loading image number
|
||||
onload = function () {
|
||||
// all images are loaded and there is a valid callback
|
||||
if ( imgList.length === count && typeof callback === 'function' ) {
|
||||
callback();
|
||||
} else {
|
||||
count++;
|
||||
}
|
||||
};
|
||||
// create set of loading images
|
||||
if ( Array.isArray(imgList) && imgList.length > 0 ) {
|
||||
imgList.forEach(function ( item ) {
|
||||
var img = new Image();
|
||||
img.src = item;
|
||||
img.onload = onload;
|
||||
img.onerror = function(){
|
||||
throw ('Image ' + item + ' load fail');
|
||||
};
|
||||
});
|
||||
} else if ( imgList.length === 0 ) {
|
||||
// nothing was given so fire at once
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* get the list of all storages (external usb and internal hdd)
|
||||
*/
|
||||
function getStorageInfo () {
|
||||
var snList = {}; // set of all serial numbers with amount of partitions on each
|
||||
|
||||
// get mount points
|
||||
var info = JSON.parse(gSTB.GetStorageInfo('{}'));
|
||||
// valid non-empty data
|
||||
if ( Array.isArray(info.result) && info.errMsg === '' && info.result.length > 0 ) {
|
||||
info.result.forEach(function ( item ) {
|
||||
// SD card-reader support
|
||||
item.mediaType = item.sn === '000022272228' ? 3 : item.mediaType;
|
||||
|
||||
item.label = item.label.trim();
|
||||
if ( snList[item.sn] ) {
|
||||
snList[item.sn]++;
|
||||
} else {
|
||||
snList[item.sn] = 1;
|
||||
}
|
||||
});
|
||||
info.result.forEach(function ( item ) {
|
||||
if ( !item.label ) {
|
||||
item.label = item.vendor + ' ' + item.model.replace(/\//, '');
|
||||
if ( snList[item.sn] > 1 ) {
|
||||
item.label += ' #' + item.partitionNum;
|
||||
}
|
||||
}
|
||||
|
||||
if ( item.isReadOnly ) {
|
||||
item.label += ' (' + _('Read only') + ')';
|
||||
}
|
||||
});
|
||||
// sort by mount path
|
||||
info.result.sort(function ( a, b ) {
|
||||
return a.mountPath < b.mountPath ? -1 : 1;
|
||||
});
|
||||
// final list of all combined data
|
||||
window.STORAGE_INFO = info.result;
|
||||
} else {
|
||||
// reset if error
|
||||
window.STORAGE_INFO = [];
|
||||
}
|
||||
|
||||
// get hdd data
|
||||
try {
|
||||
window.HDD_INFO = JSON.parse(gSTB.RDir('get_hdd_info') || '[]');
|
||||
} catch ( e ) {
|
||||
echo(e, 'get_hdd_info');
|
||||
window.HDD_INFO = [];
|
||||
}
|
||||
|
||||
echo(STORAGE_INFO, 'STORAGE_INFO');
|
||||
echo(HDD_INFO, 'HDD_INFO');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* URL parsing tool
|
||||
* (c) Steven Levithan <stevenlevithan.com>
|
||||
* MIT License
|
||||
*/
|
||||
function parseUri ( str ) {
|
||||
var o = parseUri.options,
|
||||
m = o.parser[o.strictMode ? 'strict' : 'loose'].exec(str),
|
||||
uri = {},
|
||||
i = 14;
|
||||
|
||||
while ( i-- ) { uri[o.key[i]] = m[i] || ''; }
|
||||
|
||||
uri[o.q.name] = {};
|
||||
uri[o.key[12]].replace(o.q.parser, function ( $0, $1, $2 ) {
|
||||
if ( $1 ) { uri[o.q.name][$1] = $2; }
|
||||
});
|
||||
|
||||
return uri;
|
||||
}
|
||||
|
||||
parseUri.options = {
|
||||
strictMode: false,
|
||||
key : ['source', 'protocol', 'authority', 'userInfo', 'user', 'password', 'host', 'port', 'relative', 'path', 'directory', 'file', 'query', 'anchor'],
|
||||
q : {
|
||||
name : 'queryKey',
|
||||
parser: /(?:^|&)([^&=]*)=?([^&]*)/g
|
||||
},
|
||||
parser : {
|
||||
strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,
|
||||
loose : /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate whether the passed string is valid URL.
|
||||
*
|
||||
* @param {string} url - URL for validate
|
||||
* @param {boolean} [useSolution=false] - may solution be appeared in URL
|
||||
* @param {boolean} [isSolutionOptional=false] - is solution optional or not
|
||||
* @param {boolean} [isProtocolOptional=true] - is protocol scheme optional or not
|
||||
*
|
||||
* @return {boolean} validation result
|
||||
*/
|
||||
function validateUrl ( url, useSolution, isSolutionOptional, isProtocolOptional ) {
|
||||
var allowedSolutions = '((ffmpeg|auto|rtp|rtsp|mp3|mpegps|mpegts|mp4|ifm|fm|ffrt|ffrt2|ffrt3|ffrt4)(\\s))',
|
||||
allowedProtocols = '(http|https|udp|srt|rtp|rtsp|mms|mmsh|mmst|rtmp|igmp)';
|
||||
|
||||
useSolution = typeof useSolution === 'boolean' ? useSolution : false;
|
||||
isSolutionOptional = typeof isSolutionOptional === 'boolean' ? isSolutionOptional : false;
|
||||
isProtocolOptional = typeof isProtocolOptional === 'boolean' ? isProtocolOptional : true;
|
||||
|
||||
return new RegExp(
|
||||
'^' + (useSolution ? allowedSolutions : '') + (isSolutionOptional ? '?' : '') +
|
||||
'((' + allowedProtocols + ':\\/\\/)' + (isProtocolOptional ? '?' : '') +
|
||||
"@?(([a-zA-Z0-9\\u0400-\\u04FF$\\-_.+!*'(),;:&=]|%[0-9a-fA-F\\u0400-\\u04FF]{2})+@)?(((25[0-5]|2[0-4][0-9]|[0-1][0-9][0-9]|[1-9][0-9]|[0-9])(\\.(25[0-5]|2[0-4][0-9]|[0-1][0-9][0-9]|[1-9][0-9]|[0-9])){3})|localhost|([a-zA-Z\\u0400-\\u04FF0-9\\-\\u00C0-\\u017F]+\\.)+([a-zA-Z\\u0400-\\u04FF]{2,}))(:[0-9]+)?|((file:\/\/\/|\/)[a-zA-Z\\u0400-\\u04FF0-9\\-\\u00C0-\\u017F]+))(\\/(([a-zA-Z\\u0400-\\u04FF0-9$\\-_.+!*'(),;:@&=\\s]|%[0-9a-fA-F\\u0400-\\u04FF]{2})*(\\/([a-zA-Z\\u0400-\\u04FF0-9$\\-_.+!*'(),;:@&=\\s]|%[0-9a-fA-F\\u0400-\\u04FF]{2})*)*)?(\\?([a-zA-Z\\u0400-\\u04FF0-9$\\-_.+!*'(),;:@&=\\/?]|%[0-9a-fA-F\\u0400-\\u04FF]{2})*)?(\\#([a-zA-Z\\u0400-\\u04FF0-9$\\-_.+!*'(),;:@&=\\/?]|%[0-9a-fA-F\\u0400-\\u04FF]{2})*)?)?$"
|
||||
).test(url);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Combines two objects and write result to target object
|
||||
* @param {Object} target object to which the data will be transferred
|
||||
* @param {Object} source object from which the data will be transferred
|
||||
* @param [override = true] if set to false target object not rewriting, result of combining returns from function
|
||||
* @returns {Object} combined object
|
||||
*/
|
||||
function extend (target, source, override) {
|
||||
var _target = (override === false ? extend({}, target) : target || {});
|
||||
for (var prop in source) {
|
||||
if ( typeof _target[prop] === 'object' && typeof source[prop] === 'object' && !Array.isArray(_target[prop]) && !Array.isArray(source[prop]) ) {
|
||||
_target[prop] = extend(_target[prop], source[prop], override);
|
||||
} else {
|
||||
_target[prop] = source[prop];
|
||||
}
|
||||
}
|
||||
return _target;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts the given string to utf8 and then to base64
|
||||
* @param {string} data string to convert
|
||||
* @return {string}
|
||||
*/
|
||||
function base64Encode ( data ) {
|
||||
return btoa(unescape(encodeURIComponent(data)));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts the given string from base64 to utf8 and then to utf16
|
||||
* @param {string} data string to convert
|
||||
* @return {string}
|
||||
*/
|
||||
function base64Decode ( data ) {
|
||||
try {
|
||||
return decodeURIComponent(escape(atob(data)));
|
||||
} catch(error) {
|
||||
echo(error, 'base64Decode Error');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ( !('classList' in document.documentElement) ) {
|
||||
var prototype = Array.prototype,
|
||||
indexOf = prototype.indexOf,
|
||||
slice = prototype.slice,
|
||||
push = prototype.push,
|
||||
splice = prototype.splice,
|
||||
join = prototype.join;
|
||||
|
||||
window.DOMTokenList = function(el) {
|
||||
this._element = el;
|
||||
if (el.className !== this._classCache) {
|
||||
this._classCache = el.className;
|
||||
if (!this._classCache) { return; }
|
||||
var classes = this._classCache.replace(/^\s+|\s+$/g,'').split(/\s+/),
|
||||
i;
|
||||
for (i = 0; i < classes.length; i++) {
|
||||
push.call(this, classes[i]);
|
||||
}
|
||||
}
|
||||
};
|
||||
window.DOMTokenList.prototype = {
|
||||
add: function(token) {
|
||||
if(this.contains(token)) {
|
||||
return;
|
||||
}
|
||||
push.call(this, token);
|
||||
this._element.className = slice.call(this, 0).join(' ');
|
||||
},
|
||||
contains: function(token) {
|
||||
return indexOf.call(this, token) !== -1;
|
||||
},
|
||||
item: function(index) {
|
||||
return this[index] || null;
|
||||
},
|
||||
remove: function(token) {
|
||||
var i = indexOf.call(this, token);
|
||||
if (i === -1) {
|
||||
return;
|
||||
}
|
||||
splice.call(this, i, 1);
|
||||
this._element.className = slice.call(this, 0).join(' ');
|
||||
},
|
||||
toString: function() {
|
||||
return join.call(this, ' ');
|
||||
},
|
||||
toggle: function(token) {
|
||||
if (!this.contains(token)) {
|
||||
this.add(token);
|
||||
} else {
|
||||
this.remove(token);
|
||||
}
|
||||
return this.contains(token);
|
||||
}
|
||||
};
|
||||
|
||||
Object.defineProperty(Element.prototype, 'classList',{
|
||||
get: function () {
|
||||
return new window.DOMTokenList(this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Object represents simple event model
|
||||
* @type {{ bind: Function, trigger: Function, inject: Function}}
|
||||
*/
|
||||
var Events = {
|
||||
/**
|
||||
* Assign new event to the current object
|
||||
* @param {string|Object} event Event name or Object where the key is event name and value is handler
|
||||
* @param {Function} callback Function that will be executed when event was triggered
|
||||
*/
|
||||
bind: function ( event, callback ) {
|
||||
this._events || (this._events = {});
|
||||
if ( typeof event === 'object' ) {
|
||||
for ( var name in event ) {
|
||||
this.bind(name, event[name]);
|
||||
}
|
||||
} else if ( typeof event === 'string' && typeof callback === 'function' ) {
|
||||
if ( this._events[event] === undefined ) {
|
||||
this._events[event] = [];
|
||||
}
|
||||
this._events[event].push(callback);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Trigger some event
|
||||
* @param {string} event Name of events which will be triggered
|
||||
*/
|
||||
trigger: function ( event, data ) {
|
||||
var result, results = [], self = this;
|
||||
if ( event !== undefined && this._events !== undefined && this._events[event] !== undefined ) {
|
||||
this._events[event].forEach(function ( ev ) {
|
||||
result = ev.call(self, data);
|
||||
if ( result !== undefined ) { results.push(result); }
|
||||
}
|
||||
);
|
||||
}
|
||||
return results;
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove event handlers for specified event
|
||||
* @param {string} event Name of removed event
|
||||
*/
|
||||
unbind: function(event){
|
||||
delete this._events[event];
|
||||
},
|
||||
|
||||
/**
|
||||
* Inject current functionality to another object or function
|
||||
* @param {Object|Function} obj Object which is embedded functionality
|
||||
*/
|
||||
inject: function( obj ){
|
||||
if (typeof obj === 'function'){
|
||||
extend(obj.prototype, this);
|
||||
}else if (typeof obj === 'object'){
|
||||
extend(obj, this);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Cuts to the specified number of chars and append an ellipsis
|
||||
* @param {string} text String to cut
|
||||
* @param {number} length The length to which you want to truncate the string
|
||||
* @returns {string}
|
||||
*/
|
||||
function cutTextWithEllipsis ( text, length ) {
|
||||
if ( text.length > length ) {
|
||||
return text.toString().slice(0, length) + '...';
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parse date string and return date without time zone
|
||||
* @param {string} dateStr date string in EST or UTC format
|
||||
*/
|
||||
var timeZoneOffsets = {'A':{'index':-1,'minutes':60},'ACDT':{'index':-1,'minutes':60},'ACST':{'index':-1,'minutes':570},
|
||||
'ACT':{'index':1,'minutes':300},'ACWST':{'index':-1,'minutes':525},'ADT':{'index':1,'minutes':180},
|
||||
'AEDT':{'index':-1,'minutes':61},'AEST':{'index':-1,'minutes':60},'AFT':{'index':-1,'minutes':270},
|
||||
'AKDT':{'index':1,'minutes':480},'AKST':{'index':1,'minutes':540},'ALMT':{'index':-1,'minutes':360},
|
||||
'AMST':{'index':1,'minutes':180},'AMT':{'index':1,'minutes':240},'ANAST':{'index':-1,'minutes':62},
|
||||
'ANAT':{'index':-1,'minutes':62},'AQTT':{'index':-1,'minutes':300},'ART':{'index':1,'minutes':180},
|
||||
'AST':{'index':1,'minutes':240},'AWDT':{'index':-1,'minutes':540},'AWST':{'index':-1,'minutes':480},
|
||||
'AZOST':{'index':0,'minutes':0},'AZOT':{'index':1,'minutes':60},'AZST':{'index':-1,'minutes':300},
|
||||
'AZT':{'index':-1,'minutes':240},'B':{'index':-1,'minutes':120},'BNT':{'index':-1,'minutes':480},
|
||||
'BOT':{'index':1,'minutes':240},'BRST':{'index':1,'minutes':120},'BRT':{'index':1,'minutes':180},
|
||||
'BST':{'index':-1,'minutes':60},'BTT':{'index':-1,'minutes':360},'C':{'index':-1,'minutes':180},
|
||||
'CAST':{'index':-1,'minutes':480},'CAT':{'index':-1,'minutes':120},'CCT':{'index':-1,'minutes':390},
|
||||
'CDT':{'index':1,'minutes':300},'CEST':{'index':-1,'minutes':120},'CET':{'index':-1,'minutes':60},
|
||||
'CHADT':{'index':-1,'minutes':63},'CHAST':{'index':-1,'minutes':62},'CHOT':{'index':-1,'minutes':480},
|
||||
'CHUT':{'index':-1,'minutes':600},'CKT':{'index':1,'minutes':60},'CLST':{'index':1,'minutes':180},
|
||||
'CLT':{'index':1,'minutes':240},'COT':{'index':1,'minutes':300},'CST':{'index':1,'minutes':360},
|
||||
'CVT':{'index':1,'minutes':60},'CXT':{'index':-1,'minutes':420},'ChST':{'index':-1,'minutes':60},
|
||||
'D':{'index':-1,'minutes':240},'DAVT':{'index':-1,'minutes':420},'E':{'index':-1,'minutes':300},
|
||||
'EASST':{'index':1,'minutes':300},'EAST':{'index':1,'minutes':360},'EAT':{'index':-1,'minutes':180},
|
||||
'ECT':{'index':1,'minutes':300},'EDT':{'index':-1,'minutes':61},'EEST':{'index':-1,'minutes':180},
|
||||
'EET':{'index':-1,'minutes':120},'EGST':{'index':0,'minutes':0},'EGT':{'index':1,'minutes':60},
|
||||
'EST':{'index':1,'minutes':300},'ET':{'index':1,'minutes':300},'F':{'index':-1,'minutes':360},
|
||||
'FET':{'index':-1,'minutes':180},'FJST':{'index':-1,'minutes':63},'FJT':{'index':-1,'minutes':62},
|
||||
'FKST':{'index':1,'minutes':180},'FKT':{'index':1,'minutes':240},'FNT':{'index':1,'minutes':120},
|
||||
'G':{'index':-1,'minutes':420},'GALT':{'index':1,'minutes':360},'GAMT':{'index':1,'minutes':540},
|
||||
'GET':{'index':-1,'minutes':240},'GFT':{'index':1,'minutes':180},'GILT':{'index':-1,'minutes':62},
|
||||
'GMT':{'index':0,'minutes':0},'GST':{'index':-1,'minutes':240},'GYT':{'index':1,'minutes':240},
|
||||
'H':{'index':-1,'minutes':480},'HAA':{'index':1,'minutes':180},'HAC':{'index':1,'minutes':300},
|
||||
'HADT':{'index':1,'minutes':540},'HAE':{'index':1,'minutes':240},'HAP':{'index':1,'minutes':420},
|
||||
'HAR':{'index':1,'minutes':360},'HAST':{'index':1,'minutes':60},'HAT':{'index':1,'minutes':150},
|
||||
'HAY':{'index':1,'minutes':480},'HKT':{'index':-1,'minutes':480},'HLV':{'index':1,'minutes':270},
|
||||
'HNA':{'index':1,'minutes':240},'HNC':{'index':1,'minutes':360},'HNE':{'index':1,'minutes':300},
|
||||
'HNP':{'index':1,'minutes':480},'HNR':{'index':1,'minutes':420},'HNT':{'index':1,'minutes':210},
|
||||
'HNY':{'index':1,'minutes':540},'HOVT':{'index':-1,'minutes':420},'I':{'index':-1,'minutes':540},
|
||||
'ICT':{'index':-1,'minutes':420},'IDT':{'index':-1,'minutes':180},'IOT':{'index':-1,'minutes':360},
|
||||
'IRDT':{'index':-1,'minutes':270},'IRKST':{'index':-1,'minutes':540},'IRKT':{'index':-1,'minutes':540},
|
||||
'IRST':{'index':-1,'minutes':210},'IST':{'index':-1,'minutes':60},'JST':{'index':-1,'minutes':540},
|
||||
'K':{'index':-1,'minutes':60},'KGT':{'index':-1,'minutes':360},'KOST':{'index':-1,'minutes':660},
|
||||
'KRAST':{'index':-1,'minutes':480},'KRAT':{'index':-1,'minutes':480},'KST':{'index':-1,'minutes':540},
|
||||
'KUYT':{'index':-1,'minutes':240},'L':{'index':-1,'minutes':61},'LHDT':{'index':-1,'minutes':61},
|
||||
'LHST':{'index':-1,'minutes':60},'LINT':{'index':-1,'minutes':64},'M':{'index':-1,'minutes':62},
|
||||
'MAGST':{'index':-1,'minutes':62},'MAGT':{'index':-1,'minutes':62},'MART':{'index':1,'minutes':570},
|
||||
'MAWT':{'index':-1,'minutes':300},'MDT':{'index':1,'minutes':360},'MESZ':{'index':-1,'minutes':120},
|
||||
'MEZ':{'index':-1,'minutes':60},'MHT':{'index':-1,'minutes':62},'MMT':{'index':-1,'minutes':390},
|
||||
'MSD':{'index':-1,'minutes':240},'MSK':{'index':-1,'minutes':240},'MST':{'index':1,'minutes':420},
|
||||
'MUT':{'index':-1,'minutes':240},'MVT':{'index':-1,'minutes':300},'MYT':{'index':-1,'minutes':480},
|
||||
'N':{'index':1,'minutes':60},'NCT':{'index':-1,'minutes':61},'NDT':{'index':1,'minutes':150},
|
||||
'NFT':{'index':-1,'minutes':61},'NOVST':{'index':-1,'minutes':420},'NOVT':{'index':-1,'minutes':360},
|
||||
'NPT':{'index':-1,'minutes':345},'NRT':{'index':-1,'minutes':720},'NST':{'index':1,'minutes':210},
|
||||
'NUT':{'index':1,'minutes':61},'NZDT':{'index':-1,'minutes':63},'NZST':{'index':-1,'minutes':62},
|
||||
'O':{'index':1,'minutes':120},'OMSST':{'index':-1,'minutes':420},'OMST':{'index':-1,'minutes':420},
|
||||
'ORAT':{'index':-1,'minutes':300},'P':{'index':1,'minutes':180},'PDT':{'index':1,'minutes':420},
|
||||
'PET':{'index':1,'minutes':300},'PETST':{'index':-1,'minutes':62},'PETT':{'index':-1,'minutes':62},
|
||||
'PGT':{'index':-1,'minutes':60},'PHOT':{'index':-1,'minutes':63},'PHT':{'index':-1,'minutes':480},
|
||||
'PKT':{'index':-1,'minutes':300},'PMDT':{'index':1,'minutes':120},'PMST':{'index':1,'minutes':180},
|
||||
'PONT':{'index':-1,'minutes':61},'PST':{'index':1,'minutes':480},'PT':{'index':1,'minutes':480},
|
||||
'PWT':{'index':-1,'minutes':540},'PYST':{'index':1,'minutes':180},'PYT':{'index':1,'minutes':240},
|
||||
'Q':{'index':1,'minutes':240},'QYZT':{'index':1,'minutes':360},'R':{'index':1,'minutes':300},
|
||||
'RET':{'index':-1,'minutes':240},'S':{'index':1,'minutes':360},'SAKT':{'index':1,'minutes':600},
|
||||
'SAMT':{'index':-1,'minutes':240},'SAST':{'index':-1,'minutes':120},'SBT':{'index':-1,'minutes':61},
|
||||
'SCT':{'index':-1,'minutes':240},'SGT':{'index':-1,'minutes':480},'SRET':{'index':-1,'minutes':660},
|
||||
'SRT':{'index':1,'minutes':180},'SST':{'index':1,'minutes':61},'T':{'index':1,'minutes':420},
|
||||
'TAHT':{'index':1,'minutes':60},'TFT':{'index':-1,'minutes':300},'TJT':{'index':-1,'minutes':300},
|
||||
'TKT':{'index':-1,'minutes':63},'TLT':{'index':-1,'minutes':540},'TMT':{'index':-1,'minutes':300},
|
||||
'TOT':{'index':-1,'minutes':780},'TVT':{'index':-1,'minutes':62},'U':{'index':1,'minutes':480},
|
||||
'ULAT':{'index':-1,'minutes':480},'UTC':{'index':0,'minutes':0},'UYST':{'index':1,'minutes':120},
|
||||
'UYT':{'index':1,'minutes':180},'UZT':{'index':-1,'minutes':300},'V':{'index':1,'minutes':540},
|
||||
'VET':{'index':1,'minutes':270},'VLAST':{'index':-1,'minutes':61},'VLAT':{'index':-1,'minutes':61},
|
||||
'VUT':{'index':-1,'minutes':61},'W':{'index':1,'minutes':60},'WARST':{'index':1,'minutes':180},
|
||||
'WAST':{'index':-1,'minutes':120},'WAT':{'index':-1,'minutes':60},'WEST':{'index':-1,'minutes':60},
|
||||
'WESZ':{'index':-1,'minutes':60},'WET':{'index':0,'minutes':0},'WEZ':{'index':0,'minutes':0},
|
||||
'WFT':{'index':-1,'minutes':62},'WGST':{'index':1,'minutes':120},'WGT':{'index':1,'minutes':180},
|
||||
'WIB':{'index':-1,'minutes':420},'WIT':{'index':-1,'minutes':540},'WITA':{'index':-1,'minutes':480},
|
||||
'WST':{'index':-1,'minutes':63},'WT':{'index':0,'minutes':0},'X':{'index':1,'minutes':61},
|
||||
'Y':{'index':1,'minutes':62},'YAKST':{'index':-1,'minutes':60},'YAKT':{'index':-1,'minutes':60},
|
||||
'YAPT':{'index':-1,'minutes':60},'YEKST':{'index':-1,'minutes':360},'YEKT':{'index':-1,'minutes':360},
|
||||
'Z':{'index':0,'minutes':0},'-12':{'index':-1,'minutes':720},'-11':{'index':-1,'minutes':660},
|
||||
'-10':{'index':-1,'minutes':600},'-09':{'index':-1,'minutes':540},'-08':{'index':-1,'minutes':480},
|
||||
'-07':{'index':-1,'minutes':420},'-06':{'index':-1,'minutes':360},'-05':{'index':-1,'minutes':300},
|
||||
'-04':{'index':-1,'minutes':240},'-03':{'index':-1,'minutes':180},'-02':{'index':-1,'minutes':120},
|
||||
'-01':{'index':-1,'minutes':60},'+01':{'index':1,'minutes':60},'+02':{'index':1,'minutes':120},
|
||||
'+03':{'index':1,'minutes':180},'+04':{'index':1,'minutes':240},'+05':{'index':1,'minutes':300},
|
||||
'+06':{'index':1,'minutes':360},'+07':{'index':1,'minutes':420},'+08':{'index':1,'minutes':480},
|
||||
'+09':{'index':1,'minutes':540},'+10':{'index':1,'minutes':600},'+11':{'index':1,'minutes':660},
|
||||
'+12':{'index':1,'minutes':720}};
|
||||
|
||||
/**
|
||||
* Get a Date object from given string
|
||||
* @param {string} dateStr String which contains date
|
||||
* @return {Date} Object if dateStr is normal date, else return Invalid Date object
|
||||
* TODO: if each incoming dateStr will be in the format such as 'Thu Nov 13 18:23:05 EET 2014',
|
||||
* where EET - is timezone, and there is so much this function calls,
|
||||
* parsing dateStr can be a little faster with using split lines on the ' ' to array,
|
||||
* where 4 index is timezone
|
||||
*/
|
||||
function parseDate ( dateStr ) {
|
||||
if ( !dateStr ) { return new Date(NaN);}
|
||||
|
||||
var date = new Date(dateStr.match(/\w{3}\s\w{3}\s\d{1,2}\s\d{1,2}:\d{1,2}:\d{1,2}/) + ' ' + dateStr.match(/\d{4}/)),
|
||||
timeZone = dateStr.match(/(\w{1,4}|((\+|-)\d{1,2}))\s+\d{4}/)[1],
|
||||
offset = timeZoneOffsets[timeZone];
|
||||
|
||||
date.setMinutes(date.getMinutes() - (date.getTimezoneOffset())); // remove influence of device local time zone
|
||||
if ( offset.index !== 0 ) {
|
||||
date.setMinutes(date.getMinutes() + (offset.minutes || 0) * offset.index);
|
||||
}
|
||||
return date;
|
||||
}
|
||||
|
||||
|
||||
function getMediaType ( file ) {
|
||||
var extension = file.split('.').pop().toLowerCase();
|
||||
if ( extension && configuration.registersTypes.indexOf(extension) !== -1 ) {
|
||||
for ( var i = 0, type; i < MEDIA_TYPES.length, type = MEDIA_TYPES[i]; i++ ) {
|
||||
if ( MEDIA_EXTENSION[type].indexOf(extension) !== -1 ) { return type; }
|
||||
}
|
||||
}
|
||||
return MEDIA_TYPE_NONE;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generic randomize function
|
||||
*/
|
||||
Array.prototype.shuffle = function () {
|
||||
var n = this.length,
|
||||
i, tmp;
|
||||
while ( n-- ) {
|
||||
i = Math.floor(n * Math.random());
|
||||
tmp = this[i];
|
||||
this[i] = this[n];
|
||||
this[n] = tmp;
|
||||
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
function readJSON ( path, fileName ) {
|
||||
var result,
|
||||
xmlhttp = new XMLHttpRequest();
|
||||
|
||||
path = path + '/' + fileName;
|
||||
|
||||
try {
|
||||
xmlhttp.open('GET', path, false);
|
||||
xmlhttp.send(null);
|
||||
result = xmlhttp.responseText;
|
||||
} catch ( e ) {
|
||||
echo(e, 'some xmlhttp error');
|
||||
result = '';
|
||||
}
|
||||
echo(result, 'file ' + path);
|
||||
return result;
|
||||
}
|
|
@ -0,0 +1,353 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* List of fatal error statuses
|
||||
*/
|
||||
Update.CrashStatuses = [1, 2, 3, 4, 7, 17, 19, 20, 22, 25, 27/*, 28, 29*/];
|
||||
|
||||
/**
|
||||
* Ready to update status
|
||||
* @type {number}
|
||||
*/
|
||||
Update.OkStatus = 21;
|
||||
|
||||
/**
|
||||
* Text representations of update statuses
|
||||
*/
|
||||
Update.StatusMessages = {
|
||||
1: "Signature init error",
|
||||
2: "Wrong device model",
|
||||
3: "Section size exceeds partition size on FLASH",
|
||||
4: "Required FLASH section not found. Aborting update",
|
||||
5: "Updating kernel",
|
||||
6: "Updating image",
|
||||
7: "Internal error",
|
||||
8: "Inspecting firmware",
|
||||
9: "Updating environment variables",
|
||||
10: "Updating Bootstrap section",
|
||||
11: "Skipping Bootstrap section",
|
||||
12: "Updating User FS section",
|
||||
13: "Skipping User FS section",
|
||||
14: "Updating second boot",
|
||||
15: "Updating logotype",
|
||||
16: "Update finished OK",
|
||||
17: "Wrong signature",
|
||||
18: "Erasing flash section",
|
||||
19: "Flash write error",
|
||||
20: "File write error",
|
||||
21: "Idle",
|
||||
22: "Invalid file header",
|
||||
23: "Inspecting update file",
|
||||
24: "File check finished",
|
||||
25: "File not found",
|
||||
26: "Initialising",
|
||||
27: "Read error"/*,
|
||||
28: "Loader error",
|
||||
29: "Storage not ready"*/
|
||||
};
|
||||
|
||||
/**
|
||||
* Component that is responsible for updating the device
|
||||
* @param {Object} events List of event handlers (existing events: onCheck, onProgress, onError, onStart, onReady)
|
||||
* @constructor
|
||||
*/
|
||||
function Update ( events ) {
|
||||
this.bind(events);
|
||||
this.check_url = this.update_url = '';
|
||||
this.checked = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update device by TFTP
|
||||
* @param {number} [timeout] delay before start updating
|
||||
* @returns {boolean} return true if validation is passed
|
||||
*/
|
||||
Update.StartTFTP = function ( timeout ) {
|
||||
var bootstrap_url = parseUri(Update.DefaultUpdateUrls.bootstrap_url),
|
||||
lan_params = ["ipaddr", "gatewayip", "dnsip", "netmask", "ipaddr_conf"],
|
||||
lan_config = getEnvList(lan_params),
|
||||
type = "up_dhcp_tftp",
|
||||
count = 0;
|
||||
|
||||
// check lan configuration type
|
||||
if ( lan_config.ipaddr_conf && lan_params.ipaddr_conf !== "0.0.0.0" ) { // manual configuration
|
||||
for ( var i = 0; i < lan_params.length; i++ ) { // check full lan manual configuration
|
||||
if ( lan_config[lan_params[i]] ) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
if ( count === lan_params.length ) { // manualy lan configuration
|
||||
type = "up_ip_tftp";
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if ( !this.CheckUpdateByTFTP() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// if all alright start updating
|
||||
if ( setEnvList({
|
||||
serverip_conf: bootstrap_url.host,
|
||||
tftp_path_conf: bootstrap_url.relative.substr(1) // remove first "/" in path needed for tftp
|
||||
}) ) {
|
||||
setTimeout(function () {
|
||||
gSTB.ExecAction("UpdateSW " + type);
|
||||
}, timeout || 0);
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks whether updated via tftp
|
||||
* @return {boolean}
|
||||
*/
|
||||
Update.CheckUpdateByTFTP = function () {
|
||||
var update_url = parseUri(Update.DefaultUpdateUrls.update_url),
|
||||
ip_regexp = /^((?:[0-9]{1,3}\.){3}[0-9]{1,3})$/, // for ip 192.168.1.221
|
||||
bootstrap_regexp = /^(tftp):\/\/((?:[0-9]{1,3}\.){3}[0-9]{1,3})(:[0-9]+)?(\/.+)+$/; // for url tftp://192.168.1.221/some/path
|
||||
|
||||
// validate update_url
|
||||
if ( ["igmp", "tftp", "http"].indexOf(update_url.protocol) !== -1 ) { // check protocol fo update_url
|
||||
if ( update_url.protocol === "igmp" && update_url.relative !== '' ) { // check realtive path for igmp
|
||||
return false;
|
||||
}
|
||||
if ( update_url.protocol === "tftp" || update_url.protocol === "igmp" ) { // check if host is valid ip for tftp and igmp protocols
|
||||
if ( !update_url.host.match(ip_regexp) ) { return false; }
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
if ( gSTB.GetDeviceModelExt().substr(0, 4) === "Aura" ) { return false; }
|
||||
// validate bootstrap_url
|
||||
return !!Update.DefaultUpdateUrls.bootstrap_url.match(bootstrap_regexp);
|
||||
};
|
||||
|
||||
/**
|
||||
* Update device by multicast
|
||||
* @param {number} [timeout] delay before start updating
|
||||
* @returns {boolean} return true if validation is passed
|
||||
*/
|
||||
Update.StartMulticast = function ( timeout ) {
|
||||
var bootstrap_url = parseUri(Update.DefaultUpdateUrls.bootstrap_url);
|
||||
if ( this.CheckUpdateByMulticast ) { // validate update_url and bootstrap_url
|
||||
// if all data valid start updating
|
||||
if ( setEnvList({mcip_conf: bootstrap_url.host, mcport_conf: bootstrap_url.port}) ) {
|
||||
setTimeout(function () { gSTB.ExecAction("UpdateSW up_mc_url"); }, timeout || 0);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks whether updated via multicast
|
||||
* @returns {boolean}
|
||||
*/
|
||||
Update.CheckUpdateByMulticast = function () {
|
||||
var regexp = /^(igmp):\/\/((?:[0-9]{1,3}\.){3}[0-9]{1,3})(:[0-9]+)?$/; // for url like igmp://192.168.1.221:444
|
||||
|
||||
if ( !configuration.allowMulticastUpdate ) { return false; }
|
||||
//if ( gSTB.GetDeviceModelExt().substr(0, 4) === "Aura" ) { return false; }
|
||||
|
||||
return (!!Update.DefaultUpdateUrls.bootstrap_url.match(regexp) && !!Update.DefaultUpdateUrls.update_url.match(regexp));
|
||||
};
|
||||
|
||||
Update.DefaultUpdateUrls = getEnvList(['update_url', 'bootstrap_url']);
|
||||
|
||||
/**
|
||||
* Start update by http or from USB
|
||||
* @param {string} update_url valid path to the update image
|
||||
* @param {Boolean} force force update start
|
||||
*/
|
||||
Update.prototype.Start = function ( update_url, force ) {
|
||||
var status = stbUpdate.getStatus(), // get current update status
|
||||
activeBank = stbUpdate.getActiveBank(), // get active NAND number
|
||||
modelTemp = gSTB.RDir("Model").toUpperCase(), // get device model
|
||||
realActiveBank; // check if active bank corrupted and we loaded in emergency mode
|
||||
|
||||
echo('Update.Start(update_url=' + update_url + ', force=' + force + ');');
|
||||
if ( (status == Update.OkStatus || force) && this.update_url === update_url ) { // check status
|
||||
echo('Update.Start(); => ready for update. Status:' + status + ', by force? ' + force);
|
||||
this.trigger("onStart"); // trigger onStart event
|
||||
echo('Update.Start(); => after onStart');
|
||||
this.SystemButtons(false);
|
||||
if ( modelTemp !== 'MAG250' && modelTemp !== 'MAG270' && modelTemp !== 'MAG275' ) { // check device model
|
||||
if ( activeBank != -1 && stbUpdate.GetFlashBankCount() != 2 ) { // check memory banks on old devices
|
||||
echo('Update.Start(); => trigger("onError") : bank error');
|
||||
this.trigger("onError", { // trigger onError event
|
||||
errorMessage: _('Unable to update active memory bank'),
|
||||
logMessage: 'Unable to update active memory bank',
|
||||
status: 30
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
if ( realActiveBank = stbStorage.getItem('nandEmergencyLoadingLogs') ) {
|
||||
realActiveBank = (JSON.parse(realActiveBank) || '').bootmedia;
|
||||
if ( realActiveBank === 'bank0' ) { activeBank = 0; }
|
||||
if ( realActiveBank === 'bank1' ) { activeBank = 1; }
|
||||
}
|
||||
echo('Update.Start(); => start stbUpdate.startUpdate for bank ' + (activeBank == 0 ? 1 : 0));
|
||||
if ( activeBank == 0 ) { // write to non active nand
|
||||
stbUpdate.startUpdate(1, update_url);
|
||||
} else {
|
||||
stbUpdate.startUpdate(0, update_url);
|
||||
}
|
||||
} else {
|
||||
echo('Update.Start(); => not ready for update. Status:' + status + ', is forced? ' + force + ', url is wrong? ' + (this.update_url !== update_url));
|
||||
echo('this.update_url (' + this.update_url + ') === update_url (' + update_url + ')');
|
||||
this.CheckUpdate(update_url, true); // if image not checked yet do this
|
||||
}
|
||||
this.CheckProgress();
|
||||
};
|
||||
|
||||
/**
|
||||
* Called by timeout function which check update progress state
|
||||
*/
|
||||
Update.prototype.CheckProgress = function () {
|
||||
var status = stbUpdate.getStatus(), // get current update status code
|
||||
percent = stbUpdate.getPercents(),
|
||||
self = this;
|
||||
|
||||
if ( !self.CheckError(status) ) return; // stop check progress if some error
|
||||
echo('Update.CheckProgress() => auto start if ready? ' + this.auto_start);
|
||||
if ( this.auto_start === true && status === Update.OkStatus ) { // if all ok and auto_start set in true start update after image check
|
||||
this.auto_start = false; // set autostart to false to prevent two update starts
|
||||
this.Start(this.update_url);
|
||||
}
|
||||
// fix percents - if update have been finished successfully percents value should be 100%
|
||||
if ( status === 16 ) {
|
||||
percent = 100;
|
||||
}
|
||||
echo('Update.CheckProgress() => set new progress value ' + stbUpdate.getPercents());
|
||||
this.trigger("onProgress", { // trigger onProgress event
|
||||
percent: percent,
|
||||
statusMessage: percent === 100 ? _(Update.StatusMessages[16]) : _(Update.StatusMessages[status]),
|
||||
logMessage: percent === 100 ? Update.StatusMessages[16] : Update.StatusMessages[status],
|
||||
status: percent === 100 ? 16 : status // after finish box will send status 21:"idle" again. Hide it from user (anyway next step - box reload).
|
||||
});
|
||||
this.progress_timeout = setTimeout(function () {self.CheckProgress()}, 1000); // set timeout for new progress check
|
||||
};
|
||||
|
||||
/**
|
||||
* Check status of whether the error status
|
||||
* @param {number} status current status code
|
||||
* @return {boolean} false if this is error status and true is all ok
|
||||
*/
|
||||
Update.prototype.CheckError = function ( status ) {
|
||||
echo('Update.CheckError(status); => status code: ' + status);
|
||||
if ( Update.CrashStatuses.indexOf(status) !== -1 ) { // check status presence in CrashStatuses list
|
||||
echo('Update.CheckError(status); => error status found!: ' + status);
|
||||
this.trigger("onError", { // if given the error status then trigger onError event
|
||||
errorMessage: _(Update.StatusMessages[status]),
|
||||
logMessage: Update.StatusMessages[status],
|
||||
status: status
|
||||
});
|
||||
this.SystemButtons(true);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Called by timeout function which check checking image state
|
||||
* @param {string|null} update_url valid path to the update image (local or http://..), or null if check already started
|
||||
* @param {boolean} [start_on_ready] specifies start update if returned Update.OkStatus
|
||||
* @constructor
|
||||
*/
|
||||
Update.prototype.CheckUpdate = function ( update_url, start_on_ready ) {
|
||||
var status, self;
|
||||
|
||||
echo('Update.CheckUpdate(update_url=' + update_url + ', start_on_ready=' + start_on_ready + ');');
|
||||
if ( typeof update_url === "string" ) { // if update_url is string start check
|
||||
stbUpdate.startCheck(this.check_url = update_url);
|
||||
}
|
||||
status = stbUpdate.getStatus(); // get current check status
|
||||
self = this;
|
||||
echo('Update.CheckUpdate(); => stop check if some error or got ready status. Current status: ' + status);
|
||||
if ( !this.CheckError(status) ) return; // stop check if some error
|
||||
this.trigger("onCheck", { // if all ok trigger onCheck event
|
||||
statusMessage: _(Update.StatusMessages[status]),
|
||||
logMessage: Update.StatusMessages[status],
|
||||
status: status
|
||||
});
|
||||
if ( status === Update.OkStatus ) { // check all ok status
|
||||
this.update_url = this.check_url;
|
||||
this.trigger("onReady"); // if all ok trigger onReady event
|
||||
echo('Update.CheckUpdate(); => check is ok. Start autoupdate? ' + start_on_ready);
|
||||
if ( start_on_ready === true ) {
|
||||
this.Start(this.update_url); // start updating if parameter start_on_ready set to true
|
||||
}
|
||||
return;
|
||||
}
|
||||
clearTimeout(this.check_update_timeout);
|
||||
this.check_update_timeout = setTimeout(function () { // set timeout for new check
|
||||
self.CheckUpdate(null, start_on_ready);
|
||||
}, 100);
|
||||
};
|
||||
|
||||
/**
|
||||
* Return info about image by which it check
|
||||
* Work only if current status is Update.OkStatus
|
||||
* @returns {Object|null}
|
||||
*/
|
||||
Update.prototype.GetImageInfo = function () {
|
||||
echo('Update.GetImageInfo(); => is info about new image ready? status:' + stbUpdate.getStatus());
|
||||
if ( stbUpdate.getStatus() === Update.OkStatus ) {
|
||||
echo('Update.GetImageInfo(); => {date:' + parseDate(stbUpdate.getImageDateStr()) + ', description:' + stbUpdate.getImageDescStr() + ', version:' + stbUpdate.getImageVersionStr() + '}');
|
||||
return {
|
||||
date: parseDate(stbUpdate.getImageDateStr()),
|
||||
description: stbUpdate.getImageDescStr(),
|
||||
version: stbUpdate.getImageVersionStr()
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Return information about current image
|
||||
* @returns {Object}
|
||||
*/
|
||||
Update.prototype.GetCurrentImageInfo = function () {
|
||||
if ( this.curr_info === undefined ) {
|
||||
var info = JSON.parse(gSTB.GetEnv(JSON.stringify({varList: ['Image_Desc', 'Image_Date', 'Image_Version']}))).result;
|
||||
this.curr_info = {
|
||||
date: parseDate(info.Image_Date),
|
||||
description: info.Image_Desc,
|
||||
version: info.Image_Version
|
||||
}
|
||||
}
|
||||
/*
|
||||
TODO: check this code on MAG352
|
||||
var obj = {
|
||||
date: new Date(NaN);
|
||||
}
|
||||
|
||||
JSON.stringify(obj);
|
||||
*/
|
||||
// echo('Update.GetCurrentImageInfo(); => Return:' + JSON.stringify(this.curr_info));
|
||||
return this.curr_info;
|
||||
};
|
||||
|
||||
/**
|
||||
* Brings Update to its original appearance
|
||||
*/
|
||||
Update.prototype.Clear = function () {
|
||||
clearTimeout(this.check_update_timeout);
|
||||
clearTimeout(this.progress_timeout);
|
||||
this.auto_start = false;
|
||||
this.update_url = '';
|
||||
};
|
||||
|
||||
Update.prototype.SystemButtons = function ( enable ) {
|
||||
gSTB.EnableServiceButton(enable);
|
||||
gSTB.EnableVKButton(enable);
|
||||
gSTB.EnableAppButton(enable);
|
||||
gSTB.EnableTvButton(enable);
|
||||
};
|
||||
|
||||
Events.inject(Update); // inject Events functionality
|
|
@ -0,0 +1,501 @@
|
|||
/**
|
||||
* Virtual file list component
|
||||
*
|
||||
* @author Stanislav Kalashnik <sk@infomir.eu> Igor Zaporozhets <deadbyelpy@gmail.com>
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
|
||||
/**
|
||||
* Base virtual list implementation.
|
||||
* Based on list from stb framework. but event model, and scroll bar type have been removed
|
||||
* @link https://github.com/DarkPark/stb/blob/master/app/js/ui/list.js
|
||||
*
|
||||
* @constructor
|
||||
*
|
||||
* @param {Object} [config={}] init parameters (all inherited from the parent)
|
||||
* @param {Array} [config.data=[]] component data to visualize
|
||||
* @param {number} [config.size=5] amount of visible items on a page
|
||||
* @param {boolean} [config.scrollBar=null] ScrollBar
|
||||
*/
|
||||
function VList ( parent ) {
|
||||
/**
|
||||
* Link to the currently focused DOM element.
|
||||
*
|
||||
* @type {Element}
|
||||
*/
|
||||
this.activeItem = null;
|
||||
|
||||
/**
|
||||
* Position of the visible window to render.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
this.indexView = null;
|
||||
|
||||
/**
|
||||
* Component data to visualize.
|
||||
*
|
||||
* @type {Array}
|
||||
*/
|
||||
this.data = [];
|
||||
|
||||
this.scrollBar = null;
|
||||
|
||||
/**
|
||||
* Amount of visible items on a page.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
this.size = 7;
|
||||
|
||||
this.states = {};
|
||||
|
||||
// set list size by screen height
|
||||
switch ( screen.height ) {
|
||||
case 480:
|
||||
this.size = 5;
|
||||
break;
|
||||
case 576:
|
||||
this.size = 5;
|
||||
break;
|
||||
case 720:
|
||||
this.size = 7;
|
||||
break;
|
||||
case 1080:
|
||||
this.size = 7;
|
||||
break;
|
||||
}
|
||||
// parent init
|
||||
CBase.call(this, parent);
|
||||
}
|
||||
|
||||
|
||||
// inheritance
|
||||
VList.prototype = Object.create(CBase.prototype);
|
||||
VList.prototype.constructor = VList;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Return items count.
|
||||
*
|
||||
* @deprecated created for other list compatibility
|
||||
*
|
||||
* @return {Number} total items count
|
||||
*/
|
||||
VList.prototype.Length = function () {
|
||||
return this.data.length;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Fill the given item with data.
|
||||
*
|
||||
* @param {Element} $item item DOM link
|
||||
* @param {*} attrs associated with this item data
|
||||
*/
|
||||
VList.prototype.renderItem = function ( $item, attrs ) {
|
||||
$item.innerText = attrs.name;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Fill the given item with data.
|
||||
*
|
||||
* @param {Element} $item item DOM link
|
||||
* @param {*} attrs associated with this item data
|
||||
*/
|
||||
VList.prototype.focus = function ( ) {
|
||||
this.$body.focus();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Default method to move focus according to pressed keys.
|
||||
*
|
||||
* @param {Event} event generated event source of movement
|
||||
*/
|
||||
VList.prototype.EventHandler = function ( event ) {
|
||||
switch ( event.code ) {
|
||||
case KEYS.UP:
|
||||
case KEYS.DOWN:
|
||||
case KEYS.RIGHT:
|
||||
case KEYS.LEFT:
|
||||
case KEYS.PAGE_UP:
|
||||
case KEYS.PAGE_DOWN:
|
||||
case KEYS.HOME:
|
||||
case KEYS.END:
|
||||
// cursor move only on arrow keys
|
||||
this.move(event.code);
|
||||
break;
|
||||
case KEYS.OK:
|
||||
// there are some listeners
|
||||
this.activeItem.onclick();
|
||||
break;
|
||||
}
|
||||
event.preventDefault();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Initialize list UI.
|
||||
*
|
||||
* @param {Object} config config object
|
||||
* @constructor
|
||||
*/
|
||||
VList.prototype.Init = function ( config ) {
|
||||
var self = this,
|
||||
onClick = false,
|
||||
item, i;
|
||||
// global store
|
||||
if ( config.handle === undefined ) {
|
||||
this.handleInner = this.$body = this.$node = document.createElement('div');
|
||||
} else {
|
||||
this.handleInner = this.$body = this.$node = config.handle;
|
||||
}
|
||||
|
||||
// non-empty list
|
||||
if (this.size > 0) {
|
||||
// clear old items
|
||||
this.$body.innerText = null;
|
||||
}
|
||||
|
||||
if ( config.onClick !== undefined ) {
|
||||
onClick = config.onClick;
|
||||
}
|
||||
|
||||
this.$node.classList.add('vlist-main');
|
||||
|
||||
// create new items
|
||||
for (i = 0; i < this.size; i++) {
|
||||
item = document.createElement('a');
|
||||
item.index = i;
|
||||
if ( onClick ) {
|
||||
item.onclick = onClick;
|
||||
}
|
||||
|
||||
//item.addEventListener('click', onClick);
|
||||
this.$body.appendChild(item);
|
||||
}
|
||||
// navigation by mouse
|
||||
this.$body.addEventListener('mousewheel', function ( event ) {
|
||||
// scrolling by Y axis
|
||||
if ( event.wheelDeltaY ) {
|
||||
self.move(event.wheelDeltaY > 0 ? KEYS.UP : KEYS.DOWN);
|
||||
}
|
||||
});
|
||||
|
||||
if ( config.scrollBar && this.scrollBar === null ) {
|
||||
this.scrollBar = config.scrollBar;
|
||||
this.scrollBar.init({viewSize: self.size, realSize: self.data.length});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Draw the visible window.
|
||||
*
|
||||
* @param {number} index start position to render
|
||||
*
|
||||
* @return {boolean} operation status
|
||||
*
|
||||
*/
|
||||
VList.prototype.renderView = function ( index ) {
|
||||
var $item, i, itemData, prevIndex, currIndex;
|
||||
// has the view window position changed
|
||||
if ( this.indexView !== index ) {
|
||||
// sync global pointer
|
||||
this.indexView = currIndex = index;
|
||||
|
||||
// rebuild all visible items
|
||||
for ( i = 0; i < this.size; i++ ) {
|
||||
// shortcuts
|
||||
$item = this.$body.children[i];
|
||||
itemData = this.data[index];
|
||||
|
||||
// real item or stub
|
||||
if ( itemData !== undefined ) {
|
||||
// correct inner data/index and render
|
||||
$item.data = itemData;
|
||||
$item.index = index;
|
||||
this.renderItem($item, itemData);
|
||||
|
||||
// apply CSS
|
||||
if ( itemData.markable ) {
|
||||
$item.classList.add('mark');
|
||||
} else {
|
||||
$item.classList.remove('mark');
|
||||
}
|
||||
} else {
|
||||
// nothing to render
|
||||
$item.data = $item.index = undefined;
|
||||
if ( $item.ready ) {
|
||||
$item.$body.innerText = '';
|
||||
$item.$body.style.background = '';
|
||||
}
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
if ( this.scrollBar !== null ) {
|
||||
this.scrollBar.scrollTo(this.indexView);
|
||||
}
|
||||
// full rebuild
|
||||
return true;
|
||||
}
|
||||
|
||||
// nothing was done
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Move focus to the given direction.
|
||||
*
|
||||
* @param {number} direction arrow key code
|
||||
*/
|
||||
VList.prototype.move = function ( direction ) {
|
||||
if ( direction === KEYS.UP ) {
|
||||
// still can go backward
|
||||
if ( this.activeItem && this.activeItem.index > 0 ) {
|
||||
if ( this.activeItem === this.$body.firstChild ) {
|
||||
this.renderView(this.indexView - 1);
|
||||
} else {
|
||||
this.focusItem(this.activeItem.previousSibling);
|
||||
}
|
||||
} else if ( this.onOverflow ) {
|
||||
this.onOverflow({direction: direction});
|
||||
}
|
||||
}
|
||||
if ( direction === KEYS.DOWN ) {
|
||||
// still can go forward
|
||||
if ( this.activeItem && this.activeItem.index < this.data.length - 1 ) {
|
||||
if ( this.activeItem === this.$body.lastChild ) {
|
||||
this.renderView(this.indexView + 1);
|
||||
} else {
|
||||
this.focusItem(this.activeItem.nextSibling);
|
||||
}
|
||||
} else if ( this.onOverflow ) {
|
||||
this.onOverflow({direction: direction});
|
||||
}
|
||||
}
|
||||
|
||||
if ( direction === KEYS.PAGE_UP ) {
|
||||
// determine jump size
|
||||
if ( this.indexView < this.size ) {
|
||||
// first page
|
||||
this.renderView(0);
|
||||
} else {
|
||||
// second page and further
|
||||
this.renderView(this.indexView - this.size + 1);
|
||||
}
|
||||
|
||||
this.focusItem(this.$body.firstChild);
|
||||
}
|
||||
|
||||
if ( direction === KEYS.PAGE_DOWN ) {
|
||||
// data is bigger then one page
|
||||
if ( this.data.length > this.size ) {
|
||||
// determine jump size
|
||||
if ( this.indexView > this.data.length - this.size * 2 ) {
|
||||
// last page
|
||||
this.renderView(this.data.length - this.size);
|
||||
} else {
|
||||
// before the last page
|
||||
this.renderView(this.indexView + this.size - 1);
|
||||
}
|
||||
this.focusItem(this.$body.lastChild);
|
||||
} else {
|
||||
// not the last item on the page
|
||||
this.focusItem(this.$body.children[this.data.length - 1]);
|
||||
}
|
||||
}
|
||||
|
||||
if ( direction === KEYS.HOME || direction === KEYS.LEFT ) {
|
||||
this.renderView(0);
|
||||
this.focusItem(this.$body.firstChild);
|
||||
}
|
||||
|
||||
if ( direction === KEYS.END || direction === KEYS.RIGHT ) {
|
||||
// data is bigger then one page
|
||||
if ( this.data.length > this.size ) {
|
||||
this.renderView(this.data.length - this.size);
|
||||
this.focusItem(this.$body.lastChild);
|
||||
} else {
|
||||
// not the last item on the page
|
||||
this.focusItem(this.$body.children[this.data.length - 1]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Highlight the given DOM element as focused.
|
||||
* Remove focus from the previously focused item and generate associated event.
|
||||
*
|
||||
* @param {Node|Element} $item element to focus
|
||||
*
|
||||
* @return {boolean} operation status
|
||||
*/
|
||||
VList.prototype.focusItem = function ( $item ) {
|
||||
var $prev = this.activeItem;
|
||||
|
||||
// different element
|
||||
if ( $item !== undefined && $prev !== $item ) {
|
||||
// some item is focused already
|
||||
if ( $prev !== null ) {
|
||||
// style
|
||||
$prev.classList.remove('focused');
|
||||
}
|
||||
// reassign
|
||||
this.activeItem = $item;
|
||||
|
||||
this.activeItem.data = this.data[this.activeItem.index];
|
||||
|
||||
// correct CSS
|
||||
$item.classList.add('focused');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// nothing was done
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Create new item and put it in the list
|
||||
* @param {string} name item label
|
||||
* @param {Object} attrs set of item data parameters
|
||||
* @param {Object} [states] set of additional parameters (stared)
|
||||
*
|
||||
* @return {Node|null}
|
||||
*/
|
||||
VList.prototype.Add = function ( name, attrs, states ) {
|
||||
var prop;
|
||||
attrs.name = name;
|
||||
if ( states ) {
|
||||
for ( prop in states ) {
|
||||
attrs.prop = states[prop];
|
||||
}
|
||||
}
|
||||
this.data.push(attrs);
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear inner data.
|
||||
* If u want update list, call this.renderView();
|
||||
*/
|
||||
VList.prototype.Clear = function () {
|
||||
this.data = [];
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Set inner item flags and decoration.
|
||||
*
|
||||
* @param {Object} item the element to be processed
|
||||
* @param {string} option item inner flag name
|
||||
* @param {boolean} state flag of the operation (true if change is made)
|
||||
*
|
||||
* @return {boolean} operation status
|
||||
*/
|
||||
VList.prototype.SetState = function ( item, option, state ) {
|
||||
state = !!state;
|
||||
// current and new states are different
|
||||
if ( item[option] !== state ) {
|
||||
// check if exist
|
||||
if ( !this.states[option] ) { this.states[option] = []; }
|
||||
var index = this.states[option].indexOf(item);
|
||||
//update internal list
|
||||
if ( state ) {
|
||||
// add to the list
|
||||
if ( index === -1 ) { this.states[option].push(item); }
|
||||
} else {
|
||||
// remove
|
||||
if ( index !== -1 ) { this.states[option].splice(index, 1); }
|
||||
}
|
||||
// flag
|
||||
item[option] = state;
|
||||
return true;
|
||||
}
|
||||
// nothing has changed
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Syntax sugar for render items from begin and focus first element.
|
||||
*
|
||||
*/
|
||||
VList.prototype.Render = function () {
|
||||
this.indexView = -1;
|
||||
this.renderView(0);
|
||||
this.focusItem(this.$body.firstChild);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set item state and appearance as marked.
|
||||
*
|
||||
* @return {object} next data object
|
||||
*/
|
||||
VList.prototype.Next = function () {
|
||||
return this.data[this.activeItem.index + 1];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Return active (focused) item of the list.
|
||||
*
|
||||
* @deprecated created for other list compatibility
|
||||
*
|
||||
* @return {HTMLElement}
|
||||
*/
|
||||
VList.prototype.Current = function () {
|
||||
return this.activeItem;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set position some list element
|
||||
* @param {Object} data data item from inner items
|
||||
* @param {boolean} [manageFocus] - set actual focus
|
||||
*/
|
||||
VList.prototype.SetPosition = function ( data, manageFocus ) {
|
||||
var index = this.data.indexOf( data ),
|
||||
i, viewIndex;
|
||||
if ( index === -1 || index === undefined ) {
|
||||
return false;
|
||||
}
|
||||
this.indexView = -1;
|
||||
i = data.index || 0;
|
||||
if ( i < this.size ) {
|
||||
viewIndex = 0;
|
||||
} else if ( i > this.data.length - this.size ) {
|
||||
viewIndex = this.data.length - this.size;
|
||||
i -= viewIndex;
|
||||
}
|
||||
this.renderView(viewIndex);
|
||||
if ( manageFocus ) {
|
||||
this.focusItem(this.$body.children[i]);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Go through all the items
|
||||
*
|
||||
* @deprecated created for other list compatibility
|
||||
*
|
||||
* @param {Function} callback iteration callback function
|
||||
*/
|
||||
VList.prototype.Each = function ( callback ) {
|
||||
this.data.forEach(callback);
|
||||
};
|
|
@ -0,0 +1,208 @@
|
|||
/**
|
||||
* @author Stanislav Kalashnik <sk@infomir.eu> Igor Zaporozhets <deadbyelpy@gmail.com>
|
||||
* @license GNU GENERAL PUBLIC LICENSE Version 3
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
|
||||
/**
|
||||
* Virtual scroll bar implementation.
|
||||
* Based on scroll bar from stb framework. but event model, and scroll bar type have been removed
|
||||
* @link https://github.com/DarkPark/stb/blob/master/app/js/ui/scroll.bar.js
|
||||
*
|
||||
* @constructor
|
||||
*
|
||||
* @param {Object} [config={}] init parameters (all inherited from the parent)
|
||||
* @param {number} [config.value=0] initial thumb position
|
||||
* @param {number} [config.realSize=100] actual scroll size
|
||||
* @param {number} [config.viewSize=10] visible area size
|
||||
*
|
||||
* @example
|
||||
* var scrollBar = new VScrollBar({
|
||||
* viewSize: 5,
|
||||
* realSize: 25,
|
||||
* value: 4
|
||||
* });
|
||||
*/
|
||||
function VScrollBar ( config ) {
|
||||
|
||||
/**
|
||||
* DOM outer handle.
|
||||
*
|
||||
* @type {Element}
|
||||
*/
|
||||
this.$node = null;
|
||||
|
||||
/**
|
||||
* DOM inner handle.
|
||||
* In simple cases is the same as $node.
|
||||
*
|
||||
* @type {Element}
|
||||
*/
|
||||
this.$body = null;
|
||||
|
||||
/**
|
||||
* Visible area size.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
this.viewSize = 10;
|
||||
|
||||
/**
|
||||
* Scroll area actual height or width (if scroll is horizontal).
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
this.realSize = 100;
|
||||
|
||||
/**
|
||||
* Scroll thumb position.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
this.value = 0;
|
||||
|
||||
/**
|
||||
* Geometry of the scroll thumb element.
|
||||
*
|
||||
* @type {ClientRect}
|
||||
*/
|
||||
this.thumbRect = null;
|
||||
|
||||
/**
|
||||
* Geometry of the scroll track element.
|
||||
*
|
||||
* @type {ClientRect}
|
||||
*/
|
||||
this.trackRect = null;
|
||||
|
||||
|
||||
// sanitize
|
||||
config = config || {};
|
||||
|
||||
// outer handle
|
||||
if ( config.$node !== undefined ) {
|
||||
// apply
|
||||
this.$node = config.$node;
|
||||
} else {
|
||||
// empty div in case nothing is given
|
||||
this.$node = document.createElement('div');
|
||||
}
|
||||
|
||||
// inner handle
|
||||
if ( config.$body !== undefined ) {
|
||||
// apply
|
||||
this.$body = config.$body;
|
||||
} else {
|
||||
// inner and outer handlers are identical
|
||||
this.$body = this.$node.appendChild(document.createElement('div'));
|
||||
}
|
||||
|
||||
// correct CSS class names
|
||||
this.$node.classList.add('scrollBar');
|
||||
this.$body.classList.add('thumb');
|
||||
|
||||
// component setup
|
||||
this.init(config);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Init or re-init realSize/viewSize/value parameters.
|
||||
*
|
||||
* @param {Object} config init parameters (subset of constructor config params)
|
||||
*/
|
||||
VScrollBar.prototype.init = function ( config ) {
|
||||
config = config || {};
|
||||
|
||||
if ( DEBUG ) {
|
||||
if ( arguments.length !== 1 ) { throw 'wrong arguments number'; }
|
||||
if ( typeof config !== 'object' ) { throw 'wrong config type'; }
|
||||
}
|
||||
|
||||
// set actual scroll size
|
||||
if ( config.realSize !== undefined ) {
|
||||
if ( DEBUG ) {
|
||||
if ( Number(config.realSize) !== config.realSize ) { throw 'config.realSize value must be a number'; }
|
||||
}
|
||||
// apply
|
||||
this.realSize = config.realSize;
|
||||
}
|
||||
|
||||
// set visible area size
|
||||
if ( config.viewSize !== undefined ) {
|
||||
if ( DEBUG ) {
|
||||
if ( Number(config.viewSize) !== config.viewSize ) { throw 'config.viewSize value must be a number'; }
|
||||
if ( config.viewSize <= 0 ) { throw 'config.viewSize value must be greater than 0'; }
|
||||
}
|
||||
// apply
|
||||
this.viewSize = config.viewSize;
|
||||
}
|
||||
|
||||
// show or hide thumb
|
||||
if ( this.viewSize >= this.realSize ) {
|
||||
this.$body.classList.add('hidden');
|
||||
} else {
|
||||
this.$body.classList.remove('hidden');
|
||||
}
|
||||
|
||||
// set thumb position
|
||||
if ( config.value !== undefined ) {
|
||||
// apply
|
||||
this.scrollTo(config.value);
|
||||
}
|
||||
|
||||
// set thumb size
|
||||
this.$body.style.height = (this.viewSize / this.realSize * 100) + '%';
|
||||
|
||||
// geometry
|
||||
this.thumbRect = this.$body.getBoundingClientRect();
|
||||
this.trackRect = this.$node.getBoundingClientRect();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set position of the given value.
|
||||
* Does nothing in case when scroll is in the end and passed value is more than scroll bar length.
|
||||
*
|
||||
* @param {number} value new value to set
|
||||
* @return {boolean} operation result
|
||||
*
|
||||
* @fires module:stb/ui/scroll.bar~VScrollBar#done
|
||||
* @fires module:stb/ui/scroll.bar~VScrollBar#change
|
||||
*/
|
||||
VScrollBar.prototype.scrollTo = function ( value ) {
|
||||
if ( DEBUG ) {
|
||||
if ( arguments.length !== 1 ) { throw 'wrong arguments number'; }
|
||||
if ( Number(value) !== value ) { throw 'value must be a number'; }
|
||||
if ( this.realSize > this.viewSize && value > this.realSize - this.viewSize ) { throw 'value is greater than this.realSize-this.viewSize'; }
|
||||
if ( value < 0 ) { throw 'value is less then 0'; }
|
||||
}
|
||||
|
||||
// value has changed
|
||||
if ( this.value !== value ) {
|
||||
// track and thumb geometry was not set
|
||||
if ( this.thumbRect.height === 0 || this.thumbRect.width === 0 ) {
|
||||
// apply
|
||||
this.trackRect = this.$node.getBoundingClientRect();
|
||||
this.thumbRect = this.$body.getBoundingClientRect();
|
||||
}
|
||||
|
||||
// set scroll bar width
|
||||
this.$body.style.marginTop = ((this.trackRect.height - this.thumbRect.height) * value / (this.realSize - this.viewSize)) + 'px';
|
||||
|
||||
// is it the end?
|
||||
if ( value >= this.realSize ) {
|
||||
value = this.realSize;
|
||||
}
|
||||
|
||||
// set new value
|
||||
this.value = value;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// nothing was done
|
||||
return false;
|
||||
};
|
|
@ -0,0 +1,138 @@
|
|||
/**
|
||||
* @author Stanislav Kalashnik <sk@infomir.eu>
|
||||
* @license GNU GENERAL PUBLIC LICENSE Version 3
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/** @private */
|
||||
var messageId = 0,
|
||||
callbacks = {};
|
||||
|
||||
|
||||
/**
|
||||
* Lightweight WAMP implementation based on WebSockets.
|
||||
*
|
||||
* @param {WebSocket} socket link to socket connection to wrap
|
||||
*
|
||||
* @see http://wamp-proto.org/
|
||||
* @constructor
|
||||
*/
|
||||
function Wamp ( socket ) {
|
||||
var self = this;
|
||||
|
||||
// parent constructor call
|
||||
Emitter.call(this);
|
||||
|
||||
this.socket = socket;
|
||||
|
||||
if ( 'on' in socket ) {
|
||||
// server-side
|
||||
socket.on('message', function ( message ) {
|
||||
self.router(message);
|
||||
});
|
||||
} else if ( 'onmessage' in socket ) {
|
||||
// desktop browser
|
||||
socket.onmessage = function ( event ) {
|
||||
self.router(event.data);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// inheritance
|
||||
Wamp.prototype = Object.create(Emitter.prototype);
|
||||
Wamp.prototype.constructor = Wamp;
|
||||
|
||||
|
||||
/**
|
||||
* Internal method to handle messages.
|
||||
*
|
||||
* @param {string} message JSON data
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
Wamp.prototype.router = function ( message ) {
|
||||
var self = this;
|
||||
console.log('router');
|
||||
console.log(message);
|
||||
try {
|
||||
message = JSON.parse(message);
|
||||
} catch ( e ) {
|
||||
this.socket.send(JSON.stringify({
|
||||
jsonrpc: '2.0',
|
||||
error: {code: -32700, message: 'Parse error'},
|
||||
id: null
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
if ( 'id' in message && !('method' in message) ) {
|
||||
// incoming answer for previous request
|
||||
if ( message.id in callbacks ) {
|
||||
callbacks[message.id](message.error, message.result);
|
||||
delete callbacks[message.id];
|
||||
} else {
|
||||
// no callback registered for this id
|
||||
}
|
||||
} else if ( !('id' in message) && 'method' in message ) {
|
||||
// incoming notification
|
||||
if ( this.events[message.method] ) {
|
||||
this.emit(message.method, message.params);
|
||||
}
|
||||
} else if ( 'id' in message && 'method' in message ) {
|
||||
// execute incoming method and report to sender
|
||||
if ( this.events[message.method] ) {
|
||||
this.emit(message.method, message.params, function ( error, result ) {
|
||||
self.socket.send(JSON.stringify({
|
||||
jsonrpc: '2.0',
|
||||
error: error,
|
||||
result: result,
|
||||
id: message.id
|
||||
}));
|
||||
});
|
||||
} else {
|
||||
// wrong method
|
||||
this.socket.send(JSON.stringify({
|
||||
jsonrpc: '2.0',
|
||||
error: {code: -32601, message: 'Method not found'},
|
||||
id: message.id
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
// wrong request
|
||||
this.socket.send(JSON.stringify({
|
||||
jsonrpc: '2.0',
|
||||
error: {code: -32600, message: 'Invalid Request'},
|
||||
id: null
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Send message to execute remotely or notify (without `callback` argument).
|
||||
*
|
||||
* @param {string} method procedure or event name
|
||||
* @param {*} [params] procedure associated data
|
||||
* @param {function} [callback] remote call results handler
|
||||
*/
|
||||
Wamp.prototype.call = function ( method, params, callback ) {
|
||||
var message = {
|
||||
jsonrpc: '2.0',
|
||||
method: method
|
||||
};
|
||||
|
||||
if ( params ) {
|
||||
message.params = params;
|
||||
}
|
||||
|
||||
// execution mode with callback
|
||||
// notification mode otherwise
|
||||
if ( typeof callback === 'function' ) {
|
||||
message.id = ++messageId;
|
||||
callbacks[messageId] = callback;
|
||||
}
|
||||
console.log(JSON.stringify(message));
|
||||
this.socket.send(JSON.stringify(message));
|
||||
};
|
|
@ -0,0 +1,56 @@
|
|||
'use strict';
|
||||
|
||||
var WINDOWS = {
|
||||
PORTAL: 'portal',
|
||||
HELP: 'help',
|
||||
DOWNLOAD_MANAGER: 'dlman',
|
||||
PVR: 'recordsManager',
|
||||
BROWSER: 'ibman',
|
||||
BROWSER_VIEW: 'ibmanView',
|
||||
PORTALS_LOADER: 'portalsLoader',
|
||||
SYSTEM_SETTINGS: 'systemSettings'
|
||||
};
|
||||
|
||||
function getWindowKey ( windowName ) {
|
||||
return 'window.' + windowName + '.id';
|
||||
}
|
||||
|
||||
function getWindowIdByName ( windowName ) {
|
||||
return stbStorage.getItem(getWindowKey(windowName));
|
||||
}
|
||||
|
||||
function openWindow ( windowName, url ) {
|
||||
var windowKey = getWindowKey(windowName), // get storage key
|
||||
windowID = stbStorage.getItem(windowKey), // get window id from storage
|
||||
windowInfo;
|
||||
if ( windowID ) { // if window is opened
|
||||
try {
|
||||
windowInfo = JSON.parse(stbWindowMgr.windowInfo(windowID)); // get window info
|
||||
if ( windowInfo.result.url.indexOf(url) === -1 ) {
|
||||
// set black screen and reload
|
||||
stbWebWindow.messageSend(windowID, 'window.load', url);
|
||||
}
|
||||
// browser window hasn't show method
|
||||
if ( windowName === WINDOWS.BROWSER ) {
|
||||
// window recreation
|
||||
windowID = stbWindowMgr.openWebFace(url);
|
||||
stbStorage.setItem(windowKey, windowID);
|
||||
} else {
|
||||
// create a new one
|
||||
stbWindowMgr.windowShow(windowID);
|
||||
}
|
||||
} catch ( e ) {
|
||||
echo(e);
|
||||
stbWindowMgr.openWebFace(url);
|
||||
}
|
||||
} else {
|
||||
// browser window has special init method
|
||||
windowID = windowName === WINDOWS.BROWSER ? stbWindowMgr.openWebFace(url) : stbWindowMgr.windowInit(JSON.stringify({url:url, backgroundColor:'#000'}));
|
||||
stbStorage.setItem(windowKey, windowID);
|
||||
}
|
||||
return windowID;
|
||||
}
|
||||
|
||||
function openWindowHelp ( path ) {
|
||||
openWindow(WINDOWS.HELP, PATH_ROOT + 'public/app/help/index.html?path=' + path);
|
||||
}
|
Loading…
Reference in New Issue