iobroker.vis
Version:
Graphical user interface for iobroker.
1,146 lines (1,055 loc) • 149 kB
JavaScript
/**
* ioBroker.vis
* https://github.com/ioBroker/ioBroker.vis
*
* Copyright (c) 2013-2017 bluefox https://github.com/GermanBluefox, hobbyquaker https://github.com/hobbyquaker
* Creative Common Attribution-NonCommercial (CC BY-NC)
*
* http://creativecommons.org/licenses/by-nc/4.0/
*
*/
/* jshint browser:true */
/* global document */
/* global console */
/* global session */
/* global window */
/* global location */
/* global setTimeout */
/* global clearTimeout */
/* global io */
/* global visConfig */
/* global systemLang:true */
/* global _ */
/* global can */
/* global storage */
/* global servConn */
/* global systemDictionary */
/* global $ */
/* global app */
/* global Audio */
/* global cordova */
/* global translateAll */
/* global jQuery */
/* global document */
/* global moment */
/* jshint -W097 */// jshint strict:false
'use strict';
if (typeof systemDictionary !== 'undefined') {
$.extend(systemDictionary, {
'No connection to Server': {'en': 'No connection to Server', 'de': 'Keine Verbindung zum Server', 'ru': 'Нет соединения с сервером'},
'Loading Views...': {'en': 'Loading Views...', 'de': 'Lade Views...', 'ru': 'Загрузка пользовательских страниц...'},
'Connecting to Server...': {'en': 'Connecting to Server...', 'de': 'Verbinde mit dem Server...', 'ru': 'Соединение с сервером...'},
'Loading data objects...': {'en': 'Loading data...', 'de': 'Lade Daten...', 'ru': 'Загрузка данных...'},
'Loading data values...': {'en': 'Loading values...', 'de': 'Lade Werte...', 'ru': 'Загрузка значений...'},
'error - View doesn\'t exist': {'en': 'View doesn\'t exist!', 'de': 'View existiert nicht!', 'ru': 'Страница не существует!'},
'no views found!': {'en': 'No views found!', 'de': 'Keine Views gefunden!', 'ru': 'Не найдено страниц!'},
'No Views found on Server': {
'en': 'No Views found on Server',
'de': 'Keine Views am Server gefunden.',
'ru': 'На сервере не найдено никаких страниц.'
},
'All changes are saved locally. To reset changes clear the cache.': {
'en': 'All changes are saved locally. To reset changes clear the browser cache.',
'de': 'Alle Änderungen sind lokal gespeichert. Um Änderungen zu löschen, lösche Browsercache.',
'ru': 'Все изменения сохранены локально. Для отмены локальных изменений очистите кеш броузера.'
},
'please use /vis/edit.html instead of /vis/?edit': {
'en': 'Please use /vis/edit.html instead of /vis/?edit',
'de': 'Bitte geben Sie /vis/edit.html statt /vis/?edit',
'ru': 'Используйте /vis/edit.html вместо /vis/?edit'
},
'no views found on server.\nCreate new %s ?': {
'en': 'no views found on server.\nCreate new %s?',
'de': 'Keine Views am Server gefunden am.\nErzeugen %s?',
'ru': 'На сервере не найдено никаких страниц. Создать %s?'
},
'Update found, loading new Files...': {
'en': 'Update found.<br/>Loading new Files...',
'de': 'Neue Version gefunden.<br/>Lade neue Dateien...',
'ru': 'Обнаружено Обновление.<br/>Загружаю новые файлы...'
},
'Loading Widget-Sets...': {
'en': 'Loading Widget-Sets...',
'de': 'Lade Widget-Sätze...',
'ru': 'Загрузка наборов элементов...'
},
'error: view not found.': {
'en': 'Error: view not found',
'de': 'Fehler: View wurde nicht gefunden',
'ru': 'Ошибка: Страница не существует'
},
'error: view container recursion.': {
'en': 'Error: view container recursion',
'de': 'Fehler: View ist rekursiv',
'ru': 'Ошибка: Страница вызывет саму себя'
},
"Cannot execute %s for %s, because of insufficient permissions": {
"en": "Cannot execute %s for %s, because of insufficient permissions.",
"de": "Kann das Kommando \"%s\" für %s nicht ausführen, weil nicht genügend Zugriffsrechte vorhanden sind.",
"ru": "Не могу выполнить \"%s\" для %s, так как недостаточно прав."
},
"Insufficient permissions": {
"en": "Insufficient permissions",
"de": "Nicht genügend Zugriffsrechte",
"ru": "Недостаточно прав"
},
"View disabled for user %s": {
"en": "View disabled for user <b>%s</b>",
"de": "View ist für Anwender <b>%s</b> deaktiviert",
"ru": "Страница недоступна для пользователя <b>%s</b>"
}
});
}
if (typeof systemLang !== 'undefined' && typeof cordova === 'undefined') {
systemLang = visConfig.language || systemLang;
}
var vis = {
version: '1.1.8',
requiredServerVersion: '0.0.0',
storageKeyViews: 'visViews',
storageKeySettings: 'visSettings',
storageKeyInstance: 'visInstance',
instance: null,
urlParams: {},
settings: {},
views: null,
widgets: {},
activeView: '',
activeViewDiv: '',
widgetSets: visConfig.widgetSets,
initialized: false,
toLoadSetsCount: 0, // Count of widget sets that should be loaded
isFirstTime: true,
useCache: false,
authRunning: false,
cssChecked: false,
isTouch: 'ontouchstart' in document.documentElement,
binds: {},
onChangeCallbacks: [],
viewsActiveFilter: {},
projectPrefix: window.location.search ? window.location.search.slice(1) + '/' : 'main/',
navChangeCallbacks: [],
editMode: false,
language: (typeof systemLang !== 'undefined') ? systemLang : visConfig.language,
statesDebounce: {},
visibility: {},
signals: {},
lastChanges: {},
bindings: {},
bindingsCache: {},
subscribing: {
IDs: [],
byViews: {},
active: [],
activeViews: []
},
commonStyle: null,
debounceInterval: 700,
user: '', // logged in user
loginRequired: false,
_setValue: function (id, state, isJustCreated) {
var that = this;
var oldValue = this.states.attr(id + '.val');
this.conn.setState(id, state[id + '.val'], function (err) {
if (err) {
//state[id + '.val'] = oldValue;
that.showMessage(_('Cannot execute %s for %s, because of insufficient permissions', 'setState', id), _('Insufficient permissions'), 'alert', 600);
}
if (that.states.attr(id) || that.states.attr(id + '.val') !== undefined) {
that.states.attr(state);
// If error set value back, but we need generate the edge
if (err) {
if (isJustCreated) {
that.states.removeAttr(id + '.val');
that.states.removeAttr(id + '.q');
that.states.removeAttr(id + '.from');
that.states.removeAttr(id + '.ts');
that.states.removeAttr(id + '.lc');
that.states.removeAttr(id + '.ack');
} else {
state[id + '.val'] = oldValue;
that.states.attr(state);
}
}
// Inform other widgets, that does not support canJS
for (var i = 0, len = that.onChangeCallbacks.length; i < len; i++) {
that.onChangeCallbacks[i].callback(that.onChangeCallbacks[i].arg, id, state);
}
}
});
},
setValue: function (id, val) {
if (!id) {
console.log('ID is null for val=' + val);
return;
}
var d = new Date();
var t = d.getFullYear() + '-' + ('0' + (d.getMonth() + 1)).slice(-2) + '-' + ('0' + d.getDate()).slice(-2) + " " + ('0' + d.getHours()).slice(-2) + ':' + ('0' + d.getMinutes()).slice(-2) + ':' + ('0' + d.getSeconds()).slice(-2);
var o = {};
var created = false;
if (this.states.attr(id + '.val') != val) {
o[id + '.lc'] = t;
} else {
o[id + '.lc'] = this.states.attr(id + '.lc');
}
o[id + '.val'] = val;
o[id + '.ts'] = t;
o[id + '.ack'] = false;
// Create this value
if (this.states.attr(id + '.val') === undefined) {
created = true;
this.states.attr(o);
}
var that = this;
// if no de-bounce running
if (!this.statesDebounce[id]) {
// send control command
this._setValue(id, o, created);
// Start timeout
this.statesDebounce[id] = {
timeout: _setTimeout(function () {
if (that.statesDebounce[id]) {
if (that.statesDebounce[id].state) that._setValue(id, that.statesDebounce[id].state);
delete that.statesDebounce[id];
}
}, 1000, id),
state: null
};
} else {
// If some de-bounce running, change last value
this.statesDebounce[id].state = o;
}
},
loadWidgetSet: function (name, callback) {
var url = './widgets/' + name + '.html?visVersion=' + this.version;
var that = this;
$.ajax({
url: url,
type: 'GET',
dataType: 'html',
cache: this.useCache,
success: function (data) {
setTimeout(function () {
try {
$('head').append(data);
} catch (e) {
console.error('Cannot load widget set "' + name + '": ' + e);
}
that.toLoadSetsCount -= 1;
if (that.toLoadSetsCount <= 0) {
that.showWaitScreen(true, null, null, 100);
setTimeout(function () {
callback.call(that);
}, 100);
} else {
that.showWaitScreen(true, null, null, parseInt((100 - that.waitScreenVal) / that.toLoadSetsCount, 10));
}
}, 0);
},
error: function (jqXHR, textStatus, errorThrown) {
that.conn.logError('Cannot load widget set ' + name + ' ' + errorThrown);
}
});
},
// Return as array used widgetSets or null if no information about it
getUsedWidgetSets: function () {
var widgetSets = [];
if (!this.views) {
console.log('Check why views are not yet loaded!');
return null;
}
// Convert visConfig.widgetSets to object for easier dependency search
var widgetSetsObj = {};
for (var i = 0; i < visConfig.widgetSets.length; i++) {
if (typeof visConfig.widgetSets[i] === 'object') {
if (!visConfig.widgetSets[i].depends) {
visConfig.widgetSets[i].depends = [];
}
widgetSetsObj[visConfig.widgetSets[i].name] = visConfig.widgetSets[i];
} else {
widgetSetsObj[visConfig.widgetSets[i]] = {depends: []};
}
}
for (var view in this.views) {
if (!this.views.hasOwnProperty(view) || view === '___settings') continue;
for (var id in this.views[view].widgets) {
if (!this.views[view].widgets.hasOwnProperty(id)) continue;
if (!this.views[view].widgets[id].widgetSet) {
// Views are not yet converted and have no widgetSet information)
return null;
} else if (widgetSets.indexOf(this.views[view].widgets[id].widgetSet) === -1) {
var wset = this.views[view].widgets[id].widgetSet;
widgetSets.push(wset);
// Add dependencies
if (widgetSetsObj[wset]) {
for (var u = 0, ulen = widgetSetsObj[wset].depends.length; u < ulen; u++) {
if (widgetSets.indexOf(widgetSetsObj[wset].depends[u]) === -1) {
widgetSets.push(widgetSetsObj[wset].depends[u]);
}
}
}
}
}
}
return widgetSets;
},
// Return as array used widgetSets or null if no information about it
getUsedObjectIDs: function () {
var result = getUsedObjectIDs(this.views, !this.editMode);
if (!result) {
return result;
}
this.visibility = result.visibility;
this.bindings = result.bindings;
this.signals = result.signals;
this.lastChanges = result.lastChanges;
return {IDs: result.IDs, byViews: result.byViews};
},
getWidgetGroup: function (view, widget) {
return getWidgetGroup(this.views, view, widget);
},
loadWidgetSets: function (callback) {
this.showWaitScreen(true, '<br>' + _('Loading Widget-Sets...') + ' <span id="widgetset_counter"></span>', null, 20);
var arrSets = [];
// If widgets are pre-loaded
if (this.binds && this.binds.stateful !== undefined) {
this.toLoadSetsCount = 0;
} else {
// Get list of used widget sets. if Edit mode list is null.
var widgetSets = this.editMode ? null : this.getUsedWidgetSets();
// First calculate how many sets to load
for (var i = 0; i < this.widgetSets.length; i++) {
var name = this.widgetSets[i].name || this.widgetSets[i];
// Skip unused widget sets in non-edit mode
if (!this.widgetSets[i].always) {
if (this.widgetSets[i].widgetSets && widgetSets.indexOf(name) === -1) {
continue;
}
} else {
if (widgetSets && widgetSets.indexOf(name) === -1) widgetSets.push(name);
}
arrSets[arrSets.length] = name;
if (this.editMode && this.widgetSets[i].edit) {
arrSets[arrSets.length] = this.widgetSets[i].edit;
}
}
this.toLoadSetsCount = arrSets.length;
$("#widgetset_counter").html("<span style='font-size:10px'>(" + (this.toLoadSetsCount) + ")</span>");
}
var that = this;
if (this.toLoadSetsCount) {
for (var j = 0, len = this.toLoadSetsCount; j < len; j++) {
_setTimeout(function (_i) {
that.loadWidgetSet(arrSets[_i], callback);
}, 100, j);
}
} else {
if (callback) callback.call(this);
}
},
bindInstance: function () {
if (typeof app !== 'undefined' && app.settings) {
this.instance = app.settings.instance;
}
if (typeof storage !== 'undefined') {
this.instance = this.instance || storage.get(this.storageKeyInstance);
}
if (this.editMode) {
this.bindInstanceEdit();
}
this.states.attr({'instance.val': this.instance, 'instance': this.instance});
},
init: function (onReady) {
if (this.initialized) return;
if (typeof storage !== 'undefined') {
var settings = storage.get(this.storageKeySettings);
if (settings) {
this.settings = $.extend(this.settings, settings);
}
}
// Late initialization (used only for debug)
/*if (this.binds.hqWidgetsExt) {
this.binds.hqWidgetsExt.hqInit();
}*/
var that = this;
//this.loadRemote(this.loadWidgetSets, this.initNext);
this.loadWidgetSets(function () {
that.initNext(onReady);
});
},
initNext: function (onReady) {
this.showWaitScreen(false);
var that = this;
// First start.
if (!this.views) {
this.initViewObject();
} else {
this.showWaitScreen(false);
}
var hash = decodeURIComponent(window.location.hash.substring(1));
// create demo states
if (this.views && this.views.DemoView) this.createDemoStates();
if (!this.views || (!this.views[hash] && typeof app !== 'undefined')) hash = null;
// View selected?
if (!hash) {
// Take first view in the list
this.activeView = this.findNearestResolution(true);
this.activeViewDiv = this.activeView;
// Create default view in demo mode
if (typeof io === 'undefined') {
if (!this.activeView) {
if (!this.editMode) {
window.alert(_('error - View doesn\'t exist'));
if (typeof app === 'undefined') {
// try to find first view
window.location.href = 'edit.html?' + this.projectPrefix.substring(0, this.projectPrefix.length - 1);
}
} else {
this.views.DemoView = this.createDemoView ? this.createDemoView() : {
settings: {style: {}},
widgets: {}
};
this.activeView = 'DemoView';
this.activeViewDiv = this.activeView;
}
}
} else if (!this.activeView) {
if (!this.editMode) {
if (typeof app === 'undefined') {
window.alert(_('error - View doesn\'t exist'));
window.location.href = 'edit.html?' + this.projectPrefix.substring(0, this.projectPrefix.length - 1);
}
} else {
// All views were deleted, but file exists. Create demo View
//window.alert("unexpected error - this should not happen :(");
//$.error("this should not happen :(");
// create demoView
this.views.DemoView = this.createDemoView ? this.createDemoView() : {
settings: {style: {}},
widgets: {}
};
this.activeView = 'DemoView';
this.activeViewDiv = this.activeView;
}
}
} else {
if (this.views[hash]) {
this.activeView = hash;
this.activeViewDiv = this.activeView;
} else {
window.alert(_('error - View doesn\'t exist'));
if (typeof app === 'undefined') window.location.href = 'edit.html?' + this.projectPrefix.substring(0, this.projectPrefix.length - 1);
$.error("vis Error can't find view");
}
}
if (this.views && this.views.___settings) {
if (this.views.___settings.reloadOnSleep !== undefined) this.conn.setReloadTimeout(this.views.___settings.reloadOnSleep);
if (this.views.___settings.darkReloadScreen) {
$('#server-disconnect').removeClass('disconnect-light').addClass('disconnect-dark');
}
if (this.views.___settings.reconnectInterval !== undefined) this.conn.setReconnectInterval(this.views.___settings.reconnectInterval);
if (this.views.___settings.destroyViewsAfter !== undefined) this.views.___settings.destroyViewsAfter = parseInt(this.views.___settings.destroyViewsAfter, 10);
}
// Navigation
$(window).bind('hashchange', function (/* e */) {
var view = window.location.hash.slice(1);
that.changeView(view, view);
});
this.bindInstance();
// EDIT mode
if (this.editMode) this.editInitNext();
this.initialized = true;
// If this function called earlier, it makes problems under FireFox.
// render all views, that should be always rendered
var containers = [];
var cnt = 0;
if (this.views && !this.editMode) {
for (var view in this.views) {
if (!this.views.hasOwnProperty(view) || view === '___settings') continue;
if (this.views[view].settings.alwaysRender) {
containers.push({view: view});
}
}
if (containers.length) {
cnt++;
this.renderViews(that.activeViewDiv, containers, function () {
cnt--;
if (that.activeView) {
that.changeView(that.activeViewDiv, that.activeView, function () {
if (!cnt && onReady) {
onReady();
}
});
}
});
}
}
if (!containers.length && this.activeView) {
this.changeView(this.activeViewDiv, this.activeView, onReady);
}
},
initViewObject: function () {
if (!this.editMode) {
if (typeof app !== 'undefined') {
this.showMessage(_('no views found!'));
} else {
window.location.href = 'edit.html?' + this.projectPrefix.substring(0, this.projectPrefix.length - 1);
}
} else {
if (window.confirm(_('no views found on server.\nCreate new %s ?', this.projectPrefix + 'vis-views.json'))) {
this.views = {};
this.views.DemoView = this.createDemoView ? this.createDemoView() : {
settings: {style: {}},
widgets: {}
};
if (this.saveRemote) {
this.saveRemote(true, function () {
//window.location.reload();
});
}
} else {
window.location.reload();
}
}
},
setViewSize: function (viewDiv, view) {
var $view = $('#visview_' + viewDiv);
var width;
var height;
if (this.views[view]) {
// Because of background, set the width and height of the view
width = parseInt(this.views[view].settings.sizex, 10);
height = parseInt(this.views[view].settings.sizey, 10);
}
var $vis_container = $('#vis_container');
if (!width || width < $vis_container.width()) width = '100%';
if (!height || height < $vis_container.height()) height = '100%';
$view.css({width: width, height: height});
},
updateContainers: function (viewDiv, view) {
var that = this;
// Set ths views for containers
$('#visview_' + viewDiv).find('.vis-view-container').each(function () {
var cview = $(this).attr('data-vis-contains');
if (!that.views[cview]) {
$(this).html('<span style="color: red" class="container-error">' + _('error: view not found.') + '</span>');
} else if (cview === view || cview === viewDiv) {
$(this).html('<span style="color: red" class="container-error">' + _('error: view container recursion.') + '</span>');
} else {
if ($(this).find('.container-error').length) {
$(this).html('');
}
var targetView = this;
if (!$(this).find('.vis-widget:first').length) {
that.renderView(cview, cview, function (_viewDiv) {
$('#visview_' + _viewDiv)
.appendTo(targetView)
.show();
});
} else {
$('#visview_' + cview)
.appendTo(targetView)
.show();
}
}
});
},
renderViews: function (viewDiv, views, index, callback) {
if (typeof index === 'function') {
callback = index;
index = 0;
}
index = index || 0;
if (!views || index >= views.length) {
if (callback) callback(viewDiv, views);
return;
}
var item = views[index];
var that = this;
this.renderView(this.views[item.view] ? item.view : viewDiv, item.view, true, function () {
that.renderViews(viewDiv, views, index + 1, callback);
});
},
renderView: function (viewDiv, view, hidden, callback) {
var that = this;
if (typeof hidden === 'function') {
callback = hidden;
hidden = undefined;
}
if (typeof view === 'boolean') {
callback = hidden;
hidden = undefined;
view = viewDiv;
}
if (!this.editMode && !$('#commonTheme').length) {
$('head').prepend('<link rel="stylesheet" type="text/css" href="' + ((typeof app === 'undefined') ? '../../' : '') + 'lib/css/themes/jquery-ui/' + (this.calcCommonStyle() || 'redmond') + '/jquery-ui.min.css" id="commonTheme"/>');
}
if (!this.views[view] || !this.views[view].settings) {
window.alert('Cannot render view ' + view + '. Invalid settings');
if (callback) {
setTimeout(function () {
callback(viewDiv, view);
}, 0);
}
return false;
}
// try to render background
// collect all IDs, used in this view and in containers
this.subscribeStates(view, function () {
var isViewsConverted = false; // Widgets in the views hav no information which WidgetSet they use, this info must be added and this flag says if that happens to store the views
that.views[view].settings.theme = that.views[view].settings.theme || 'redmond';
if (that.views[view].settings.filterkey) {
that.viewsActiveFilter[view] = that.views[view].settings.filterkey.split(',');
} else {
that.viewsActiveFilter[view] = [];
}
//noinspection JSJQueryEfficiency
var $view = $('#visview_' + viewDiv);
// apply group policies
if (!that.editMode && that.views[view].settings.group && that.views[view].settings.group.length) {
if (that.views[view].settings.group_action === 'hide') {
if (!that.isUserMemberOf(that.conn.getUser(), that.views[view].settings.group)) {
if (!$view.length) {
$('#vis_container').append('<div id="visview_' + viewDiv + '" class="vis-view vis-user-disabled"></div>');
$view = $('#visview_' + viewDiv);
}
$view.html('<div class="vis-view-disabled-text">' + _('View disabled for user %s', that.conn.getUser()) + '</div>');
if (callback) {
setTimeout(function () {
callback(viewDiv, view);
}, 0);
}
return;
}
}
}
if (!$view.length) {
$('#vis_container').append('<div style="display: none;" id="visview_' + viewDiv + '" ' +
'data-view="' + view + '" ' +
'class="vis-view ' + (viewDiv !== view ? 'vis-edit-group' : '') + '" ' +
(that.views[view].settings.alwaysRender ? 'data-persistent="true"' : '') + '>' +
'<div class="vis-view-disabled" style="display: none"></div>' +
'</div>');
that.addViewStyle(viewDiv, view, that.views[view].settings.theme);
$view = $('#visview_' + viewDiv);
$view.css(that.views[view].settings.style);
if (that.views[view].settings.style.background_class) {
$view.addClass(that.views[view].settings.style.background_class);
}
var id;
if (viewDiv !== view && that.editMode) {
//noinspection JSJQueryEfficiency
var $widget = $('#' + viewDiv);
if (!$widget.length) {
that.renderWidget(view, view, viewDiv);
$widget = $('#' + viewDiv);
}
$view.append('<div class="group-edit-header" data-view="' + viewDiv + '">' + _('Edit group:') + ' <b>' + viewDiv + '</b><button class="group-edit-close"></button></div>');
$view.find('.group-edit-close').button({
icons: {
primary: 'ui-icon-close'
},
text: false
}).data('view', view).css({width: 20, height: 20}).click(function () {
var view = $(this).data('view');
that.changeView(view, view);
});
$widget.appendTo($view);
$widget.css({top: 0, left: 0});
/*$widget.unbind('click dblclick');
$widget.find('.vis-widget').each(function () {
var id = $(this).attr('id');
that.bindWidgetClick(view, id, true);
});*/
} else {
that.setViewSize(viewDiv, view);
// Render all widgets
for (id in that.views[view].widgets) {
if (!that.views[view].widgets.hasOwnProperty(id)) continue;
// Try to complete the widgetSet information to optimize the loading of widgetSets
if (id[0] !== 'g' && !that.views[view].widgets[id].widgetSet) {
var obj = $('#' + that.views[view].widgets[id].tpl);
if (obj) {
that.views[view].widgets[id].widgetSet = obj.attr('data-vis-set');
isViewsConverted = true;
}
}
if (!that.views[view].widgets[id].renderVisible && !that.views[view].widgets[id].grouped) that.renderWidget(viewDiv, view, id);
}
}
if (that.editMode) {
if (that.binds.jqueryui) that.binds.jqueryui._disable();
that.droppable(viewDiv, view);
}
}
// move views in container
var containers = [];
$view.find('.vis-view-container').each(function () {
var cview = $(this).attr('data-vis-contains');
if (!that.views[cview]) {
$(this).append('error: view not found.');
return false;
} else if (cview === view) {
$(this).append('error: view container recursion.');
return false;
}
containers.push({thisView: this, view: cview});
});
// add view class
if (that.views[view].settings['class']) {
$view.addClass(that.views[view].settings['class'])
}
var wait = false;
if (containers.length) {
wait = true;
that.renderViews(viewDiv, containers, function (_viewDiv, _containers) {
for (var c = 0; c < _containers.length; c++) {
$('#visview_' + _containers[c].view)
.appendTo(_containers[c].thisView)
.show();
}
if (!hidden) $view.show();
$('#visview_' + _viewDiv).trigger('rendered');
if (callback) callback(_viewDiv, view);
});
}
// Store modified view
if (isViewsConverted && that.saveRemote) that.saveRemote();
if (that.editMode && $('#wid_all_lock_function').prop('checked')) {
$('.vis-widget').addClass('vis-widget-lock');
if (viewDiv !== view) {
$('#' + viewDiv).removeClass('vis-widget-lock');
}
}
if (!wait) {
if (!hidden) $view.show();
setTimeout(function () {
$('#visview_' + viewDiv).trigger('rendered');
if (callback) callback(viewDiv, view);
}, 0);
}
// apply group policies
if (!that.editMode && that.views[view].settings.group && that.views[view].settings.group.length) {
if (that.views[view].settings.group_action !== 'hide') {
if (!that.isUserMemberOf(that.conn.getUser(), that.views[view].settings.group)) {
$view.addClass('vis-user-disabled');
}
}
}
});
},
addViewStyle: function (viewDiv, view, theme) {
var _view = 'visview_' + viewDiv;
if (this.calcCommonStyle() === theme) return;
$.ajax({
url: ((typeof app === 'undefined') ? '../../' : '') + 'lib/css/themes/jquery-ui/' + theme + '/jquery-ui.min.css',
cache: false,
success: function (data) {
$('#' + viewDiv + '_style').remove();
data = data.replace('.ui-helper-hidden', '#' + _view + ' .ui-helper-hidden');
data = data.replace(/(}.)/g, '}#' + _view + ' .');
data = data.replace(/,\./g, ',#' + _view + ' .');
data = data.replace(/images/g, ((typeof app === 'undefined') ? '../../' : '') + 'lib/css/themes/jquery-ui/' + theme + '/images');
var $view = $('#' + _view);
$view.append('<style id="' + viewDiv + '_style">' + data + '</style>');
$('#' + viewDiv + '_style_common_user').remove();
$view.append('<style id="' + viewDiv + '_style_common_user" class="vis-common-user">' + $('#vis-common-user').html() + '</style>');
$('#' + viewDiv + '_style_user').remove();
$view.append('<style id="' + viewDiv + '_style_user" class="vis-user">' + $('#vis-user').html() + '</style>');
}
});
},
preloadImages: function (srcs) {
if (!this.preloadImages.cache) {
this.preloadImages.cache = [];
}
var img;
for (var i = 0; i < srcs.length; i++) {
img = new Image();
img.src = srcs[i];
this.preloadImages.cache.push(img);
}
},
destroyWidget: function (viewDiv, view, widget) {
var $widget = $('#' + widget);
if ($widget.length) {
var widgets = this.views[view].widgets[widget].data.members;
if (widgets) {
for (var w = 0; w < widgets.length; w++) {
if (widgets[w] !== widget) {
this.destroyWidget(viewDiv, view, widgets[w]);
} else {
console.warn('Cyclic structure in ' + widget + '!');
}
}
}
try {
// get array of bound OIDs
var bound = $widget.data('bound');
if (bound) {
var bindHandler = $widget.data('bindHandler');
for (var b = 0; b < bound.length; b++) {
if (typeof bindHandler === 'function') {
this.states.unbind(bound[b], bindHandler);
} else {
this.states.unbind(bound[b], bindHandler[b]);
}
}
$widget.data('bindHandler', null);
$widget.data('bound', null);
}
// If destroy function exists => destroy it
var destroy = $widget.data('destroy');
if (typeof destroy === 'function') {
destroy(widget, $widget);
}
} catch (e) {
console.error('Cannot destroy "' + widget + '": ' + e);
}
}
},
reRenderWidget: function (viewDiv, view, widget) {
var $widget = $('#' + widget);
var updateContainers = $widget.find('.vis-view-container').length;
view = view || this.activeView;
viewDiv = viewDiv || this.activeViewDiv;
this.destroyWidget(viewDiv, view, widget);
this.renderWidget(viewDiv, view, widget, !this.views[viewDiv] && viewDiv !== widget ? viewDiv : null);
if (updateContainers) this.updateContainers(viewDiv, view);
},
changeFilter: function (view, filter, showEffect, showDuration, hideEffect, hideDuration) {
view = view || this.activeView;
// convert from old style
if (!this.views[view]) {
hideDuration = hideEffect;
hideEffect = showDuration;
showDuration = showEffect;
showEffect = filter;
filter = view;
view = this.activeView;
}
var widgets = this.views[view].widgets;
var that = this;
var widget;
var mWidget;
if (!(filter || '').trim()) {
// show all
for (widget in widgets) {
if (!widgets.hasOwnProperty(widget)) continue;
if (widgets[widget] && widgets[widget].data && widgets[widget].data.filterkey) {
$('#' + widget).show(showEffect, null, parseInt(showDuration));
}
}
// Show complex widgets
setTimeout(function () {
var mWidget;
for (var widget in widgets) {
if (!widgets.hasOwnProperty(widget)) continue;
mWidget = document.getElementById(widget);
if (widgets[widget] &&
widgets[widget].data &&
widgets[widget].data.filterkey &&
mWidget &&
mWidget._customHandlers &&
mWidget._customHandlers.onShow) {
mWidget._customHandlers.onShow(mWidget, widget);
}
}
}, parseInt(showDuration) + 10);
} else if (filter === '$') {
// hide all
for (widget in widgets) {
if (!widgets.hasOwnProperty(widget)) continue;
if (!widgets[widget] || !widgets[widget].data || !widgets[widget].data.filterkey) continue;
mWidget = document.getElementById(widget);
if (mWidget &&
mWidget._customHandlers &&
mWidget._customHandlers.onHide) {
mWidget._customHandlers.onHide(mWidget, widget);
}
$('#' + widget).hide(hideEffect, null, parseInt(hideDuration));
}
} else {
this.viewsActiveFilter[this.activeView] = filter.split(',');
var vFilters = this.viewsActiveFilter[this.activeView];
for (widget in widgets) {
if (!widgets.hasOwnProperty(widget) || !widgets[widget] || !widgets[widget].data) continue;
var wFilters = widgets[widget].data.filterkey;
if (wFilters) {
if (typeof wFilters !== 'object') {
widgets[widget].data.filterkey = wFilters.split(/[;,]+/);
wFilters = widgets[widget].data.filterkey;
}
var found = false;
// optimization
if (wFilters.length === 1) {
found = vFilters.indexOf(wFilters[0]) !== -1;
} else if (vFilters.length === 1) {
found = wFilters.indexOf(vFilters[0]) !== -1;
} else {
for (var f = 0; f < wFilters.length; f++) {
if (vFilters.indexOf(wFilters[f]) !== -1) {
found = true;
break;
}
}
}
if (!found) {
mWidget = document.getElementById(widget);
if (mWidget &&
mWidget._customHandlers &&
mWidget._customHandlers.onHide) {
mWidget._customHandlers.onHide(mWidget, widget);
}
$('#' + widget).hide(hideEffect, null, parseInt(hideDuration));
} else {
mWidget = document.getElementById(widget);
if (mWidget && mWidget._customHandlers && mWidget._customHandlers.onShow) {
mWidget._customHandlers.onShow(mWidget, widget);
}
$('#' + widget).show(showEffect, null, parseInt(showDuration));
}
}
}
setTimeout(function () {
var mWidget;
// Show complex widgets like hqWidgets or bars
for (var widget in widgets) {
if (!widgets.hasOwnProperty(widget)) continue;
mWidget = document.getElementById(widget);
if (mWidget &&
mWidget._customHandlers &&
mWidget._customHandlers.onShow) {
if (widgets[widget] && widgets[widget].data && widgets[widget].data.filterkey) {
if (!(that.viewsActiveFilter[that.activeView].length > 0 &&
that.viewsActiveFilter[that.activeView].indexOf(widgets[widget].data.filterkey) === -1)) {
mWidget._customHandlers.onShow(mWidget, widget);
}
}
}
}
}, parseInt(showDuration) + 10);
}
if (this.binds.bars && this.binds.bars.filterChanged) {
this.binds.bars.filterChanged(view, filter);
}
},
isSignalVisible: function (view, widget, index, val, widgetData) {
widgetData = widgetData || this.views[view].widgets[widget].data;
var oid = widgetData['signals-oid-' + index];
if (oid) {
if (val === undefined) val = this.states.attr(oid + '.val');
var condition = widgetData['signals-cond-' + index];
var value = widgetData['signals-val-' + index];
if (val === undefined) return (condition === 'not exist');
if (!condition || value === undefined) return (condition === 'not exist');
if (val === 'null' && condition !== 'exist' && condition !== 'not exist') return false;
var t = typeof val;
if (t === 'boolean' || val === 'false' || val === 'true') {
value = (value === 'true' || value === true || value === 1 || value === '1');
} else
if (t === 'number') {
value = parseFloat(value);
} else
if (t === 'object') {
val = JSON.stringify(val);
}
switch (condition) {
case '==':
value = value.toString();
val = val.toString();
if (val === '1') val = 'true';
if (value === '1') value = 'true';
if (val === '0') val = 'false';
if (value === '0') value = 'false';
return value === val;
case '!=':
value = value.toString();
val = val.toString();
if (val === '1') val = 'true';
if (value === '1') value = 'true';
if (val === '0') val = 'false';
if (value === '0') value = 'false';
return value !== val;
case '>=':
return val >= value;
case '<=':
return val <= value;
case '>':
return val > value;
case '<':
return val < value;
case 'consist':
value = value.toString();
val = val.toString();
return (val.toString().indexOf(value) !== -1);
case 'not consist':
value = value.toString();
val = val.toString();
return (val.toString().indexOf(value) === -1);
case 'exist':
return (value !== 'null');
case 'not exist':
return (value === 'null');
default:
console.log('Unknown signals condition for ' + widget + ': ' + condition);
return false;
}
} else {
return false;
}
},
addSignalIcon: function (view, wid, data, index) {
// show icon
var display = (this.editMode || this.isSignalVisible(view, wid, index, undefined, data)) ? '' : 'none';
if (this.editMode && data['signals-hide-edit-' + index]) display = 'none';
$('#' + wid).append('<div class="vis-signal ' + (data['signals-blink-' + index] ? 'vis-signals-blink' : '') + ' ' + (data['signals-text-class-' + index] || '') + ' " data-index="' + index + '" style="display: ' + display + '; pointer-events: none; position: absolute; z-index: 10; top: ' + (data['signals-vert-' + index] || 0) + '%; left: ' + (data['signals-horz-' + index] || 0) + '%"><img class="vis-signal-icon" src="' + data['signals-icon-' + index] + '" style="width: ' + (data['signals-icon-size-' + index] || 32) + 'px; height: auto;' + (data['signals-icon-style-' + index] || '') + '"/>' +
(data['signals-text-' + index] ? ('<div class="vis-signal-text " style="' + (data['signals-text-style-' + index] || '') + '">' + data['signals-text-' + index] + '</div>') : '') + '</div>');
},
addGestures: function (id, wdata) {
// gestures
var gestures = ['swipeRight', 'swipeLeft', 'swipeUp', 'swipeDown', 'rotateLeft', 'rotateRight', 'pinchIn', 'pinchOut', 'swiping', 'rotating', 'pinching'];
var $$wid = $$('#' + id);
var $wid = $('#' + id);
var offsetX = parseInt(wdata['gestures-offsetX']) || 0;
var offsetY = parseInt(wdata['gestures-offsetY']) || 0;
var that = this;
gestures.forEach(function (gesture) {
if (wdata && wdata['gestures-' + gesture + '-oid']) {
var oid = wdata['gestures-' + gesture + '-oid'];
if (oid) {
var val = wdata['gestures-' + gesture + '-value'];
var delta = parseInt(wdata['gestures-' + gesture + '-delta']) || 10;
var limit = parseFloat(wdata['gestures-' + gesture + '-limit']) || false;
var max = parseFloat(wdata['gestures-' + gesture + '-maximum']) || 100;
var min = parseFloat(wdata['gestures-' + gesture + '-minimum']) || 0;
var valState = that.states.attr(oid + '.val');
var newVal = null;
var $indicator;
if (valState !== undefined) {
$wid.on('touchmove', function (evt) {
evt.preventDefault();
});
$wid.css({
'-webkit-user-select': 'none',
'-khtml-user-select': 'none',
'-moz-user-select': 'none',
'-ms-user-select': 'none',
'user-select': 'none'
});
$$wid[gesture](function (data) {
valState = that.states.attr(oid + '.val');
if (val === 'toggle') {
if (valState === true) {
newVal = false;
} else if (valState === false) {
newVal = true;
} else {
newVal = null;
return;
}
} else if (gesture === 'swiping' || gesture === 'rotating' || gesture === 'pinching') {
if (newVal === null) {
$indicator = $('#' + wdata['gestures-indicator']);
// create default indicator
if (!$indicator.length) {