/** * yunkong2.vis * https://github.com/yunkong2/yunkong2.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', 'cn': '未连接服务器'}, 'Loading Views...': {'en': 'Loading Views...', 'cn': '加载视图...'}, 'Connecting to Server...': {'en': 'Connecting to Server...', 'cn': '连接到服务器...'}, 'Loading data objects...': {'en': 'Loading data...', 'cn': '加载结构...'}, 'Loading data values...': {'en': 'Loading values...', 'cn': '加载数据...'}, 'error - View doesn\'t exist': {'en': 'View doesn\'t exist!', 'cn': '视图不存在!'}, 'no views found!': {'en': 'No views found!', 'cn': '未找到视图!'}, 'No Views found on Server': { 'en': 'No Views found on Server', 'cn': '视图不存在.' }, 'All changes are saved locally. To reset changes clear the cache.': { 'en': 'All changes are saved locally. To reset changes clear the browser cache.' }, 'please use /vis/edit.html instead of /vis/?edit': { 'en': 'Please use /vis/edit.html instead of /vis/?edit' }, 'no views found on server.\nCreate new %s ?': { 'en': 'no views found on server.\nCreate new %s?' }, 'Update found, loading new Files...': { 'en': 'Update found.
Loading new Files...' }, 'Loading Widget-Sets...': { 'en': 'Loading Widget-Sets...', 'cn': 'Loading Widget-Sets...' }, 'error: view not found.': { 'en': 'Error: view not found', 'cn': 'Error: 未找到视图' }, 'error: view container recursion.': { 'en': 'Error: view container recursion', 'cn': 'Error: view container recursion' }, "Cannot execute %s for %s, because of insufficient permissions": { "en": "Cannot execute %s for %s, because of insufficient permissions." }, "Insufficient permissions": { "en": "Insufficient permissions", "cn": "Insufficient permissions" }, "View disabled for user %s": { "en": "View disabled for user %s", "cn": "View disabled for user %s" } }); } if (typeof systemLang !== 'undefined' && typeof cordova === 'undefined') { systemLang = visConfig.language || systemLang; } var vis = { version: '1.1.7', 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, '
' + _('Loading Widget-Sets...') + ' ', 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("(" + (this.toLoadSetsCount) + ")"); } 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('' + _('error: view not found.') + ''); } else if (cview === view || cview === viewDiv) { $(this).html('' + _('error: view container recursion.') + ''); } 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(''); } 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('
'); $view = $('#visview_' + viewDiv); } $view.html('
' + _('View disabled for user %s', that.conn.getUser()) + '
'); if (callback) { setTimeout(function () { callback(viewDiv, view); }, 0); } return; } } } if (!$view.length) { $('#vis_container').append(''); 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('
' + _('Edit group:') + ' ' + viewDiv + '
'); $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(''); $('#' + viewDiv + '_style_common_user').remove(); $view.append(''); $('#' + viewDiv + '_style_user').remove(); $view.append(''); } }); }, 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(''); }, 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) { //noinspection JSJQueryEfficiency $indicator = $('#gestureIndicator'); if (!$indicator.length) { $('body').append('
'); $indicator = $('#gestureIndicator'); $indicator.on('gestureUpdate', function (event, evData) { if (evData.val === null) { $(this).hide(); } else { $(this).html(evData.val); $(this).css({ left: parseInt(evData.x) - $(this).width() / 2 + 'px', top: parseInt(evData.y) - $(this).height() / 2 + 'px' }).show(); } }); } } $('#vis_container').css({ '-webkit-user-select': 'none', '-khtml-user-select': 'none', '-moz-user-select': 'none', '-ms-user-select': 'none', 'user-select': 'none' }); $(document).on('mouseup.gesture touchend.gesture', function () { if (newVal !== null) { that.setValue(oid, newVal); newVal = null; } $indicator.trigger('gestureUpdate', {val: null}); $(document).off('mouseup.gesture touchend.gesture'); $('#vis_container').css({ '-webkit-user-select': 'text', '-khtml-user-select': 'text', '-moz-user-select': 'text', '-ms-user-select': 'text', 'user-select': 'text' }); }); } var swipeDelta, indicatorX, indicatorY = 0; switch (gesture) { case 'swiping': swipeDelta = Math.abs(data.touch.delta.x) > Math.abs(data.touch.delta.y) ? data.touch.delta.x : data.touch.delta.y * (-1); swipeDelta = swipeDelta > 0 ? Math.floor(swipeDelta / delta) : Math.ceil(swipeDelta / delta); indicatorX = data.touch.x; indicatorY = data.touch.y; break; case 'rotating': swipeDelta = data.touch.delta; swipeDelta = swipeDelta > 0 ? Math.floor(swipeDelta / delta) : Math.ceil(swipeDelta / delta); if (data.touch.touches[0].y < data.touch.touches[1].y) { indicatorX = data.touch.touches[1].x; indicatorY = data.touch.touches[1].y; } else { indicatorX = data.touch.touches[0].x; indicatorY = data.touch.touches[0].y; } break; case 'pinching': swipeDelta = data.touch.delta; swipeDelta = swipeDelta > 0 ? Math.floor(swipeDelta / delta) : Math.ceil(swipeDelta / delta); if (data.touch.touches[0].y < data.touch.touches[1].y) { indicatorX = data.touch.touches[1].x; indicatorY = data.touch.touches[1].y; } else { indicatorX = data.touch.touches[0].x; indicatorY = data.touch.touches[0].y; } break; default: break; } newVal = (parseFloat(valState) || 0) + (parseFloat(val) || 1) * swipeDelta; newVal = Math.max(min, Math.min(max, newVal)); $indicator.trigger('gestureUpdate', { val: newVal, x: indicatorX + offsetX, y: indicatorY + offsetY }); return; } else if (limit !== false) { newVal = (parseFloat(valState) || 0) + (parseFloat(val) || 1); if (parseFloat(val) > 0 && newVal > limit) { newVal = limit; } else if (parseFloat(val) < 0 && newVal < limit) { newVal = limit; } } else { newVal = val; } that.setValue(oid, newVal); newVal = null; }); } } } }); }, addLastChange: function (view, wid, data) { // show last change var border = (parseInt(data['lc-border-radius'], 10) || 0) + 'px'; var css = { background: 'rgba(182,182,182,0.6)', 'font-family': 'Tahoma', position: 'absolute', 'z-index': 0, 'border-radius': data['lc-position-horz'] === 'left' ? (border + ' 0 0 ' + border) : (data['lc-position-horz'] === 'right' ? '0 ' + border + ' ' + border + ' 0' : border), 'white-space': 'nowrap' }; if (data['lc-font-size']) { css['font-size'] = data['lc-font-size']; } if (data['lc-font-style']) { css['font-style'] = data['lc-font-style']; } if (data['lc-font-family']) { css['font-family'] = data['lc-font-family']; } if (data['lc-bkg-color']) { css['background'] = data['lc-bkg-color']; } if (data['lc-color']) { css['color'] = data['lc-color']; } if (data['lc-border-width']) { css['border-width'] = parseInt(data['lc-border-width'], 10) || 0; } if (data['lc-border-style']) { css['border-style'] = data['lc-border-style']; } if (data['lc-border-color']) { css['border-color'] = data['lc-border-color']; } if (data['lc-padding']) { css['padding'] = data['lc-padding']; } else { css['padding-top'] = 3; css['padding-bottom'] = 3; } if (data['lc-zindex']) { css['z-index'] = data['lc-zindex']; } if (data['lc-position-vert'] === 'top') { css.top = parseInt(data['lc-offset-vert'], 10); } else if (data['lc-position-vert'] === 'bottom') { css.bottom = parseInt(data['lc-offset-vert'], 10); } else if (data['lc-position-vert'] === 'middle') { css.top = 'calc(50% + ' + (parseInt(data['lc-offset-vert'], 10) - 10) + 'px)'; } var offset = parseFloat(data['lc-offset-horz']) || 0; if (data['lc-position-horz'] === 'left') { css.right = 'calc(100% - ' + offset + 'px)'; if (!data['lc-padding']) { css['padding-right'] = 10; css['padding-left'] = 10; } } else if (data['lc-position-horz'] === 'right') { css.left = 'calc(100% + ' + offset + 'px)'; if (!data['lc-padding']) { css['padding-right'] = 10; css['padding-left'] = 10; } } else if (data['lc-position-horz'] === 'middle') { css.left = 'calc(50% + ' + offset + 'px)'; } var text = '
' + this.binds.basic.formatDate(this.states.attr(data['lc-oid'] + '.ts'), data['lc-format'], data['lc-is-interval'], data['lc-is-moment']) + '
'; $('#' + wid).prepend($(text).css(css)).css('overflow', 'visible'); }, isUserMemberOf: function (user, userGroups) { if (!this.userGroups) return true; if (typeof userGroups !== 'object') userGroups = [userGroups]; for (var g = 0; g < userGroups.length; g++) { var group = this.userGroups['system.group.' + userGroups[g]]; if (!group || !group.common || !group.common.members || !group.common.members.length) continue; if (group.common.members.indexOf('system.user.' + user) !== -1) return true; } return false; }, renderWidget: function (viewDiv, view, id, groupId) { var $view; var that = this; if (!groupId) { $view = $('#visview_' + viewDiv); } else { $view = $('#' + groupId); } if (!$view.length) return; var widget = this.views[view].widgets[id]; if (groupId && widget) { widget = JSON.parse(JSON.stringify(widget)); var aCount = parseInt(this.views[view].widgets[groupId].data.attrCount, 10); if (aCount) { $.map(widget.data, function(val, key) { var m; if (typeof val === 'string' && (m = val.match(/^groupAttr(\d+)$/))) { widget.data[key] = that.views[view].widgets[groupId].data[m[0]] || ''; } }); } } var isRelative = widget && widget.style && (widget.style.position === 'relative' || widget.style.position === 'static' || widget.style.position === 'sticky'); // if widget has relative position => insert it into relative div if (this.editMode && isRelative && viewDiv === view) { if (this.views[view].settings && this.views[view].settings.sizex) { var $relativeView = $view.find('.vis-edit-relative'); if (!$relativeView.length) { var ww = this.views[view].settings.sizex; var hh = this.views[view].settings.sizey; if (parseFloat(ww).toString() === ww.toString()) ww = parseFloat(ww); if (parseFloat(hh).toString() === hh.toString()) hh = parseFloat(hh); if (typeof ww === 'number' || ww[ww.length - 1] < '0' || ww[ww.length - 1] > '9') { ww = ww + 'px'; } if (typeof hh === 'number' || hh[hh.length - 1] < '0' || hh[hh.length - 1] > '9') { hh = hh + 'px'; } $view.append('
'); $view = $view.find('.vis-edit-relative'); } else { $view = $relativeView; } } } // Add to the global array of widgets try { var userGroups; if (!this.editMode && widget.data['visibility-groups'] && widget.data['visibility-groups'].length) { userGroups = widget.data['visibility-groups']; if (widget.data['visibility-groups-action'] === 'hide') { if (!this.isUserMemberOf(this.conn.getUser(), userGroups)) return; userGroups = null; } } this.widgets[id] = { wid: id, data: new can.Map($.extend({ wid: id }, widget.data)) }; } catch (e) { console.log('Cannot bind data of widget widget:' + id); return; } // Register oid to detect changes // if (widget.data.oid !== 'nothing_selected') // $.homematic("advisState", widget.data.oid, widget.data.hm_wid); var widgetData = this.widgets[id].data; try { //noinspection JSJQueryEfficiency var $widget = $('#' + id); if ($widget.length) { var destroy = $widget.data('destroy'); if (typeof destroy === 'function') { $widget.off('resize'); // remove resize handler destroy(id, $widget); $widget.data('destroy', null); } if (isRelative && !$view.find('#' + id).length) { $widget.remove(); $widget.length = 0; } else { $widget.html('
').attr('id', id + '_removed'); } } var canWidget; // Append html element to view if (widget.data && widget.data.oid) { canWidget = can.view(widget.tpl, { val: this.states.attr(widget.data.oid + '.val'), data: widgetData, viewDiv: viewDiv, view: view }); if ($widget.length) { if ($widget.parent().attr('id') !== $view.attr('id')) $widget.appendTo($view); $widget.replaceWith(canWidget); // shift widget to group if required } else { $view.append(canWidget); } } else if (widget.tpl) { canWidget = can.view(widget.tpl, { data: widgetData, viewDiv: viewDiv, view: view }); if ($widget.length) { if ($widget.parent().attr('id') !== $view.attr('id')) $widget.appendTo($view); $widget.replaceWith(canWidget); // shift widget to group if required } else { $view.append(canWidget); } } else { console.error('Widget "' + id + '" is invalid. Please delete it.'); return; } var $wid = null; if (widget.style && !widgetData._no_style) { $wid = $wid || $('#' + id); // fix position for (var attr in widget.style) { if (!widget.style.hasOwnProperty(attr)) continue; if (attr === 'top' || attr === 'left' || attr === 'width' || attr === 'height') { var val = widget.style[attr]; if (val !== '0' && val !== 0 && val !== null && val !== '' && val.toString().match(/^[-+]?\d+$/)) { widget.style[attr] = val + 'px'; } } } $wid.css(widget.style); } if (widget.data && widget.data.class) { $wid = $wid || $('#' + id); $wid.addClass(widget.data.class); } var $tpl = $('#' + widget.tpl); $wid.addClass('vis-tpl-' + $tpl.data('vis-set') + '-' + $tpl.data('vis-name')); if (!this.editMode) { if (this.isWidgetFilteredOut(view, id) || this.isWidgetHidden(view, id, undefined, widget.data)) { var mWidget = document.getElementById(id); $(mWidget).hide(); if (mWidget && mWidget._customHandlers && mWidget._customHandlers.onHide) { mWidget._customHandlers.onHide(mWidget, id); } } // Processing of gestures if (typeof $$ !== 'undefined') this.addGestures(id, widget.data); } // processing of signals var s = 0; while (widget.data['signals-oid-' + s]) { this.addSignalIcon(view, id, widget.data, s); s++; } if (widget.data['lc-oid']) { this.addLastChange(view, id, widget.data); } // If edit mode, bind on click event to open this widget in edit dialog if (this.editMode) { this.bindWidgetClick(viewDiv, view, id); // @SJ cannot select menu and dialogs if it is enabled /*if ($('#wid_all_lock_f').hasClass("ui-state-active")) { $('#' + id).addClass("vis-widget-lock") }*/ } $(document).trigger('wid_added', id); if (id[0] === 'g') { for (var w = 0; w < widget.data.members.length; w++) { if (widget.data.members[w] === id) continue; this.renderWidget(viewDiv, view, widget.data.members[w], id); } } } catch (e) { var lines = (e.toString() + e.stack.toString()).split('\n'); this.conn.logError('can\'t render ' + widget.tpl + ' ' + id + ' on "' + view + '": '); for (var l = 0; l < lines.length; l++) { this.conn.logError(l + ' - ' + lines[l]); } } if (userGroups && $wid && $wid.length) { if (!this.isUserMemberOf(this.conn.getUser(), userGroups)) { $wid.addClass('vis-user-disabled'); } } }, changeView: function (viewDiv, view, hideOptions, showOptions, sync, callback) { var that = this; if (typeof view === 'object') { callback = sync; sync = showOptions; hideOptions = showOptions; view = viewDiv; } if (!view && viewDiv) view = viewDiv; if (typeof hideOptions === 'function') { callback = hideOptions; hideOptions = undefined; } if (typeof showOptions === 'function') { callback = showOptions; showOptions = undefined; } if (typeof sync === 'function') { callback = sync; sync = undefined; } var effect = (hideOptions !== undefined) && (hideOptions.effect !== undefined) && hideOptions.effect; if (!effect) { effect = (showOptions !== undefined) && (showOptions.effect !== undefined) && showOptions.effect; } if (effect && ((showOptions === undefined) || !showOptions.effect)) { showOptions = {effect: hideOptions.effect, options: {}, duration: hideOptions.duration}; } if (effect && ((hideOptions === undefined) || !hideOptions.effect)) { hideOptions = {effect: showOptions.effect, options: {}, duration: showOptions.duration}; } hideOptions = $.extend(true, {effect: undefined, options: {}, duration: 0}, hideOptions); showOptions = $.extend(true, {effect: undefined, options: {}, duration: 0}, showOptions); if (hideOptions.effect === 'show') effect = false; if (this.editMode && this.activeView !== this.activeViewDiv) { this.destroyGroupEdit(this.activeViewDiv, this.activeView); } if (!this.views[view]) { //noinspection JSUnusedAssignment view = null; for (var prop in this.views) { if (prop === '___settings') continue; view = prop; break; } } // If really changed if (this.activeView !== viewDiv) { if (effect) { this.renderView(viewDiv, view, true, function (_viewDiv, _view) { var $view = $('#visview_' + _viewDiv); // Get the view, if required, from Container if ($view.parent().attr('id') !== 'vis_container') $view.appendTo('#vis_container'); var oldView = that.activeView; that.postChangeView(_viewDiv, _view, callback); // If hide and show at the same time if (sync) { $view.show(showOptions.effect, showOptions.options, parseInt(showOptions.duration, 10)).dequeue(); } $('#visview_' + oldView).hide(hideOptions.effect, hideOptions.options, parseInt(hideOptions.duration, 10), function () { // If first hide, than show if (!sync) { $view.show(showOptions.effect, showOptions.options, parseInt(showOptions.duration, 10), function () { that.destroyUnusedViews(); }); } else { that.destroyUnusedViews(); } }); }); } else { var $oldView = $('#visview_' + that.activeViewDiv); // disable view and show some action $oldView.find('> .vis-view-disabled').show(); this.renderView(viewDiv, view, true, function (_viewDiv, _view) { var $oldView; if (that.activeViewDiv !== _viewDiv) { $oldView = $('#visview_' + that.activeViewDiv); // hide old view $oldView.hide(); $oldView.find('.vis-view-disabled').hide(); } var $view = $('#visview_' + _viewDiv); // Get the view, if required, from Container if ($view.parent().attr('id') !== 'vis_container') { $view.appendTo('#vis_container'); } // show new view $view.show(); $view.find('.vis-view-disabled').hide(); if (that.activeViewDiv !== _viewDiv) { if ($oldView.hasClass('vis-edit-group')) { that.destroyView(that.activeViewDiv, that.activeView); } else { $oldView.hide(); } } that.postChangeView(_viewDiv, _view, callback); that.destroyUnusedViews(); }); } // remember last click for de-bounce this.lastChange = Date.now(); } else { this.renderView(viewDiv, view, false, function (_viewDiv, _view) { var $view = $('#visview_' + _viewDiv); // Get the view, if required, from Container if ($view.parent().attr('id') !== 'vis_container') $view.appendTo('#vis_container'); $view.show(); that.postChangeView(_viewDiv, _view, callback); that.destroyUnusedViews(); }); } }, postChangeView: function (viewDiv, view, callback) { this.activeView = view; this.activeViewDiv = viewDiv; /*$('#visview_' + viewDiv).find('.vis-view-container').each(function () { $('#visview_' + $(this).attr('data-vis-contains')).show(); });*/ this.updateContainers(viewDiv, view); if (!this.editMode) { this.conn.sendCommand(this.instance, 'changedView', this.projectPrefix ? (this.projectPrefix + this.activeView) : this.activeView); $(window).trigger('viewChanged', viewDiv); } if (window.location.hash.slice(1) !== view) { if (history && history.pushState) { history.pushState({}, '', '#' + viewDiv); } } // Navigation-Widgets for (var i = 0; i < this.navChangeCallbacks.length; i++) { this.navChangeCallbacks[i](viewDiv, view); } // --------- Editor ----------------- if (this.editMode) { this.changeViewEdit(viewDiv, view, false, callback); } else if (typeof callback === 'function') { callback(viewDiv, view); } this.updateIframeZoom(); }, loadRemote: function (callback, callbackArg) { var that = this; if (!this.projectPrefix) { if (callback) callback.call(that, callbackArg); return; } this.conn.readFile(this.projectPrefix + 'vis-views.json', function (err, data) { if (err) { window.alert(that.projectPrefix + 'vis-views.json ' + err); if (err === 'permissionError') { that.showWaitScreen(true, '', _('Loading stopped', location.protocol + '//' + location.host, location.protocol + '//' + location.host), 0); // do nothing any more return; } } if (typeof app !== 'undefined' && app.replaceFilesInViewsWeb) { data = app.replaceFilesInViewsWeb(data); } if (data) { if (typeof data === 'string') { try { that.views = JSON.parse(data.trim()); } catch (e) { console.log('Cannot parse views file "' + that.projectPrefix + 'vis-views.json"'); window.alert('Cannot parse views file "' + that.projectPrefix + 'vis-views.json'); that.views = null; } } else { that.views = data; } var _data = that.getUsedObjectIDs(); that.subscribing.IDs = _data.IDs; that.subscribing.byViews = _data.byViews; } else { that.views = null; } if (callback) callback.call(that, callbackArg); }); }, wakeUpCallbacks: [], initWakeUp: function () { var that = this; var oldTime = Date.now(); setInterval(function () { var currentTime = Date.now(); //console.log("checkWakeUp "+ (currentTime - oldTime)); if (currentTime > (oldTime + 10000)) { oldTime = currentTime; for (var i = 0; i < that.wakeUpCallbacks.length; i++) { //console.log("calling wakeUpCallback!"); that.wakeUpCallbacks[i](); } } else { oldTime = currentTime; } }, 2500); }, onWakeUp: function (callback) { this.wakeUpCallbacks.push(callback); }, showMessage: function (message, title, icon, width, callback) { // load some theme to show message if (!this.editMode && !$('#commonTheme').length) { $('head').prepend(''); } if (typeof icon === 'number') { callback = width; width = icon; icon = null; } if (typeof title === 'function') { callback = title; title = null; } else if (typeof icon === 'function') { callback = icon; icon = null; } else if (typeof width === 'function') { callback = width; width = null; } if (!this.$dialogMessage) { this.$dialogMessage = $('#dialog-message'); this.$dialogMessage.dialog({ autoOpen: false, modal: true, open: function () { $(this).parent().css({'z-index': 1003}); var callback = $(this).data('callback'); if (callback) { $(this).find('#dialog_message_cancel').show(); } else { $(this).find('#dialog_message_cancel').hide(); } }, buttons: [ { text: _('Ok'), click: function () { var callback = $(this).data('callback'); $(this).dialog('close'); if (typeof callback === 'function') { callback(true); $(this).data('callback', null); } } }, { id: 'dialog_message_cancel', text: _('Cancel'), click: function () { var callback = $(this).data('callback'); $(this).dialog('close'); if (typeof callback === 'function') { callback(false); $(this).data('callback', null); } } } ] }); } this.$dialogMessage.dialog('option', 'title', title || _('Message')); if (width) { this.$dialogMessage.dialog('option', 'width', width); } else { this.$dialogMessage.dialog('option', 'width', 300); } $('#dialog-message-text').html(message); this.$dialogMessage.data('callback', callback ? callback : null); if (icon) { $('#dialog-message-icon') .show() .attr('class', '') .addClass('ui-icon ui-icon-' + icon); } else { $('#dialog-message-icon').hide(); } this.$dialogMessage.dialog('open'); }, showError: function (error) { this.showMessage(error, _('Error'), 'alert', 400); }, waitScreenVal: 0, showWaitScreen: function (isShow, appendText, newText, step) { var waitScreen = document.getElementById("waitScreen"); if (!waitScreen && isShow) { $('body').append('
'); waitScreen = document.getElementById("waitScreen"); this.waitScreenVal = 0; } $('.vis-progressbar').progressbar({value: this.waitScreenVal}).height(19); if (isShow) { $(waitScreen).show(); if (newText !== null && newText !== undefined) { $('#waitText').html(newText); } if (appendText !== null && appendText !== undefined) { $('#waitText').append(appendText); } if (step !== undefined) { this.waitScreenVal += step; _setTimeout(function (_val) { $('.vis-progressbar').progressbar('value', _val); }, 0, this.waitScreenVal); } } else if (waitScreen) { $(waitScreen).remove(); } }, registerOnChange: function (callback, arg) { for (var i = 0, len = this.onChangeCallbacks.length; i < len; i++) { if (this.onChangeCallbacks[i].callback === callback && this.onChangeCallbacks[i].arg === arg) { return; } } this.onChangeCallbacks[this.onChangeCallbacks.length] = {callback: callback, arg: arg}; }, unregisterOnChange: function (callback, arg) { for (var i = 0, len = this.onChangeCallbacks.length; i < len; i++) { if (this.onChangeCallbacks[i].callback === callback && (arg === undefined || this.onChangeCallbacks[i].arg === arg)) { this.onChangeCallbacks.slice(i, 1); return; } } }, isWidgetHidden: function (view, widget, val, widgetData) { widgetData = widgetData || this.views[view].widgets[widget].data; var oid = widgetData['visibility-oid']; var condition = widgetData['visibility-cond']; if (oid) { if (val === undefined) val = this.states.attr(oid + '.val'); if (val === undefined) return (condition === 'not exist'); var value = widgetData['visibility-val']; 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); } // Take care: return true if widget is hidden! 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 val === 'null'; case 'not exist': return val !== 'null'; default: console.log('Unknown visibility condition for ' + widget + ': ' + condition); return false; } } else { return (condition === 'not exist'); } }, isWidgetFilteredOut: function (view, widget) { var w = this.views[view].widgets[widget]; var v = this.viewsActiveFilter[view]; return (w && w.data && w.data.filterkey && widget && widget.data && v.length > 0 && v.indexOf(widget.data.filterkey) === -1); }, calcCommonStyle: function (recalc) { if (!this.commonStyle || recalc) { if (this.editMode) { this.commonStyle = this.config.editorTheme || 'redmond'; return this.commonStyle; } var styles = {}; if (this.views) { for (var view in this.views) { if (!this.views.hasOwnProperty(view)) continue; if (view === '___settings') continue; if (!this.views[view] || !this.views[view].settings.theme) continue; if (this.views[view].settings.theme && styles[this.views[view].settings.theme]) { styles[this.views[view].settings.theme]++; } else { styles[this.views[view].settings.theme] = 1; } } } var max = 0; this.commonStyle = ''; for (var s in styles) { if (styles[s] > max) { max = styles[s]; this.commonStyle = s; } } } return this.commonStyle; }, formatValue: function formatValue(value, decimals, _format) { if (typeof decimals !== 'number') { decimals = 2; _format = decimals; } //format = (_format === undefined) ? (that.isFloatComma) ? ".," : ",." : _format; // does not work... // using default german... var format = (_format === undefined) ? ".," : _format; if (typeof value !== "number") value = parseFloat(value); return isNaN(value) ? "" : value.toFixed(decimals || 0).replace(format[0], format[1]).replace(/\B(?=(\d{3})+(?!\d))/g, format[0]); }, formatDate: function formatDate(dateObj, isDuration, _format) { // copied from js-controller/lib/adapter.js if ((typeof isDuration === 'string' && isDuration.toLowerCase() === 'duration') || isDuration === true) { isDuration = true; } if (typeof isDuration !== 'boolean') { _format = isDuration; isDuration = false; } if (!dateObj) return ''; var type = typeof dateObj; if (type === 'string') dateObj = new Date(dateObj); if (type !== 'object') { var j = parseInt(dateObj, 10); if (j == dateObj) { // may this is interval if (j < 946681200) { isDuration = true; dateObj = new Date(dateObj); } else { // if less 2000.01.01 00:00:00 dateObj = (j < 946681200000) ? new Date(j * 1000) : new Date(j); } } else { dateObj = new Date(dateObj); } } var format = _format || this.dateFormat || 'DD.MM.YYYY'; if (isDuration) dateObj.setMilliseconds(dateObj.getMilliseconds() + dateObj.getTimezoneOffset() * 60 * 1000); var validFormatChars = 'YJГMМDTДhSчmмsс'; var s = ''; var result = ''; function put(s) { var v = ''; switch (s) { case 'YYYY': case 'JJJJ': case 'ГГГГ': case 'YY': case 'JJ': case 'ГГ': v = dateObj.getFullYear(); if (s.length === 2) v %= 100; break; case 'MM': case 'M': case 'ММ': case 'М': v = dateObj.getMonth() + 1; if ((v < 10) && (s.length === 2)) v = '0' + v; break; case 'DD': case 'TT': case 'D': case 'T': case 'ДД': case 'Д': v = dateObj.getDate(); if ((v < 10) && (s.length === 2)) v = '0' + v; break; case 'hh': case 'SS': case 'h': case 'S': case 'чч': case 'ч': v = dateObj.getHours(); if ((v < 10) && (s.length === 2)) v = '0' + v; break; case 'mm': case 'm': case 'мм': case 'м': v = dateObj.getMinutes(); if ((v < 10) && (s.length === 2)) v = '0' + v; break; case 'ss': case 's': case 'cc': case 'c': v = dateObj.getSeconds(); if ((v < 10) && (s.length === 2)) v = '0' + v; v = v.toString(); break; case 'sss': case 'ссс': v = dateObj.getMilliseconds(); if (v < 10) { v = '00' + v; } else if (v < 100) { v = '0' + v; } v = v.toString(); } return result += v; } for (var i = 0; i < format.length; i++) { if (validFormatChars.indexOf(format[i]) >= 0) s += format[i]; else { put(s); s = ''; result += format[i]; } } put(s); return result; }, extractBinding: function (format) { if (this.editMode || !format) return null; if (this.bindingsCache[format]) return JSON.parse(JSON.stringify(this.bindingsCache[format])); var result = extractBinding(format); // cache bindings if (result) { this.bindingsCache = this.bindingsCache || {}; this.bindingsCache[format] = JSON.parse(JSON.stringify(result)); } return result; }, getSpecialValues: function (name, view, wid, widget) { switch (name) { case 'username.val': return this.user; case 'login.val': return this.loginRequired; case 'instance.val': return this.instance; case 'language.val': return this.language; case 'wid.val': return wid; case 'wname.val': return widget && (widget.data.name || wid); case 'view.val': return view; default: return undefined; } }, formatBinding: function (format, view, wid, widget) { var oids = this.extractBinding(format); for (var t = 0; t < oids.length; t++) { var value; if (oids[t].visOid) { value = this.getSpecialValues(oids[t].visOid, view, wid, widget); if (value === undefined) { value = this.states.attr(oids[t].visOid); } } if (oids[t].operations) { for (var k = 0; k < oids[t].operations.length; k++) { switch (oids[t].operations[k].op) { case 'eval': var string = '';//'(function() {'; for (var a = 0; a < oids[t].operations[k].arg.length; a++) { if (!oids[t].operations[k].arg[a].name) continue; value = this.getSpecialValues(oids[t].operations[k].arg[a].visOid, view, wid, widget); if (value === undefined) { value = this.states.attr(oids[t].operations[k].arg[a].visOid); } string += 'var ' + oids[t].operations[k].arg[a].name + ' = "' + value + '";'; } var formula = oids[t].operations[k].formula; if (formula && formula.indexOf('widget.') !== -1) { string += 'var widget = ' + JSON.stringify(widget) + ';'; } string += 'return ' + oids[t].operations[k].formula + ';'; //string += '}())'; try { value = new Function(string)(); } catch (e) { console.error('Error in eval[value] : ' + format); console.error('Error in eval[script]: ' + string); console.error('Error in eval[error] : ' + e); value = 0; } break; case '*': if (oids[t].operations[k].arg !== undefined) { value = parseFloat(value) * oids[t].operations[k].arg; } break; case '/': if (oids[t].operations[k].arg !== undefined) { value = parseFloat(value) / oids[t].operations[k].arg; } break; case '+': if (oids[t].operations[k].arg !== undefined) { value = parseFloat(value) + oids[t].operations[k].arg; } break; case '-': if (oids[t].operations[k].arg !== undefined) { value = parseFloat(value) - oids[t].operations[k].arg; } break; case '%': if (oids[t].operations[k].arg !== undefined) { value = parseFloat(value) % oids[t].operations[k].arg; } break; case 'round': if (oids[t].operations[k].arg === undefined) { value = Math.round(parseFloat(value)); } else { value = parseFloat(value).toFixed(oids[t].operations[k].arg); } break; case 'pow': if (oids[t].operations[k].arg === undefined) { value = Math.pow(parseFloat(value), 2); } else { value = Math.pow(parseFloat(value), oids[t].operations[k].arg); } break; case 'sqrt': value = Math.sqrt(parseFloat(value)); break; case 'hex': value = Math.round(parseFloat(value)).toString(16); break; case 'hex2': value = Math.round(parseFloat(value)).toString(16); if (value.length < 2) value = '0' + value; break; case 'HEX': value = Math.round(parseFloat(value)).toString(16).toUpperCase(); break; case 'HEX2': value = Math.round(parseFloat(value)).toString(16).toUpperCase(); if (value.length < 2) value = '0' + value; break; case 'value': value = this.formatValue(value, parseInt(oids[t].operations[k].arg, 10)); break; case 'array': value = oids[t].operations[k].arg [~~value]; break; case 'date': value = this.formatDate(value, oids[t].operations[k].arg); break; case 'min': value = parseFloat(value); value = (value < oids[t].operations[k].arg) ? oids[t].operations[k].arg : value; break; case 'max': value = parseFloat(value); value = (value > oids[t].operations[k].arg) ? oids[t].operations[k].arg : value; break; case 'random': if (oids[t].operations[k].arg === undefined) { value = Math.random(); } else { value = Math.random() * oids[t].operations[k].arg; } break; case 'floor': value = Math.floor(parseFloat(value)); break; case 'ceil': value = Math.ceil(parseFloat(value)); break; } //switch } } //if for format = format.replace(oids[t].token, value); }//for format = format.replace(/{{/g, '{').replace(/}}/g, '}'); return format; }, findNearestResolution: function (resultRequiredOrX, height) { var w; var h; if (height !== undefined) { w = resultRequiredOrX; h = height; resultRequiredOrX = false; } else { w = $(window).width(); h = $(window).height(); } var result = null; var views = []; var difference = 10000; // First find all with best fitting width for (var view in this.views) { if (!this.views.hasOwnProperty(view)) continue; if (view === '___settings') continue; if (this.views[view].settings && this.views[view].settings.useAsDefault) { // If difference less than 20% if (Math.abs(this.views[view].settings.sizex - w) / this.views[view].settings.sizex < 0.2) { views.push(view); } } } for (var i = 0; i < views.length; i++) { if (Math.abs(this.views[views[i]].settings.sizey - h) < difference) { result = views[i]; difference = Math.abs(this.views[views[i]].settings.sizey - h); } } // try to find by ratio if (!result) { var ratio = w / h; difference = 10000; for (var view_ in this.views) { if (!this.views.hasOwnProperty(view_)) continue; if (view_ === '___settings') continue; if (this.views[view_].settings && this.views[view_].settings.useAsDefault) { // If difference less than 20% if (this.views[view_].settings.sizey && Math.abs(ratio - (this.views[view_].settings.sizex / this.views[view_].settings.sizey)) < difference) { result = view_; difference = Math.abs(ratio - (this.views[view_].settings.sizex / this.views[view_].settings.sizey)); } } } } if (!result && resultRequiredOrX) { for (var view__ in this.views) { if (!this.views.hasOwnProperty(view__)) continue; if (view__ === '___settings') continue; return view__; } } return result; }, orientationChange: function () { if (this.resolutionTimer) return; var that = this; this.resolutionTimer = setTimeout(function () { that.resolutionTimer = null; var view = that.findNearestResolution(); if (view && view !== that.activeView) { that.changeView(view, view); } }, 200); }, detectBounce: function (el, isUp) { if (!this.isTouch) return false; // Protect against two events var now = Date.now(); //console.log('gclick: ' + this.lastChange + ' ' + (now - this.lastChange)); if (this.lastChange && now - this.lastChange < this.debounceInterval) { //console.log('gclick: filtered'); return true; } var $el = $(el); var tag = $(el).prop('tagName').toLowerCase(); while (tag !== 'div') { $el = $el.parent(); tag = $el.prop('tagName').toLowerCase(); } var lastClick = $el.data(isUp ? 'lcu' : 'lc'); //console.log('click: ' + lastClick + ' ' + (now - lastClick)); if (lastClick && now - lastClick < this.debounceInterval) { //console.log('click: filtered'); return true; } $el.data(isUp ? 'lcu' : 'lc', now); return false; }, createDemoStates: function () { // Create demo variables this.states.attr({'demoTemperature.val': 25.4}); this.states.attr({'demoHumidity.val': 55}); }, getHistory: function (id, options, callback) { // Possible options: // - **instance - (mandatory) sql.x or history.y // - **start** - (optional) time in ms - *Date.now()*' // - **end** - (optional) time in ms - *Date.now()*', by default is (now + 5000 seconds) // - **step** - (optional) used in aggregate (m4, max, min, average, total) step in ms of intervals // - **count** - number of values if aggregate is 'onchange' or number of intervals if other aggregate method. Count will be ignored if step is set. // - **from** - if *from* field should be included in answer // - **ack** - if *ack* field should be included in answer // - **q** - if *q* field should be included in answer // - **addId** - if *id* field should be included in answer // - **limit** - do not return more entries than limit // - **ignoreNull** - if null values should be include (false), replaced by last not null value (true) or replaced with 0 (0) // - **aggregate** - aggregate method: // - *minmax* - used special algorithm. Splice the whole time range in small intervals and find for every interval max, min, start and end values. // - *max* - Splice the whole time range in small intervals and find for every interval max value and use it for this interval (nulls will be ignored). // - *min* - Same as max, but take minimal value. // - *average* - Same as max, but take average value. // - *total* - Same as max, but calculate total value. // - *count* - Same as max, but calculate number of values (nulls will be calculated). // - *none* - no aggregation this.conn.getHistory(id, options, callback); }, destroyView: function (viewDiv, view) { var $view = $('#visview_' + viewDiv); console.debug('Destroy ' + view); // Get all widgets and try to destroy them for (var wid in this.views[view].widgets) { if (!this.views[view].widgets.hasOwnProperty(wid)) continue; this.destroyWidget(viewDiv, view, wid); } $view.remove(); this.unsubscribeStates(view); }, findAndDestroyViews: function () { if (this.destroyTimeout) { clearTimeout(this.destroyTimeout); this.destroyTimeout = null; } var containers = []; var $createdViews = $('.vis-view'); for (var view in this.views) { if (!this.views.hasOwnProperty(view) || view === '___settings') continue; if (this.views[view].settings.alwaysRender || view === this.activeView) { if (containers.indexOf(view) === -1) containers.push(view); var $containers = $('#visview_' + view).find('.vis-view-container'); $containers.each(function () { var cview = $(this).attr('data-vis-contains'); if (containers.indexOf(cview) === -1) containers.push(cview); }); // check dialogs too var $dialogs = $('.vis-widget-dialog'); $dialogs.each(function () { if ($(this).is(':visible')) { var $containers = $(this).find('.vis-view-container'); $containers.each(function () { var cview = $(this).attr('data-vis-contains'); if (containers.indexOf(cview) === -1) containers.push(cview); }); } }); } } var that = this; $createdViews.each(function () { var $this = $(this); var view = $this.data('view'); var viewDiv = $this.attr('id').substring('visview_'.length); // If this view is used as container if (containers.indexOf(viewDiv) !== -1) return; if ($this.hasClass('vis-edit-group')) return; if ($this.data('persistent')) return; that.destroyView(viewDiv, view); }); }, destroyUnusedViews: function () { if (this.destroyTimeout) clearTimeout(this.destroyTimeout); var timeout = 30000; if (this.views.___settings && this.views.___settings.destroyViewsAfter !== undefined) { timeout = this.views.___settings.destroyViewsAfter * 1000; } if (timeout) { this.destroyTimeout = _setTimeout(function (that) { that.destroyTimeout = null; that.findAndDestroyViews(); }, timeout, this); } }, generateInstance: function () { if (typeof storage !== 'undefined') { this.instance = (Math.random() * 4294967296).toString(16); this.instance = '0000000' + this.instance; this.instance = this.instance.substring(this.instance.length - 8); $('#vis_instance').val(this.instance); storage.set(this.storageKeyInstance, this.instance); } }, subscribeStates: function (view, callback) { if (!view || this.editMode) { if (callback) callback(); return; } // view yet active if (this.subscribing.activeViews.indexOf(view) !== -1) { if (callback) callback(); return; } this.subscribing.activeViews.push(view); this.subscribing.byViews[view] = this.subscribing.byViews[view] || []; // subscribe var oids = []; for (var i = 0; i < this.subscribing.byViews[view].length; i++) { if (this.subscribing.active.indexOf(this.subscribing.byViews[view][i]) === -1) { this.subscribing.active.push(this.subscribing.byViews[view][i]); oids.push(this.subscribing.byViews[view][i]); } } if (oids.length) { var that = this; console.debug('[' + Date.now() + '] Request ' + oids.length + ' states.'); this.conn.getStates(oids, function (error, data) { if (error) that.showError(error); that.updateStates(data); that.conn.subscribe(oids); if (callback) callback(); }); } else { if (callback) callback(); } }, unsubscribeStates: function (view) { if (!view || this.editMode) return; // view yet active var pos = this.subscribing.activeViews.indexOf(view); if (pos === -1) return; this.subscribing.activeViews.splice(pos, 1); // unsubscribe var oids = []; // check every OID for (var i = 0; i < this.subscribing.byViews[view].length; i++) { var id = this.subscribing.byViews[view][i]; pos = this.subscribing.active.indexOf(id); if (pos !== -1) { var isUsed = false; // Is OID is used something else for (var v = 0; v < this.subscribing.activeViews.length; v++) { if (this.subscribing.byViews[this.subscribing.activeViews[v]].indexOf(id) !== -1) { isUsed = true; break; } } if (!isUsed) { oids.push(id); this.subscribing.active.splice(pos, 1); } } } if (oids.length) this.conn.unsubscribe(oids); }, updateState: function (id, state) { if (this.editMode) { this.states[id + '.val'] = state.val; this.states[id + '.ts'] = state.ts; this.states[id + '.ack'] = state.ack; this.states[id + '.lc'] = state.lc; if (state.q !== undefined) this.states[id + '.q'] = state.q; } else { var o = {}; // Check new model o[id + '.val'] = state.val; o[id + '.ts'] = state.ts; o[id + '.ack'] = state.ack; o[id + '.lc'] = state.lc; if (state.q !== undefined) o[id + '.q'] = state.q; try { this.states.attr(o); } catch (e) { this.conn.logError('Error: can\'t create states object for ' + id + '(' + e + '): ' + JSON.stringify(e.stack)); } } if (!this.editMode && this.visibility[id]) { for (var k = 0; k < this.visibility[id].length; k++) { var mmWidget = document.getElementById(this.visibility[id][k].widget); if (!mmWidget) continue; if (this.isWidgetHidden(this.visibility[id][k].view, this.visibility[id][k].widget, state.val) || this.isWidgetFilteredOut(this.visibility[id][k].view, this.visibility[id][k].widget)) { $(mmWidget).hide(); if (mmWidget && mmWidget._customHandlers && mmWidget._customHandlers.onHide) { mmWidget._customHandlers.onHide(mmWidget, id); } } else { $(mmWidget).show(); if (mmWidget && mmWidget._customHandlers && mmWidget._customHandlers.onShow) { mmWidget._customHandlers.onShow(mmWidget, id); } } } } // process signals if (!this.editMode && this.signals[id]) { for (var s = 0; s < this.signals[id].length; s++) { var signal = this.signals[id][s]; var mWidget = document.getElementById(signal.widget); if (!mWidget) continue; if (this.isSignalVisible(signal.view, signal.widget, signal.index, state.val)) { $(mWidget).find('.vis-signal[data-index="' + signal.index + '"]').show(); } else { $(mWidget).find('.vis-signal[data-index="' + signal.index + '"]').hide(); } } } // Process last update if (!this.editMode && this.lastChanges[id]) { for (var l = 0; l < this.lastChanges[id].length; l++) { var update = this.lastChanges[id][l]; var uWidget = document.getElementById(update.widget); if (uWidget) { var $lc = $(uWidget).find('.vis-last-change'); $lc.html(this.binds.basic.formatDate($lc.data('type') === 'last-change' ? state.lc : state.ts, $lc.data('format'), $lc.data('interval') === 'true')); } } } // Bindings on every element if (!this.editMode && this.bindings[id]) { for (var i = 0; i < this.bindings[id].length; i++) { var widget = this.views[this.bindings[id][i].view].widgets[this.bindings[id][i].widget]; var value = this.formatBinding(this.bindings[id][i].format, this.bindings[id][i].view, this.bindings[id][i].widget, widget); widget[this.bindings[id][i].type][this.bindings[id][i].attr] = value; if (this.widgets[this.bindings[id][i].widget] && this.bindings[id][i].type === 'data') { this.widgets[this.bindings[id][i].widget][this.bindings[id][i].type + '.' + this.bindings[id][i].attr] = value; } this.reRenderWidget(this.bindings[id][i].view, this.bindings[id][i].view, this.bindings[id][i].widget); } } // Inform other widgets, that do not support canJS for (var j = 0, len = this.onChangeCallbacks.length; j < len; j++) { this.onChangeCallbacks[j].callback(this.onChangeCallbacks[j].arg, id, state.val, state.ack); } if (this.editMode && $.fn.selectId) $.fn.selectId('stateAll', id, state); }, updateStates: function (data) { if (data) { for (var id in data) { if (!data.hasOwnProperty(id)) continue; var obj = data[id]; if (!obj) continue; try { if (this.editMode) { this.states[id + '.val'] = obj.val; this.states[id + '.ts'] = obj.ts; this.states[id + '.ack'] = obj.ack; this.states[id + '.lc'] = obj.lc; if (obj.q !== undefined) this.states[id + '.q'] = obj.q; } else { var oo = {}; oo[id + '.val'] = obj.val; oo[id + '.ts'] = obj.ts; oo[id + '.ack'] = obj.ack; oo[id + '.lc'] = obj.lc; if (obj.q !== undefined) oo[id + '.q'] = obj.q; this.states.attr(oo); } } catch (e) { this.conn.logError('Error: can\'t create states object for ' + id + '(' + e + ')'); } if (!this.editMode && this.bindings[id]) { for (var i = 0; i < this.bindings[id].length; i++) { var widget = this.views[this.bindings[id][i].view].widgets[this.bindings[id][i].widget]; widget[this.bindings[id][i].type][this.bindings[id][i].attr] = this.formatBinding(this.bindings[id][i].format, this.bindings[id][i].view, this.bindings[id][i].widget, widget); } } } } }, updateIframeZoom: function (zoom) { if (zoom === undefined) zoom = document.body.style.zoom; if (zoom) { $('iframe').each(function () { if (this.contentWindow.document.body) { this.contentWindow.document.body.style.zoom = zoom; } }).unbind('onload').load(function () { if (this.contentWindow.document.body) { this.contentWindow.document.body.style.zoom = zoom; } }); } } }; // WebApp Cache Management if ('applicationCache' in window) { window.addEventListener('load', function (/* e */) { window.applicationCache.addEventListener('updateready', function (e) { if (window.applicationCache.status === window.applicationCache.UPDATEREADY) { vis.showWaitScreen(true, null, _('Update found, loading new Files...'), 100); $('#waitText').attr('id', 'waitTextDisabled'); $('.vis-progressbar').hide(); try { window.applicationCache.swapCache(); } catch (_e) { servConn.logError('Cannot execute window.applicationCache.swapCache - ' + _e); } setTimeout(function () { window.location.reload(); }, 1000); } }, false); }, false); } // Parse Querystring window.onpopstate = function () { var match, pl = /\+/g, search = /([^&=]+)=?([^&]*)/g, decode = function (s) { return decodeURIComponent(s.replace(pl, ' ')); }, query = window.location.search.substring(1); vis.urlParams = {}; while ((match = search.exec(query))) { vis.urlParams[decode(match[1])] = decode(match[2]); } vis.editMode = ( window.location.href.indexOf('edit.html') !== -1 || window.location.href.indexOf('edit.full.html') !== -1 || window.location.href.indexOf('edit.src.html') !== -1 || vis.urlParams.edit === ''); }; window.onpopstate(); if (!vis.editMode) { // Protection after view change $(window).on('click touchstart mousedown', function (e) { if (Date.now() - vis.lastChange < vis.debounceInterval) { e.stopPropagation(); e.preventDefault(); return false; } }); /*$(window).on('touchend mouseup', function () { vis.lastChange = null; var $log = $('#w00039'); var $log1 = $('#w00445'); $log.append('
gclick touchend: ' + vis.lastChange); $log1.append('
gclick touchend: ' + vis.lastChange); });*/ } function main($, onReady) { // parse arguments var args = document.location.href.split('?')[1]; vis.args = {}; if (args) { vis.projectPrefix = 'main/'; var pos = args.indexOf('#'); if (pos !== -1) { args = args.substring(0, pos); } args = args.split('&'); for (var a = 0; a < args.length; a++) { var parts = args[a].split('='); vis.args[parts[0]] = parts[1]; if (!parts[1]) vis.projectPrefix = parts[0] + '/'; } if (vis.args.project) vis.projectPrefix = vis.args.project + '/'; } // If cordova project => take cordova project name if (typeof app !== 'undefined') vis.projectPrefix = app.settings.project ? app.settings.project + '/' : null; // On some platforms, the can.js is not immediately ready vis.states = new can.Map({ 'nothing_selected.val': null }); if (vis.editMode) { vis.states.__attrs = vis.states.attr; vis.states.attr = function (attr, val) { var type = typeof attr; if (type !== 'string' && type !== 'number') { for (var o in attr) { // allow only dev1, dev2, ... to be bound if (o && attr.hasOwnProperty(o) && o.match(/^dev\d+(.val|.ack|.tc|.lc)+/)) { return this.__attrs(attr, val); } } } else if (arguments.length === 1 && attr) { if (attr.match(/^dev\d+(.val|.ack|.tc|.lc)+/)) { can.__reading(this, attr); return this._get(attr); } else { return vis.states[attr]; } } else { console.log('This is ERROR!'); this._set(attr, val); return this; } }; // binding vis.states.___bind = vis.states.bind; vis.states.bind = function (id, callback) { // allow only dev1, dev2, ... to be bound if (id && id.match(/^dev\d+(.val|.ack|.tc|.lc)+/)) { return vis.states.___bind(id, callback); } //console.log('ERROR: binding in edit mode is not allowed on ' + id); }; } // für iOS Safari - wirklich notwendig? $('body').on('touchmove', function (e) { if (!$(e.target).closest('body').length) e.preventDefault(); }); vis.preloadImages(['img/disconnect.png']); /*$('#server-disconnect').dialog({ modal: true, closeOnEscape: false, autoOpen: false, dialogClass: 'noTitle', width: 400, height: 90 });*/ $('.vis-version').html(vis.version); vis.showWaitScreen(true, null, _('Connecting to Server...') + '
', 0); function compareVersion(instVersion, availVersion) { var instVersionArr = instVersion.replace(/beta/, '.').split('.'); var availVersionArr = availVersion.replace(/beta/, '.').split('.'); var updateAvailable = false; for (var k = 0; k < 3; k++) { instVersionArr[k] = parseInt(instVersionArr[k], 10); if (isNaN(instVersionArr[k])) instVersionArr[k] = -1; availVersionArr[k] = parseInt(availVersionArr[k], 10); if (isNaN(availVersionArr[k])) availVersionArr[k] = -1; } if (availVersionArr[0] > instVersionArr[0]) { updateAvailable = true; } else if (availVersionArr[0] === instVersionArr[0]) { if (availVersionArr[1] > instVersionArr[1]) { updateAvailable = true; } else if (availVersionArr[1] === instVersionArr[1]) { if (availVersionArr[2] > instVersionArr[2]) { updateAvailable = true; } } } return updateAvailable; } vis.conn = servConn; // old !!! // First of all load project/vis-user.css //$('#project_css').attr('href', '/' + vis.conn.namespace + '/' + vis.projectPrefix + 'vis-user.css'); if (typeof app === 'undefined') { $.ajax({ url: 'css/vis-common-user.css', type: 'GET', dataType: 'html', cache: vis.useCache, success: function (data) { if (data && typeof app !== 'undefined' && app.replaceFilesInViewsWeb) { data = app.replaceFilesInViewsWeb(data); } if (data || vis.editMode) $('head').append(''); $(document).trigger('vis-common-user'); }, error: function (jqXHR, textStatus, errorThrown) { vis.conn.logError('Cannot load vis-common-user.css - ' + errorThrown); $('head').append(''); $(document).trigger('vis-common-user'); } }); $.ajax({ url: '/' + vis.conn.namespace + '/' + vis.projectPrefix + 'vis-user.css', type: 'GET', dataType: 'html', cache: vis.useCache, success: function (data) { if (data && typeof app !== 'undefined' && app.replaceFilesInViewsWeb) { data = app.replaceFilesInViewsWeb(data); } if (data || vis.editMode) { $('head').append(''); } $(document).trigger('vis-user'); }, error: function (jqXHR, textStatus, errorThrown) { vis.conn.logError('Cannot load /' + vis.conn.namespace + '/' + vis.projectPrefix + 'vis-user.css - ' + errorThrown); $('head').append(''); $(document).trigger('vis-user'); } }); } function createIds(IDs, index, callback) { if (typeof index === 'function') { callback = index; index = 0; } index = index || 0; var j; var now = Date.now(); var obj = {}; for (j = index; j < vis.subscribing.IDs.length && j < index + 100; j++) { var _id = vis.subscribing.IDs[j]; if (vis.states[_id + '.val'] === undefined) { if (!_id || !_id.match(/^dev\d+$/)) { console.log('Create inner vis object ' + _id); } if (vis.editMode) { vis.states[_id + '.val'] = 'null'; vis.states[_id + '.ts'] = now; vis.states[_id + '.ack'] = false; vis.states[_id + '.lc'] = now; } else { obj[_id + '.val'] = 'null'; obj[_id + '.ts'] = now; obj[_id + '.ack'] = false; obj[_id + '.lc'] = now; } if (!vis.editMode && vis.bindings[_id]) { for (var k = 0; k < vis.bindings[_id].length; k++) { var _widget = vis.views[vis.bindings[_id][k].view].widgets[vis.bindings[_id][k].widget]; _widget[vis.bindings[_id][k].type][vis.bindings[_id][k].attr] = vis.formatBinding(vis.bindings[_id][k].format, vis.bindings[_id][k].view, vis.bindings[_id][k].widget, _widget); } } } } try { vis.states.attr(obj); } catch (e) { vis.conn.logError('Error: can\'t create states objects (' + e + ')'); } if (j < vis.subscribing.IDs.length) { setTimeout(function () { createIds(IDs, j, callback); }, 0) } else { callback(); } } function afterInit(error, onReady) { if (error) { console.log('Possibly not authenticated, wait for request from server'); // Possibly not authenticated, wait for request from server } else { // Get user groups info vis.conn.getGroups(function (err, userGroups) { vis.userGroups = userGroups || {}; // Get Server language vis.conn.getConfig(function (err, config) { systemLang = vis.args.lang || config.language || systemLang; vis.language = systemLang; vis.dateFormat = config.dateFormat; vis.isFloatComma = config.isFloatComma; // set moment language if (typeof moment !== 'undefined') { //moment.lang(vis.language); moment.locale(vis.language); } translateAll(); if (vis.isFirstTime) { // Init edit dialog if (vis.editMode && vis.editInit) vis.editInit(); vis.isFirstTime = false; vis.init(onReady); } }); }); // If metaIndex required, load it if (vis.editMode) { /* socket.io */ if (vis.isFirstTime) vis.showWaitScreen(true, _('Loading data objects...'), null, 20); // Read all data objects from server vis.conn.getObjects(function (err, data) { vis.objects = data; // Detect if objects are loaded for (var ob in data) { if (data.hasOwnProperty(ob)) { vis.objectSelector = true; break; } } if (vis.editMode && vis.objectSelector) { vis.inspectWidgets(vis.activeViewDiv, vis.activeView, true); } }); } //console.log((new Date()) + " socket.io reconnect"); if (vis.isFirstTime) { setTimeout(function () { if (vis.isFirstTime) { // Init edit dialog if (vis.editMode && vis.editInit) vis.editInit(); vis.isFirstTime = false; vis.init(onReady); } }, 1000); } } } vis.conn.init(null, { mayReconnect: typeof app !== 'undefined' ? app.mayReconnect : null, onAuthError: typeof app !== 'undefined' ? app.onAuthError : null, onConnChange: function (isConnected) { //console.log("onConnChange isConnected="+isConnected); if (isConnected) { //$('#server-disconnect').dialog('close'); if (vis.isFirstTime) { vis.conn.getVersion(function (version) { if (version) { if (compareVersion(version, vis.requiredServerVersion)) { vis.showMessage(_('Warning: requires Server version %s - found Server version %s - please update Server.', vis.requiredServerVersion, version)); } } //else { // Possible not authenticated, wait for request from server //} }); vis.showWaitScreen(true, _('Loading data values...') + '
', null, 20); } vis.conn.getLoggedUser(function (authReq, user) { vis.user = user; vis.loginRequired = authReq; vis.states.attr({ 'username.val' : vis.user, 'login.val' : vis.loginRequired, 'username' : vis.user, 'login' : vis.loginRequired }); // first of all try to load views vis.loadRemote(function () { vis.subscribing.IDs = vis.subscribing.IDs || []; vis.subscribing.byViews = vis.subscribing.byViews || {}; vis.conn.subscribe([vis.conn.namespace + '.control.instance', vis.conn.namespace + '.control.data', vis.conn.namespace + '.control.command']); // first of all add custom scripts if (!vis.editMode && vis.views && vis.views.___settings) { if (vis.views.___settings.scripts) { var script = document.createElement('script'); script.innerHTML = vis.views.___settings.scripts; document.head.appendChild(script); } } // Read all states from server console.debug('Request ' + (vis.editMode ? 'all' : vis.subscribing.active.length) + ' states.'); vis.conn.getStates(vis.editMode ? null : vis.subscribing.active, function (error, data) { if (error) vis.showError(error); vis.updateStates(data); if (vis.subscribing.active.length) { vis.conn.subscribe(vis.subscribing.active); } // Create non-existing IDs if (vis.subscribing.IDs) { createIds(vis.subscribing.IDs, function () { afterInit(error, onReady); }); } else { afterInit(error, onReady); } }); }); }); } else { //console.log((new Date()) + " socket.io disconnect"); //$('#server-disconnect').dialog('open'); } }, onRefresh: function () { window.location.reload(); }, onUpdate: function (id, state) { _setTimeout(function (_id, _state) { vis.updateState(_id, _state); }, 0, id, state); }, onAuth: function (message, salt) { if (vis.authRunning) { return; } vis.authRunning = true; var users; if (visConfig.auth.users && visConfig.auth.users.length) { users = ''; } else { users = ''; } var text = ''; // Add the mask to body $('body') .append(text) .append('
'); var loginBox = $('#login-box'); //Fade in the Popup $(loginBox).fadeIn(300); //Set the center alignment padding + border see css style var popMargTop = ($(loginBox).height() + 24) / 2; var popMargLeft = ($(loginBox).width() + 24) / 2; $(loginBox).css({ 'margin-top': -popMargTop, 'margin-left': -popMargLeft }); $('#login-mask').fadeIn(300); // When clicking on the button close or the mask layer the popup closed $('#login-password').keypress(function (e) { if (e.which === 13) { $('.login-button').trigger('click'); } }); $('.login-button').bind('click', function () { var user = $('#login-username').val(); var pass = $('#login-password').val(); $('#login_mask , .login-popup').fadeOut(300, function () { $('#login-mask').remove(); $('#login-box').remove(); }); setTimeout(function () { vis.authRunning = false; console.log('user ' + user + ', ' + pass + ' ' + salt); vis.conn.authenticate(user, pass, salt); }, 500); return true; }); }, onCommand: function (instance, command, data) { var parts; if (!instance || (instance !== vis.instance && instance !== 'FFFFFFFF' && instance.indexOf('*') === -1)) return false; if (command) { if (vis.editMode && command !== 'tts' && command !== 'playSound') return; // external Commands switch (command) { case 'alert': parts = data.split(';'); vis.showMessage(parts[0], parts[1], parts[2]); break; case 'changedView': // Do nothing return false; case 'changeView': parts = data.split('/'); if (parts[1]) { // detect actual project var actual = vis.projectPrefix ? vis.projectPrefix.substring(0, vis.projectPrefix.length - 1) : 'main'; if (parts[0] !== actual) { document.location.href = 'index.html?' + actual + '#' + parts[1]; return; } } var view = parts[1] || parts[0]; vis.changeView(view, view); break; case 'refresh': case 'reload': setTimeout(function () { window.location.reload(); }, 1); break; case 'dialog': case 'dialogOpen': //noinspection JSJQueryEfficiency $('#' + data + '_dialog').dialog('open'); break; case 'dialogClose': //noinspection JSJQueryEfficiency $('#' + data + '_dialog').dialog('close'); break; case 'popup': window.open(data); break; case 'playSound': setTimeout(function () { var href; if (data && data.match(/^http(s)?:\/\//)) { href = data; } else { href = location.protocol + '//' + location.hostname + ':' + location.port + data; } // force read from server href += '?' + Date.now(); if (typeof Audio !== 'undefined') { var snd = new Audio(href); // buffers automatically when created snd.play(); } else { //noinspection JSJQueryEfficiency var $sound = $('#external_sound'); if (!$sound.length) { $('body').append(''); $sound = $('#external_sound'); } $sound.attr('src', href); document.getElementById('external_sound').play(); } }, 1); break; case 'tts': if (typeof app !== 'undefined') { app.tts(data); } break; default: vis.conn.logError('unknown external command ' + command); } } return true; }, onObjectChange: function(id, obj) { if (!vis.objects || !vis.editMode) return; if (obj) { vis.objects[id] = obj; } else { if (vis.objects[id]) delete vis.objects[id]; } if ($.fn.selectId) $.fn.selectId('objectAll', id, obj); }, onError: function (err) { if (err.arg === 'vis.0.control.instance' || err.arg === 'vis.0.control.data' || err.arg === 'vis.0.control.command') { console.warn('Cannot set ' + err.arg + ', because of insufficient permissions'); } else { vis.showMessage(_('Cannot execute %s for %s, because of insufficient permissions', err.command, err.arg), _('Insufficient permissions'), 'alert', 600); } } }, vis.editMode, vis.editMode); if (!vis.editMode) { // Listen for resize changes window.addEventListener('orientationchange', function () { vis.orientationChange(); }, false); window.addEventListener('resize', function () { vis.orientationChange(); }, false); } //vis.preloadImages(["../../lib/css/themes/jquery-ui/redmond/images/modalClose.png"]); vis.initWakeUp(); } // Start of initialisation: main () if (typeof app === 'undefined') { $(document).ready(function () { main(jQuery); }); } // IE8 indexOf compatibility if (!Array.prototype.indexOf) { Array.prototype.indexOf = function (obj, start) { for (var i = (start || 0), j = this.length; i < j; i++) { if (this[i] === obj) { return i; } } return -1; }; } function _setTimeout(func, timeout, arg1, arg2, arg3, arg4, arg5, arg6) { return setTimeout(function () { func(arg1, arg2, arg3, arg4, arg5, arg6); }, timeout); } function _setInterval(func, timeout, arg1, arg2, arg3, arg4, arg5, arg6) { return setInterval(function () { func(arg1, arg2, arg3, arg4, arg5, arg6); }, timeout); } /*if (window.location.search === '?edit') { window.alert(_('please use /vis/edit.html instead of /vis/?edit')); location.href = './edit.html' + window.location.hash; }*/ // TODO find out if iPad 1 has map or not. // Production steps of ECMA-262, Edition 5, 15.4.4.19 // Reference: http://es5.github.io/#x15.4.4.19 if (!Array.prototype.map) { Array.prototype.map = function(callback, thisArg) { var T, A, k; if (this === null || this === undefined || this === 0) { throw new TypeError('this is null or not defined'); } // 1. Let O be the result of calling ToObject passing the |this| // value as the argument. var O = Object(this); // 2. Let lenValue be the result of calling the Get internal // method of O with the argument "length". // 3. Let len be ToUint32(lenValue). var len = O.length >>> 0; // 4. If IsCallable(callback) is false, throw a TypeError exception. // See: http://es5.github.com/#x9.11 if (typeof callback !== 'function') { throw new TypeError(callback + ' is not a function'); } // 5. If thisArg was supplied, let T be thisArg; else let T be undefined. if (arguments.length > 1) { T = thisArg; } // 6. Let A be a new array created as if by the expression new Array(len) // where Array is the standard built-in constructor with that name and // len is the value of len. A = new Array(len); // 7. Let k be 0 k = 0; // 8. Repeat, while k < len while (k < len) { var kValue, mappedValue; // a. Let Pk be ToString(k). // This is implicit for LHS operands of the in operator // b. Let kPresent be the result of calling the HasProperty internal // method of O with argument Pk. // This step can be combined with c // c. If kPresent is true, then if (k in O) { // i. Let kValue be the result of calling the Get internal // method of O with argument Pk. kValue = O[k]; // ii. Let mappedValue be the result of calling the Call internal // method of callback with T as the this value and argument // list containing kValue, k, and O. mappedValue = callback.call(T, kValue, k, O); // iii. Call the DefineOwnProperty internal method of A with arguments // Pk, Property Descriptor // { Value: mappedValue, // Writable: true, // Enumerable: true, // Configurable: true }, // and false. // In browsers that support Object.defineProperty, use the following: // Object.defineProperty(A, k, { // value: mappedValue, // writable: true, // enumerable: true, // configurable: true // }); // For best browser support, use the following: A[k] = mappedValue; } // d. Increase k by 1. k++; } // 9. return A return A; }; }