/** * yunkong2.mobile * https://git.spacen.net/yunkong2/yunkong2.mobile * * Copyright (c) 2015-2016 bluefox https://git.spacen.net/GermanBluefox * MIT * */ /* jshint browser:--undef */ /* jshint browser:true */ /* global document */ /* global console */ /* global session */ /* global window */ /* global location */ /* global setTimeout */ /* global clearTimeout */ /* global io */ /* global systemLang:true */ /* global systemLang */ /* global _ */ /* global can */ /* global storage */ /* global servConn */ /* global systemDictionary:true */ /* global systemDictionary */ /* global $ */ /* global translateAll */ /* global jQuery */ /* global $ */ /* global document */ /* jshint -W097 */// jshint strict:false 'use strict'; systemDictionary = { 'Views': {'en': 'Views', 'cn': '视图'}, 'edit description': { 'en': 'In edit mode you can add, delete or modify the extensions. Additionally the elements can be sorted or styled.' }, "Loading data values...": { "en": "Loading data values...", "cn": "装载数据..." }, "Rooms": {"en": "Rooms", "cn": "场景"}, "Favorites": {"en": "Favorites", "cn": "收藏"}, "Functions": {"en": "Functions", "cn": "功能"}, "off": {"en": "off", "cn": "关"}, "on": {"en": "on", "cn": "开"}, "CLOSED": {"en": "closed", "cn": "已关闭"}, "closed": {"en": "closed", "cn": "已关闭"}, "opened": {"en": "opened", "cn": "已打开"}, /* "OPEN": {"en": "open", "de": "开"},*/ "open": {"en": "open", "cn": "打开"}, "TILTED": {"en": "tilted", "cn": "tilted"}, "tilted": {"en": "tilted", "cn": "tilted"}, "false": {"en": "false", "cn": "false"}, "true": {"en": "true", "cn": "true"}, "No connection to server": { "en": "No connection to server", "cn": "和服务器断开连接" }, "Edit mode": {"en": "Edit mode", "cn": "编辑模式"}, "Info": {"en": "Info", "cn": "信息"}, "License: ": {"en": "License: ", "cn": "授权: "}, "Refresh": {"en": "Refresh", "cn": "刷新"}, "Used icons": { "en": '新空间' }, "Edit name": { "en": "Edit name", "cn": "修改名称"}, "Last synchronized: ": { "en": "Last synchronized: ", "cn": "上次同步: " }, "Edit-Mode": {"en": "Edit-Mode", "cn": "编辑模式"}, "Finish Edit-Mode": {"en": "Finish Edit-Mode", "cn": "完成"}, "Documentation": {"en": "Documentation", "cn": "文档"}, "Reset layout": {"en": "Reset layout", "cn": "复位"}, "%s seconds ago": {"en": "%s days ago", "de": "vor %s Sekunden", "ru": "%s секунд(у) назад"}, "ca. %s minutes ago": {"en": "ca. %s minutes ago", "de": "vor ca. %s Minuten", "ru": "примеро %s минут(у) назад"}, "%s minutes ago": {"en": "%s minutes ago", "de": "vor %s Minuten", "ru": "%s минут(у) назад"}, "%s h. and %s m. ago": {"en": "%s h. and %s m. ago", "de": "vor %s Stunden und %s Minuten", "ru": "%s час(ов) и %s минут(у) назад"}, "%s hours ago": {"en": "%s hours ago", "de": "vor %s Stunden", "ru": "%s час(ов) назад"}, "%s days and %s h. ago": {"en": "%s days and %s h. ago", "de": "vor %s Tagen und %s Stunden", "ru": "%s дней и %s час(ов) назад"}, "%s days ago": {"en": "%s days ago", "de": "vor %s days", "ru": "%s дней назад"}, "Theme": {"en": "Theme", "cn": "样式"}, "none": {"en": "no type", "de": "kein Typ", "ru": "нет типа"}, "Light": {"en": "light", "de": "Licht", "ru": "свет"}, "Socket": {"en": "socket", "de": "Steckdose", "ru": "розетка"}, "Blinds": {"en": "blinds", "de": "Rolladen", "ru": "жалюзи"}, "Red": {"en": "red", "de": "rot", "ru": "красный"}, "Green": {"en": "green", "de": "grün", "ru": "зелёный"}, "Blue": {"en": "blue", "de": "blau", "ru": "синий"}, "White": {"en": "white", "de": "weiß", "ru": "белый"}, "Wohnzimmer": {"en": "Living room", "de": "Wohnzimmer", "ru": "Гостинная"}, "Küche": {"en": "Kitchen", "de": "Küche", "ru": "Кухня"}, "Schlafzimmer": {"en": "Sleeping room", "de": "Schlafzimmer", "ru": "Спальня"}, "Kinderzimmer": {"en": "Kids room", "de": "Kinderzimmer", "ru": "Детская"}, "Kabinet": {"en": "Cabinet", "de": "Kabinet", "ru": "Кабинет"}, "Bad": {"en": "Bath", "de": "Bad", "ru": "Ванная"}, "Balkon": {"en": "Balcony", "de": "Balkon", "ru": "Балкон"}, "Gäste WC": {"en": "Guest WC", "de": "Gäste WC", "ru": "Гостевой туалет"}, "Flur": {"en": "Hall", "de": "Flur", "ru": "Коридор"}, "Admin Favorites": {"en": "Admin Favorites", "de": "Admin Favorites", "ru": "Admin Favorites"}, "Licht": {"en": "Light", "de": "Licht", "ru": "Свет"}, "Heizung": {"en": "Heting", "de": "Heizung", "ru": "Отопление"}, "Rolladen": {"en": "Shutter", "de": "Rolladen", "ru": "Жалюзи"}, "Wetter": {"en": "Weather", "de": "Wetter", "ru": "Погода"}, "Umwelt": {"en": "Environment", "de": "Umwelt", "ru": "Окружающая среда"}, "Taster": {"en": "Button", "de": "Taster", "ru": "Кнопка"}, "Zentrale": {"en": "Central unit", "de": "Zentrale", "ru": "Центральное устройство"}, "Fenster": {"en": "Window", "de": "Fenster", "ru": "Окно"}, "Aktor": {"en": "Actor", "de": "Aktor", "ru": "Выключатель"}, "Beleuchtung": {"en": "Lighting", "de": "Beleuchtung", "ru": "Подсветка"}, "Technikraum": {"en": "Technical room", "de": "Technikraum", "ru": "Комната оборудования"}, "Büro": {"en": "Office", "de": "Büro", "ru": "Бюро"}, "Kellerraum": {"en": "Basement", "de": "Kellerraum", "ru": "Подвал"}, "Vorraum": {"en": "Vestibule", "de": "Vorraum", "ru": "Сени"}, "Gang EG": {"en": "Corridor 1st floor", "de": "Gang EG", "ru": "Коридор 1й этаж"}, "Aussen EG": {"en": "Outside 1st floor", "de": "Aussen EG", "ru": "Снаружи 1й этаж"}, "Carport": {"en": "Carport", "de": "Carport", "ru": "Навес над машиной"}, "WC EG": {"en": "WC 1st floor", "de": "WC EG", "ru": "Туалет 1й этаж"}, "Bad EG": {"en": "Bath 1st floor", "de": "Bad EG", "ru": "Bad 1й этаж"}, "Schrankraum": {"en": "Wardrobe", "de": "Schrankraum", "ru": "Шкаф"}, "Bad OG": {"en": "Bath 2nd floor", "de": "Bad OG", "ru": "Ванная 2й этаж"}, "Kinderzimmer 1": {"en": "Kid's room 1", "de": "Kinderzimmer 1", "ru": "Детская 1"}, "Kinderzimmer 2": {"en": "Kid's room 2", "de": "Kinderzimmer 2", "ru": "Детская 2"}, "Gang OG": {"en": "Corridor 2nd floor", "de": "Gang OG", "ru": "Коридор 2й этаж"}, "WC OG": {"en": "WC 2nd floor", "de": "WC OG", "ru": "Туалет 2й этаж"}, "Speis": {"en": "Speis", "de": "Speis", "ru": "Speis"}, "Terrasse": {"en": "Terrace", "de": "Terrasse", "ru": "Терасса"}, "Garten": {"en": "Garden", "de": "Garten", "ru": "Сад"}, "Aussen OG": {"en": "Outside 2nd floor", "de": "Aussen OG", "ru": "Снаружи 2й этаж"}, "Treppe": {"en": "Stair", "de": "Treppe", "ru": "Лестница"}, "Esszimmer": {"en": "Eating room", "de": "Esszimmer", "ru": "Столовая"}, "Helligkeitswerte": {"en": "Brightness", "de": "Helligkeitswerte", "ru": "Яркость"}, "IPCams": {"en": "IP Cams", "de": "IPCams", "ru": "Веб камеры"}, "Raffstore": {"en": "Raffstore", "de": "Raffstore", "ru": "Raffstore"}, "Klima": {"en": "Air condition", "de": "Klima", "ru": "Кондиционер"}, "Sicherheit": {"en": "Alarm", "de": "Sicherheit", "ru": "Сигнализация"}, "Verschluss": {"en": "Gate", "de": "Verschluss", "ru": "Двери"}, "Energiemanagement": {"en": "Energy management", "de": "Energiemanagement", "ru": "Потребление энергии"}, "VALVE STATE": {"en": "Valve State", "de": "Ventilposition", "ru": "Позиция вентиля"}, "LEVEL": {"en": "Level", "de": "Wert", "ru": "Положение"}, "PRESS LONG": {"en": "Press long", "de": "Lang drucken", "ru": "Длинное нажатие"}, "PRESS SHORT": {"en": "Press short", "de": "Kurz drucken", "ru": "Короткое нажатие"}, "PRESS CONT": {"en": "Press continuous", "de": "Kontinuerlich", "ru": "Продолжительное нажатие"}, "PRESS LONG RELEASE": {"en": "Press long release", "de": "Press Long Release", "ru": "Прекратить длинное нажатие"}, "STATE": {"en": "State", "de": "Zustand", "ru": "Состояние"}, "ADJUSTING COMMAND": {"en": "Adjusting Command", "de": "Justier-Kommando", "ru": "Команда настойки"}, "ADJUSTING DATA": {"en": "Adjusting Data", "de": "Justierungsdaten", "ru": "Данные для настойки"}, "SETPOINT": {"en": "Setpoint", "de": "Sollwert", "ru": "Заданное значение"}, "HUMIDITY": {"en": "Humidity", "de": "Luftfeuchtigkeit", "ru": "Влажность"}, "TEMPERATURE": {"en": "Temperature", "de": "Temperatur", "ru": "Температура"}, "INHIBIT": {"en": "Inhibit", "de": "Sperrung", "ru": "Заперто"}, "OPEN": {"en": "Open", "de": "Aufmachen", "ru": "Открыть"}, "RELOCK DELAY": {"en": "Relock Delay", "de": "Relock Verzögerung", "ru": "Задержка закрытия"}, "STATE UNCERTAIN": {"en": "State Uncertain", "de": "Unbekannter Zustand", "ru": "Неизвестное состояние"}, "BRIGHTNESS": {"en": "Brightness", "de": "Helligkeit", "ru": "Яркость"}, "ON TIME": {"en": "On Time", "de": "Auf-Zeit", "ru": "Время вкл."}, "SUBMIT": {"en": "Submit", "de": "Bestätigen", "ru": "Подтвердить"}, "FILLING LEVEL": {"en": "Filling Level", "de": "Füllniveau ", "ru": "Уровень заполнения"}, "DEFEKT STATE": {"en": "Defect", "de": "Defekt State", "ru": "Поломка"}, "UP": {"en": "up", "de": "Nach oben", "ru": "наверх"}, "DOWN": {"en": "down", "de": "nach unten", "ru": "вниз"}, "STOP": {"en": "Stop", "de": "Stop", "ru": "Стоп"}, "VOLTAGE": {"en": "Voltage", "de": "Spannung", "ru": "Напряжение"}, "POWER": {"en": "Power", "de": "Power", "ru": "Мощность"}, "FREQUENCY": {"en": "Frequency", "de": "Frequenz", "ru": "Частота"}, "ENERGY COUNTER": {"en": "Energy Counter", "de": "Energiezähler", "ru": "Счётчик расхода"}, "CURRENT": {"en": "Current", "de": "Strom", "ru": "Ток"}, "BOOT": {"en": "Boot", "de": "Boot", "ru": "Boot"}, "DECISION VALUE": {"en": "Decision Value", "de": "Entscheidungswert", "ru": "Порог принятия решения"}, "UNDEFINED": {"en": "--", "de": "--", "ru": "--"}, "AIR PRESSURE": {"en": "Air Pressure", "de": "Luftdruck", "ru": "Атмосферное давление"}, "RAINING": {"en": "Raining", "de": "Regen", "ru": "Дождь"}, "RAIN COUNTER": {"en": "Rain counter", "de": "Luftdruck", "ru": "Атмосферное давление"}, "SUNSHINEDURATION": {"en": "Sunshine duration", "de": "Sonnenscheindauer", "ru": "Длительность солнечного сияния"}, "WIND DIRECTION": {"en": "Wind direction", "de": "Windrichtung", "ru": "Направление ветра"}, "WIND DIRECTION RANGE": {"en": "Wind direction range", "de": "Windrichtungumfang", "ru": "Разброс направления ветра"}, "WIND SPEED": {"en": "Wind speed", "de": "Windgeschwindigkeit", "ru": "Скорость ветра"}, "INSTALL TEST": {"en": "Install test", "de": "Installtest", "ru": "INSTALL TEST"}, "VENT_CLOSED": {"en": "Closed", "de": "Ventil Zu", "ru": "Закрыто"} }; var mobile = { version: "0.4.10", requiredServerVersion: '0.0.0', enums: {}, objects: {}, states: {}, root: [], refresh: false, isFirstTime: true, conn: servConn, editMode: false, queueStates: [], updateStates: [], ids: [], user: 'admin', defaultInvisibleRoles: ['inhibit', 'button', 'action', 'timer'], defaultInvisibleNames: ['TIMER_ON', 'RELOCK_DELAY', 'OLD_VALUE', 'STATE_UNCERTAIN', 'DECISION_VALUE', 'ADJUSTING_DATA' , 'ADJUSTING_COMMAND', 'INSTALL_TEST'], ignoreIndicators: ['indicator.updates', 'indicator.state'], lastTimes: [], activePage: null, erasePage: {}, isMobile: null, icons: { temperature: 'temp_temperature.svg', humidity: 'weather_humidity.svg', setpoint: 'temp_control.svg' }, detectMobile: function () { if( navigator.userAgent.match(/Android/i) || navigator.userAgent.match(/webOS/i) || navigator.userAgent.match(/iPhone/i) || navigator.userAgent.match(/iPad/i) || navigator.userAgent.match(/iPod/i) || navigator.userAgent.match(/BlackBerry/i) || navigator.userAgent.match(/Windows Phone/i) ){ return true; } else { if(window.innerWidth <= 800 && window.innerHeight <= 600) { return true; } else { return false; } } }, init: function (id) { var that = this; //this.isMobile = this.detectMobile(); this.conn.namespace = 'mobile.0'; this.conn._useStorage = true; var url = $.mobile.path.parseUrl(location.href); this.editMode = (url.search === '?edit'); if (this.editMode) { this.conn.clearCache(); } else if (url.search === '?refresh') { this.conn.clearCache(); location.href = './' + location.hash; } // show edit indicator if (this.editMode) $('#edit_indicator').show(); $('#popupEditName').popup().popup('close'); $('#name_edit').keyup(function (e) { if (e.keyCode === 13) { $('#ok_edit').trigger('click'); } }); $('#ok_edit').click(function (e) { e.stopPropagation(); var $nameEdit = $('#name_edit'); var id = $nameEdit.data('id'); var val = $nameEdit.val(); if (that.objects[id].common) { that.objects[id].common.mobile = that.objects[id].common.mobile || {}; that.objects[id].common.mobile[that.user] = that.objects[id].common.mobile[that.user] || {}; that.objects[id].common.mobile[that.user].name = val || undefined; $('.mobile-widget-title[data-edit-id="' + id + '"]').html(_(val || that.objects[id].common.name || id)); that.saveSettings(id); } $('#popupEditName').popup('close'); }); $(document).bind('pagebeforechange', function (e, data) { if (typeof data.toPage === 'string') { var u = $.mobile.path.parseUrl(data.toPage); var id = decodeURIComponent(u.hash.toString()); id = id.substring(1); id = decodeURIComponent(id); id = id.replace(/&.*$/, ''); that.renderPage(id); if (id && that.activePage !== id && that.activePage && id !== 'info') { // destroy page that.destroyPage(that.activePage); } if (id && that.erasePage[id]) { clearTimeout(that.erasePage[id]); delete that.erasePage[id]; } if (id !== 'info') that.activePage = id; } }); // edit mode is off => switch it on $('.mobile-noedit').click(function () { document.location = '?edit#' + (that.activePage || ''); }); // edit mode is on => switch it off $('.mobile-edit').click(function () { document.location = './?refresh#' + (that.activePage || ''); }); $('#edit_indicator').click(function () { that.objects = that.calcChildren(); document.location = './?refresh' + document.location.hash.replace('&ui-state=dialog', ''); }); $('.mobile-reset').click(function () { that.resetVisibility(function () { document.location = './' + document.location.hash.replace('&ui-state=dialog', ''); }); }); this.conn.init(null, { onConnChange: function (isConnected) { if (isConnected) { //$('#server-disconnect').popup().popup('close'); if (that.isFirstTime) { that.conn.getVersion(function (version) { if (version) { if (that.compareVersion(version, that.requiredServerVersion)) { window.alert(_('Warning: requires Server version %s - found Server version %s - please update Server.', that.requiredServerVersion, version)); } } //else { // Possible not authenticated, wait for request from server //} }); console.log(_('Loading data values...')); } // Get Server language that.conn.getObjects(!that.refresh, function (err, objects) { if (err) console.error(err); that.conn.getConfig(!that.refresh, function (err, config) { if (err) console.error(err); that.conn.getObject('system.adapter.' + that.conn.namespace, !that.refresh, function (err, mobileConfig) { if (err) console.error(err); systemLang = config.language || systemLang; that.language = systemLang; that.dateFormat = config.dateFormat; translateAll(); if (that.isFirstTime) { // Init edit dialog that.isFirstTime = false; } that.config = mobileConfig && mobileConfig.native ? mobileConfig.native : {indicators: {}, theme: 'light'}; that.config.indicators = that.config.indicators || {}; that.config.theme = that.config.theme || 'light'; that.loadStyle(); that.objects = that.calcChildren(objects); // find for every object the children // show last sync time var syncTime = that.conn.getSyncTime(); if (typeof(Storage) === 'undefined') { $('.mobile-refresh').hide(); $('.last-synchronised').html(_('not supported')); } else { $('.last-synchronised').html(!syncTime ? _('never') : syncTime.toLocaleDateString() + ' ' + syncTime.toLocaleTimeString()); } that.conn.getEnums(!that.refresh, function (err, enums) { that.enums = enums; for (var e in that.enums) { var parts = e.split('.'); if (parts.length === 2) that.root.push(e); } that.renderRootPages(); }); }); }); }); } else { //$('#server-disconnect').popup('open'); } }, onRefresh: function () { window.location.reload(); }, onUpdate: function (id, state) { setTimeout(function () { that.states[id] = state; that.updateState(id); }, 0); }, onObjectChange: function (id, obj) { if (!that.objects || !that.editMode) return; if (obj) { obj.children = that.objects[id].children; that.objects[id] = obj; } else { if (that.objects[id]) delete that.objects[id]; } }, onError: function (err) { window.alert(_('Cannot execute %s for %s, because of insufficient permissions', err.command, err.arg)); } }, true/*edit mode */); $('.mobile-version').html('v' + this.version); $('.mobile-theme-selector').click(function () { var oldTheme = that.config.theme; that.config.theme = $(this).data('theme'); that.saveConfig(); that.loadStyle(); $('.mobile-theme-' + oldTheme).removeClass('mobile-theme-' + oldTheme).addClass('mobile-theme-' + that.config.theme); $('.mobile-theme-selector').each(function () { if ($(this).data('theme') === that.config.theme) { $(this).parent().addClass('ui-btn-active'); } else { $(this).parent().removeClass('ui-btn-active'); } }); }); that.monitorSleep(); }, monitorSleep: function () { var that = this; that.lastTimerUpdate = new Date().getTime(); that.sleepTimer = setTimeout(function () { var now = new Date().getTime(); if (now - that.lastTimerUpdate > 31000) { window.location.reload(); } else{ that.monitorSleep(); } }, 10000); }, saveObjects: function (cb) { if (this.idsToSave && this.idsToSave.length) { var id = this.idsToSave.pop(); var that = this; this.conn._socket.emit('getObject', id, function (err, obj) { obj.common.mobile = that.objects[id].common.mobile; if (!err) { that.conn._socket.emit('setObject', id, obj, function (err) { if (!err) { setTimeout(function () { that.saveObjects(); }, 0); } else { console.error(err); cb && cb(err, id); } }); } else { console.error(err); cb && cb(err, id); } }); } else { cb && cb(); } }, saveSettings: function (id) { this.idsToSave = this.idsToSave || []; if (this.saveTimeout) clearTimeout(this.saveTimeout); if (this.idsToSave.indexOf(id) === -1) this.idsToSave.push(id); var that = this; this.saveTimeout = setTimeout(function () { that.saveObjects(); }, 300); }, saveConfig: function (cb) { var that = this; this.conn._socket.emit('getObject', 'system.adapter.' + this.conn.namespace, function (err, obj) { obj.native = that.config; that.conn._socket.emit('setObject', obj._id, obj, function (err) { cb && cb(err); }); }); }, modifyObjects: function (list, cb) { if (!list || !list.length) { cb && cb(); return; } var id = list.pop(); var that = this; this.conn._socket.emit('setObject', id, this.objects[id], function (err) { if (!err) { setTimeout(function () { that.modifyObjects(list, cb); }, 0); } else { console.error(err); cb && cb(err, id); } }); }, resetVisibility: function (cb) { var modified = []; for (var id in this.objects) { if (this.objects[id] && this.objects[id].common && this.objects[id].common.mobile && this.objects[id].common.mobile[this.user] && this.objects[id].common.mobile[this.user].visible !== undefined) { delete this.objects[id].common.mobile[this.user].visible; modified.push(id); } } this.modifyObjects(modified, cb); }, updateLastStates: function (justStartStopTimer) { var that = mobile; if (that.lastTimes.length && !that.lastTimesTimer) { that.lastTimesTimer = setInterval(function () { that.updateLastStates(); }, 30000); } else if (!that.lastTimes.length && that.lastTimesTimer) { clearInterval(that.lastTimesTimer); that.lastTimesTimer = null; } if (!justStartStopTimer) { var actual = JSON.parse(JSON.stringify(that.lastTimes)); $('.mobile-last-change').each(function () { var lc = $(this).data('time'); if (!lc) return; var pos = actual.indexOf($(this).data('edit-id')); if (!pos) { that.lastTimes.push($(this).data('edit-id')); } else { actual.splice(pos, 1); } $(this).html(that.formatLastChange(lc)); }); // remove all ids from that.lastTimes, that are no more exists for (var a = 0; a < actual.length; a++) { var pos = that.lastTimes.indexOf(actual[a]); if (pos) that.lastTimes.splice(pos, 1); } } }, updateState: function (id, force) { var that = this; $('[data-mobile-id="' + id + '"]').each(function () { var _id = $(this).data('mobile-id'); // actually _id and id must be equal var role = $(this).data('role'); if (role === 'lastChange') { var lc = that.states[_id].lc; var pos = that.lastTimes.indexOf(_id); if (!lc) { if (pos !== -1) { that.lastTimes.splice(pos, 1); that.updateLastStates(true); } $(this).data('time', null).hide(); return; } $(this) .data('time', lc) .show() .html(that.formatLastChange(lc)); if (pos === -1) { that.lastTimes.push(_id); that.updateLastStates(true); } } else { var val = $(this).val(); if (that.states[_id].val === undefined || that.states[_id].val === 'undefined' || that.states[_id].val === null || that.states[_id].val === 'null') { console.warn('No data for "' + _id + '".'); that.states[_id].val = ''; } var type = $(this).data('type'); if (type !== 'set' && (force || val !== that.states[_id].val.toString())) { var rawVal = that.states[_id].val; val = that.states[_id].val; if (type === 'range') { $(this).val(val); $(this).slider('refresh'); } else if (type === 'set') { //ignore } else if (type === 'icon') { var img; var _type = $(this).data('icon-type'); switch (_type) { case 'light': if (val === 'true' || val === true) { rawVal = true; } else if (val === 'false' || val === false) { rawVal = false; } else if (val !== null && parseFloat(val).toString() === val.toString()) { rawVal = parseFloat(val); } img = rawVal ? 'img/bulbOn.png' : 'img/bulbOff.png'; break; case 'red': case 'green': case 'blue': case 'white': rawVal = parseFloat(val) || 0; var min = $(this).data('min'); var max = $(this).data('max'); if (min === undefined || min === null || min === '') { min = 0; } else { min = parseFloat(min); } if (max === undefined || max === null || max === '') { max = 100; } else { max = parseFloat(max); } if (rawVal > max) rawVal = max; if (rawVal < min) rawVal = min; rawVal = (rawVal - min) / (max - min); if (rawVal < 0.1) rawVal = 0.1; img = 'img/rgb-' + _type + '.png'; $(this).css('opacity', rawVal); break; case 'socket': if (val === 'true' || val === true) { rawVal = true; } else if (val === 'false' || val === false) { rawVal = false; } else if (val !== null && parseFloat(val).toString() === val.toString()) { rawVal = parseFloat(val); } img = rawVal ? 'img/socketOn.png' : 'img/socketOff.png'; break; case 'blinds': if (val === 'true' || val === true) { rawVal = 100; } else if (val === 'false' || val === false) { rawVal = 0; } else if (val !== null && parseFloat(val).toString() === val.toString()) { rawVal = parseFloat(val); } var min = $(this).data('min'); var max = $(this).data('max'); if (min === undefined || min === null || min === '') { min = 0; } else { min = parseFloat(min); } if (max === undefined || max === null || max === '') { max = 100; } else { max = parseFloat(max); } if (rawVal > max) rawVal = max; if (rawVal < min) rawVal = min; rawVal = Math.round((rawVal - min) * 100 / (max - min)); rawVal = Math.round(rawVal / 10) * 10; var imgVal = 100 - rawVal; // invert values, in HM 100% means: completely open img = 'img/blind' + imgVal + '.png'; break; default: console.warn('Unknown icon type "' + $(this).data('icon-type') + '"'); break; } $(this).val(val); if (img) { $(this).attr('src', img); } } else if (role === 'slider') { var found = false; var sVal = val.toString(); $(this).find('option').each(function () { if ($(this).val() === sVal) { found = true; return false; } }); if (found) { $(this).val(sVal); $(this).slider().slider('refresh'); } } else if (role === 'select') { $(this).val(val); $(this).selectmenu().selectmenu('refresh'); $(this).prev().removeClass('ui-body-inherit'); } else if($(this).prop('tagName') === 'INPUT') { $(this).val(val); } else { if (val === 'true' || val === true) { rawVal = true; val = _('true'); } else if (val === 'false' || val === false) { rawVal = false; val = _('false'); } else if (val !== null && parseFloat(val).toString() === val.toString()) { val = parseFloat(val); rawVal = val; } var states = $(this).data('states'); if (states && ((role !== 'indicator' && role !== 'error') || val)) { // convert JSON if (states[0] === '{') { try { var values = JSON.parse(states); if (values[val] === undefined) { rawVal = val; } else { rawVal = values[val]; val = _(values[val]); } } catch (ex) { console.error('Cannot parse states for ' + _id); } } else if (typeof states === 'object') { if (states[val] === undefined) { rawVal = val; } else { rawVal = states[val]; val = _(states[val]); } } } if (role === 'indicator' || role === 'error') { if (!rawVal) { $(this).hide(); } else { $(this).show().attr('data-value', rawVal); } if (role === 'error') { if (rawVal) { $(this).addClass('mobile-error'); } else { $(this).removeClass('mobile-error'); } } } var unit = $(this).data('unit') || ''; if (unit) unit = '' + unit + ''; if ($(this).data('title')) { $(this).attr('title', $(this).attr('name') + ' - ' + val + unit); } else { var id = $(this).data('p'); if (id) { if (typeof rawVal === 'boolean') { $('#' + id).html($(this).attr('name')); } else { $('#' + id).html($(this).attr('name') + ' - ' + val + unit); } } else { $(this).html(val + unit); } } } } } }); }, renderWidget: function (obj, elem, parentName) { switch (obj.type) { case 'device': this.renderChannel(obj, elem, parentName); break; case 'channel': this.renderChannel(obj, elem, parentName); break; case 'state': this.renderState(obj, elem, parentName); break; default: } }, isDefaultInvisible: function (role, id) { if (id) { var name = id.split('.').pop(); if (this.defaultInvisibleNames.indexOf(name) !== -1) return true; } if (role) { for (var w = 0; w < this.defaultInvisibleRoles.length; w++) { if (role.indexOf(this.defaultInvisibleRoles[w]) !== -1) return true; } } return false; }, prepareGenericState: function (obj, struct) { var states = obj.common.states || ''; var vStates; var stateName = obj._id.split('.').pop(); if (states) { if (typeof states === 'object') { vStates = states; states = JSON.stringify(states); } else if (typeof states === 'string' && states[0] !== '{') { var values = states.split(';'); states = {}; for (var w = 0; w < values.length; w++) { var parts = values[w].split(':'); states[parts[0]] = parts[1]; } vStates = states; states = JSON.stringify(states); } } if (!this.states[obj._id]) { // read states if (this.queueStates.indexOf(obj._id) === -1) this.queueStates.push(obj._id); } else { if (this.updateStates.indexOf(obj._id) === -1) this.updateStates.push(obj._id); } struct.controls = []; var on = 'on'; var off = 'off'; var roles = obj.common.role ? obj.common.role.split('.') : []; if (roles.indexOf('blind') !== -1 || stateName === 'OPEN' || stateName === 'CLOSE') { on = 'opened'; off = 'closed'; } // add for blinds and dimmer on/off switch if (obj.common.role === 'level.dimmer' || obj.common.role === 'level.blind') { struct.controls.push({ checkbox: true, value: '', control: ' \n' }); } // add slider if (roles.indexOf('level') !== -1 || (obj.common.write && obj.common.type === 'number' && obj.common.max !== undefined)) { struct.controls.push({ value: '
', control: '' }); } else // add button if (roles.indexOf('button') !== -1 || roles.indexOf('action') !== -1) { struct.controls.push({ value: '', control: '' }); // title is on the button struct.title = null; } else // generic types // controllable boolean if (obj.common.write && obj.common.type === 'boolean') { var min; var max; // support of boolean states if (vStates) { if (vStates[0] !== undefined) { min = 0; off = vStates[0]; } else if (vStates['false'] !== undefined) { min = false; off = vStates['false']; } else { var count = 0; for (var vv in vStates) { if (count === 0) { min = vv; off = vStates[vv]; } else if (count === 1) { on = vStates[vv]; max = vv; break; } count++; } } if (vStates[1] !== undefined) { max = 1; on = vStates[1]; } else if (vStates['true'] !== undefined) { max = true; on = vStates['true']; } } else { min = (obj.common.min !== undefined ? obj.common.min : false); max = (obj.common.max !== undefined ? obj.common.max : true); } var length = off.length; if (on.length > length) length = on.length; length = 70 + length * 7; if (length < 112) length = 112; struct.controls.push({ checkbox: true, value: '', control: ' ' }); } else // controllable number, but as text if (obj.common.write && obj.common.type === 'number' && obj.common.max !== undefined && !states) { struct.controls.push({ value: '
', control: '' }); } else if (obj.common.write && !states) { struct.controls.push({ set: true, value: '' + (obj.common.unit ? '' + obj.common.unit + '': ''), valueStyle: 'margin-top: 16px', control: '' }); } else if (obj.common.write && states) { var text = '\n'; struct.controls.push({ value: '', select: true, control: text }); } else { // non controllable value struct.controls.push({ value: '
', lastChange: (obj.common.type === 'boolean') ? '
' : undefined }); } return struct; }, prepareElement: function (obj, parentName, struct) { var i; var id; var name = (obj.common.mobile && obj.common.mobile[this.user]) ? obj.common.mobile[this.user].name || obj.common.name || obj._id : obj.common.name || obj._id; struct = struct || {}; // ignore indicators if (obj.common.role && obj.common.role.match(/^indicator\./) && this.ignoreIndicators.indexOf(obj.common.role) === -1) return ''; var name; if (obj.common.mobile && obj.common.mobile[this.user] && obj.common.mobile[this.user].name) { name = obj.common.mobile[this.user].name } else { name = obj.common.name || obj._id; name = decodeURIComponent(name); // remove room or function name from device name if (parentName) { var reg1 = new RegExp('[-.\/]+' + parentName + '[-.\/]+', 'gi'); var reg2 = new RegExp('[-.\/]+' + parentName, 'gi'); var reg3 = new RegExp(parentName + '[-.\/]+', 'gi'); var reg4 = new RegExp(parentName, 'gi'); name = name.replace(reg1, '.'); name = name.replace(reg2, ''); name = name.replace(reg3, ''); name = name.replace(reg4, ''); name = name.replace(/_/g, ' '); name = name.replace(/\./g, ' '); try { name = decodeURIComponent(name); } catch(err) { console.error('Cannot decode: ' + name + '(' + err + ')'); } var words = name.split(/\s+/); for (var w = words.length - 1; w >= 0; w--) { if (!words[w]) { words.splice(w, 1); continue; } if (!words[w].match(/^[A-ZÜÖÄА-Я0-9]+$/)) { words[w] = words[w][0].toUpperCase() + words[w].substring(1).toLowerCase(); } } name = words.join(' '); } } // try to translate the state anme if (obj.type === 'state') name = _(name); struct.title = name; var mobile = obj.common.mobile && obj.common.mobile[this.user]; var type; if (mobile && (type = obj.common.mobile[this.user].type)) { struct.icon = { type: type, min: (obj.common.min === undefined) ? 0 : parseFloat(obj.common.min) || 0, max: (obj.common.max === undefined) ? 100 : parseFloat(obj.common.max) || 0 }; } else if (mobile && obj.common.mobile[this.user].icon) { struct.icon = obj.common.mobile[this.user].icon; } else if (obj.common.icon) { var adapter = obj._id.split('.').shift(); if (obj.common.icon[0] === '/' && obj.common.icon[1] === '/') { struct.icon = obj.common.icon.substring(1); } else { struct.icon = '/adapter/' + adapter + '/' + obj.common.icon; } } if (obj.children && obj.children.length) { //html += ''; struct.children = {}; for (i = 0; i < obj.children.length; i++) { id = obj.children[i]; if (!this.editMode && this.objects[id].common.mobile && this.objects[id].common.mobile[this.user] && this.objects[id].common.mobile[this.user].visible === false) continue; if (this.objects[id] && this.objects[id].type === 'state') { struct.children[id] = struct.children[id] || {}; struct.children[id].parent = obj._id; this.prepareElement(this.objects[id], obj.common.name || obj._id, struct.children[id]); if (!struct.children[id].controls && !struct.children[id].children) delete struct.children[id]; } } // Check if any child is visible var anyChild = false; for (var c in struct.children) { if (struct.children.hasOwnProperty(c)) { anyChild = true; break; } } if (!anyChild) delete struct.children; } else if (this.editMode || !obj.common.mobile || !obj.common.mobile[this.user] || obj.common.mobile[this.user].visible !== false) { // do not show some buttons by default if (!this.editMode && (!obj.common.mobile || !obj.common.mobile[this.user] || obj.common.mobile[this.user].visible === undefined) && this.isDefaultInvisible(obj.common.role, obj._id)) { // ignore it if not explicit enabled } else { this.prepareGenericState(obj, struct); } } return struct; }, renderIndicators: function (obj, $elem, originId) { var id; // try to find some in device itself var parent = obj._id.split('.'); parent.pop(); parent = parent.join('.'); if (this.objects[parent] && this.objects[parent].type === 'device') { this.renderIndicators(this.objects[parent], $elem, originId); } else { var checked = $elem.data('indicators') || []; if (checked.indexOf(obj._id) === -1) { var $indicators = $elem.find('.mobile-indicators'); if (!$indicators.length) { $elem.append('
'); $indicators = $elem.find('.mobile-indicators'); } checked.push(obj._id); $elem.data('indicators', checked); if (obj.children) { for (var i = 0; i < obj.children.length; i++) { id = obj.children[i]; if (this.objects[id] && this.objects[id].common) { if (this.objects[id].common.role && this.objects[id].common.role.match(/^indicator\./) && this.ignoreIndicators.indexOf(obj.common.role) === -1) { // do not render WORKING and DIRECTION for other channels than origin if ((this.objects[id].common.role === 'indicator.working' || this.objects[id].common.role === 'indicator.direction') && originId !== id.substring(0, originId.length)) { continue; } var stateName = id.split('.').pop(); if (this.config.indicators[stateName] !== undefined && !this.config.indicators[stateName]) continue; if (!this.states[id]) { // read states if (this.queueStates.indexOf(id) === -1) this.queueStates.push(id); } else { if (this.updateStates.indexOf(id) === -1) this.updateStates.push(id); } var states = this.objects[id].common.states || ''; if (states) { if (typeof states === 'object') { states = JSON.stringify(states); } else if (typeof states === 'string' && states[0] !== '{') { var values = states.split(';'); states = {}; for (var v = 0; v < values.length; v++) { var parts = values.split(':'); states[parts[0]] = parts[1]; } states = JSON.stringify(states); } } var name = (this.objects[id].common.name || id).split('.').pop().replace(/_/g, ' '); var _id = id.replace(/\./g, '-'); var text = '' + '
' + '

'; $indicators.append(text); } if (this.objects[id].children && this.objects[id].children.length) { this.renderIndicators(this.objects[id], $elem, originId); } } } } } } }, formatLastChange: function (ms) { if (ms < 946684800000) ms *= 1000; // if less than 2000.01.01 may be it is seconds var now = new Date().getTime(); var seconds = Math.round((now - ms) / 1000); var text = ''; // format seconds if (seconds < 60) { text += _('%s seconds ago', seconds); } else if (seconds < 180) { // under 3 minutes text += _('ca. %s minutes ago', Math.floor(seconds / 60)); } else if (seconds < 600) { // under 10 minutes text += _('%s minutes ago', Math.round(seconds / 60)); } else if (seconds < 36000) { // under 10 hours text += _('%s h. and %s m. ago', Math.floor(seconds / 3600), Math.round(seconds / 60) % 60); } else if (seconds < 86400) { // under one day text += _('%s hours ago', Math.floor(seconds / 3600), Math.round(seconds / 60) % 60); } else if (seconds < 86400) { // under 3 days text += _('%s days and %s h. ago', Math.floor(seconds / 86400), Math.round(seconds / 3600) % 24); } else { text += _('%s days ago', Math.round(seconds / 86400)); } return text; }, _renderSingleSubState: function (control, parent, id) { var html = ''; html += '<' + (this.editMode && !control.oneState ? 'li' : 'div') + ' style="width: 100%" class="' + (control.id ? 'mobile-visibility' : '') + '" ' + (control.id ? ' data-edit-id="' + control.id + '"' : '') + '>'; html += '\n'; var icon = ''; if (parent.icon) { if (typeof parent.icon === 'object') { icon = ''; } else { icon = ''; } } if (control.set) { html += ' \n'; html += ' \n'; } else if (control.checkbox && !control.value) { html += ' \n'; html += ' \n'; // show title only one time parent.title = null; } else { if (parent.title && control.control) { html += '\n'; //html += ''; parent.title = null; } else if (parent.title && !control.control) { html += ' \n'; //html += ''; parent.title = null; } else if (control.control) { // just control or just value html += ' \n'; //html += ' \n'; html += ' \n'; } else { } } /*if (parent.icon) { html += ''; }*/ html += '\n'; html += '
' + (control.control || '') + '' + (control.value || '') + '' + (control.control || '') + '' + (parent.title || '') + '' + (control.value || '').replace('mobile-value', 'mobile-value mobile-value-big mobile-value-with-units') + ''; html += ''; html += ' '; html += ' \n'; html += ' \n'; html += '
' + parent.title + icon + '
' + control.control + '
'; html += '
' + (control.value || '') + ''; html += ''; if (control.lastChange) { html += ' '; html += ' \n'; html += ' \n'; html += '
' + parent.title + '
' + control.lastChange + '
'; } else { html += '
' + parent.title + '
\n'; } html += '
' + (control.value || '') + '' + (control.control || '') + '' + (control.control || '') + ''; html += parent.icon ? '' : ''; html += '
\n'; return html; }, _renderCombinedSubState : function (control, parent, state, id) { // used only in non edit mode var html = ''; html += '
\n'; html += ' \n'; html += '
' + (control.value || '') + (control.control || '') + ''; html += parent.title ? '
' + parent.title + '
\n' : ''; html += state && state.title ? '
' + state.title + '
\n' : ''; html += control.lastChange ? '
' + control.lastChange + '
' : ''; html += '
'; if (parent.icon) { if (typeof parent.icon === 'object') { html += ''; } else { html += ''; } } html += '
\n'; return html; }, renderChannel: function (obj, $elem, parentName) { var _mobile = obj.common && obj.common.mobile ? obj.common.mobile[this.user] : null; if (!this.editMode && _mobile && _mobile.visible === false) return; var struct = this.prepareElement(obj, parentName); if (!struct.controls && !struct.children) return; var html = '
  • \n'; // show channel description html += '
    \n'; var count = 0; var order; var firstChild; for (var child in struct.children) { if (!firstChild) firstChild = child; count++; } var c; order = _mobile ? _mobile.order || [] : []; if (struct.children) { for (child in struct.children) { if (order.indexOf(child) === -1) order.push(child); } for (c = order.length - 1; c >= 0; c--) { if (!struct.children[order[c]]) order.splice(c, 1); } } else { order = []; } // do not show title if only one child if (count < 2 && !this.editMode) { //struct.title = ''; } // if only one child and not slider if (!this.editMode && count === 1 && (struct.children[firstChild].controls[0].select || struct.children[firstChild].controls[0].checkbox || !struct.children[firstChild].controls[0].control)) { // Show all in one line // control | text | icon struct.children[firstChild].controls[0].id = firstChild; // enable visibility html += this._renderCombinedSubState(struct.children[firstChild].controls[0], struct, struct.children[child].controls.length === 1 ? struct.children[firstChild]: null, firstChild); for (c = 1; c < struct.children[child].controls.length; c++) { struct.children[child].controls[c].id = child; // enable visibility html += this._renderSingleSubState(struct.children[child].controls[c], struct.children[child], child); } } else { // place title if (struct.icon) { if (typeof struct.icon === 'object') { html += ''; } else { html += ''; } } html += struct.title ? '
    ' + struct.title + '
    \n' : ''; html += '<' + (this.editMode ? 'ul' : 'div') + ' class="mobile-widget-b" title="' + (obj.common.role || '') +'" style="padding: 0" data-edit-id="' + obj._id + '">\n'; // try to build states for (var or = 0; or < order.length; or++) { child = order[or]; if (!struct.children[child].controls) continue; for (c = 0; c < struct.children[child].controls.length; c++) { struct.children[child].controls[c].id = child; // enable visibility html += this._renderSingleSubState(struct.children[child].controls[c], struct.children[child], child); } } html += '\n'; } html += '
    \n'; html += '
  • \n'; var $channel = $(html); $elem.append($channel); if ($elem.hasClass('ui-listview')) { $elem.listview('refresh'); } if (!this.editMode) this.renderIndicators(obj, $channel, obj._id); }, renderState: function (obj, $elem, parentName) { if (!this.editMode && obj.common && obj.common.mobile && obj.common.mobile[this.user] && obj.common.mobile[this.user].visible === false) return; var struct = this.prepareElement(obj, parentName); if (!struct.controls) return; var html = '
  • \n'; // show State description html += '
    \n'; var c; if (!this.editMode && (struct.controls[0].select || struct.controls[0].checkbox || !struct.controls[0].control)) { // Show all in one line // control | text | icon html += this._renderCombinedSubState(struct.controls[0], struct, null, obj._id); for (c = 1; c < struct.controls.length; c++) { html += this._renderSingleSubState(struct.controls[c], struct, obj._id); } } else { // place title if (struct.icon) { if (typeof struct.icon === 'object') { html += ''; } else { html += ''; } } html += struct.title ? '
    ' + struct.title + '
    \n' : ''; html += '
    \n'; // try to build state for (c = 0; c < struct.controls.length; c++) { struct.controls[c].oneState = true; html += this._renderSingleSubState(struct.controls[c], struct, obj._id); } html += '
    \n'; } html += '
    \n'; html += '
  • \n'; $elem.append(html); if ($elem.hasClass('ui-listview')) $elem.listview('refresh'); }, renderEditButtons: function () { if (this.editMode) { var that = this; $(document).on('pageshow', function (event) { // render root pages $('.mobile-visibility-root').each(function () { if (!$(this).is(':visible')) return; if ($(this).parent().find('.mobile-edit-enum').length) return; var id = $(this).data('edit-id'); var href = $(this).attr('href'); that.objects[id].common.mobile = that.objects[id].common.mobile || {}; that.objects[id].common.mobile[that.user] = that.objects[id].common.mobile[that.user] || {}; var mobile = that.objects[id].common.mobile[that.user]; // everything is visible by default if (mobile.visible === undefined) { mobile.visible = !that.isDefaultInvisible(that.objects[id].common.role, id); that.saveSettings(id); } var $overlay = $('
    '); var $checkbox = $('
    ' + (mobile.visible ? '✓' : '') + '
    '); var $name = $(''); //var $sort = $(''); $overlay.append($checkbox); $overlay.append($name); //$overlay.append($sort); $(this).parent().prepend($overlay); $overlay.click(onForward); $checkbox.click(onEnumVisibility); $name.click(onEditName); }); // render second menu $('.mobile-visibility-subroot').each(function () { if (!$(this).is(':visible')) return; if ($(this).parent().find('.mobile-edit-subenum').length) return; var id = $(this).data('edit-id'); var href = $(this).attr('href'); that.objects[id].common.mobile = that.objects[id].common.mobile || {}; that.objects[id].common.mobile[that.user] = that.objects[id].common.mobile[that.user] || {}; var mobile = that.objects[id].common.mobile[that.user]; // everything is visible by default if (mobile.visible === undefined) { mobile.visible = !that.isDefaultInvisible(that.objects[id].common.role, id); that.saveSettings(id); } var $overlay = $('
    '); var $checkbox = $('
    ' + (mobile.visible ? '✓' : '') + '
    '); var $name = $(''); var $icon = $(''); var $sort = $(''); $overlay.append($checkbox); $overlay.append($name); $overlay.append($icon); $overlay.append($sort); $(this).parent().prepend($overlay); $overlay.click(onForward); $checkbox.click(onEnumVisibility); $name.click(onEditName); }); var typeSelect = '' + '' + '' + '' + '' + '' + '' + ''; // render elements $('.mobile-visibility').each(function () { if (!$(this).is(':visible')) return; var id = $(this).data('edit-id'); if ($(this).data('processed')) return; $(this).data('processed', true); that.objects[id].common.mobile = that.objects[id].common.mobile || {}; that.objects[id].common.mobile[that.user] = that.objects[id].common.mobile[that.user] || {}; var mobile = that.objects[id].common.mobile[that.user]; // button is invisible by default if (mobile.visible === undefined) { mobile.visible = !that.isDefaultInvisible(that.objects[id].common.role, id); that.saveSettings(id); } var $overlay = $('
    '); var $checkbox = $('
    ' + (mobile.visible ? '✓' : '') + '
    '); var margin = that.objects[id].type === 'state' ? '1.6em' : '2.3em'; var $name = $(''); var $type = $('
    '); var $sort = $(''); $overlay.append($checkbox); $overlay.append($name); $overlay.append($type); $overlay.append($sort); $type.find('select').change(onTypeChanged); $(this).prepend($overlay); $checkbox.click(onStateVisibility); $name.click(onEditName); }); // detect channels, that have all states hidden that.detectAllHiddenStates(); $('ul').each(function () { var $ul = $(this); var $li = $ul.find('> li'); if (!$ul.data('edit-id')) { $li.find('.mobile-edit-sort').remove(); return; } if ($li.length > 1) { $ul.sortable(); $ul.disableSelection().bind('sortstop', function(event, ui) { // get order var $li = $(this).find('> li'); var order = []; var rootId = $(this).data('edit-id'); $li.each(function () { var id = $(this).data('edit-id'); order.push(id); }); that.objects[rootId].common.mobile = that.objects[rootId].common.mobile || {}; that.objects[rootId].common.mobile[that.user] = that.objects[rootId].common.mobile[that.user] || {}; that.objects[rootId].common.mobile[that.user].order = order; that.saveSettings(rootId); //$(this).listview('refresh'); }); } else { $li.find('.mobile-edit-sort:first').remove(); } }); }); } }, renderIndicatorSettings: function () { var text = '' + _('Enabled indicators') + ':'; for (var ind in this.config.indicators) { text += ''; } $('#enabledIndicators').html(text); }, renderRootPages: function () { // http://demos.jquerymobile.com/1.2.1/docs/toolbars/docs-navbar.html var grid; var count = 0; var name; var common; for (var i = 0; i < this.root.length; i++) { common = this.objects[this.root[i]] ? this.objects[this.root[i]].common : null; // hide if invisible if (!this.editMode && common && common.mobile && common.mobile[this.user] && common.mobile[this.user].visible === false) continue; count++; } switch (count) { case 1: grid = '0'; break; case 2: grid = 'a'; break; case 3: grid = 'b'; break; case 4: grid = 'c'; break; case 5: grid = 'd'; break; default: } for (i = 0; i < this.root.length; i++) { var navbar = ''; var rCommon = this.objects[this.root[i]] ? this.objects[this.root[i]].common : null; if (!this.editMode && rCommon && rCommon.mobile && rCommon.mobile[this.user] && rCommon.mobile[this.user].visible === false) continue; // build footer (root menu) for (var j = 0; j < this.root.length; j++) { common = this.objects[this.root[j]] ? this.objects[this.root[j]].common : null; if (!common) { console.error('Invalid object "' + _id + '" '); continue; } // hide if invisible if (!this.editMode && common && common.mobile && common.mobile[this.user] && common.mobile[this.user].visible === false) continue; name = common.mobile && common.mobile[this.user] ? common.mobile[this.user].name || common.name || this.root[j] : common.name || this.root[j]; navbar += '
  • ' + _(name) + '
  • \n'; } var menu = ''; // build footer (root menu) var order = (rCommon.mobile && rCommon.mobile[this.user]) ? rCommon.mobile && rCommon.mobile[this.user].order || [] : []; // check if still actual for (var k = order.length - 1; k >= 0; k--) { var _id = order[k]; if (_id.substring(0, this.root[i].length + 1) !== this.root[i] + '.') { order.splice(k, 1); } } // fill all missing ids into order for (var id in this.enums) { var _id = this.enums[id]._id; if (_id.substring(0, this.root[i].length + 1) === this.root[i] + '.') { if (order.indexOf(_id) === -1) order.push(_id); } } // build content for (var k = 0; k < order.length; k++) { var _id = order[k]; common = this.objects[_id] ? this.objects[_id].common : null; if (!common) { console.error('Invalid object "' + _id + '" '); continue; } // hide if invisible if (!this.editMode && common && common.mobile && common.mobile[this.user] && common.mobile[this.user].visible === false) continue; name = common.mobile && common.mobile[this.user] ? common.mobile[this.user].name || common.name || _id : common.name || _id; menu += '
  • ' + _(name) + '
  • '; } name = rCommon.mobile && rCommon.mobile[this.user] ? rCommon.mobile[this.user].name || rCommon.name || this.root[i] : rCommon.name || this.root[i]; var page = '
    ' + // header '
    ' + '

    ' + _(name) + '

    ' + ' ' + '
    ' + // content '
    ' + ' ' + '
    ' + // footer '
    ' + '
    ' + '
      ' + navbar + '
    ' + '
    ' + '
    ' + '
    '; $('body').append(page); } var url = $.mobile.path.parseUrl(location.href); // this is a bug. If use all together something goes wrong and string will not be decoded var pageId = decodeURIComponent(url.hash.toString()); pageId = pageId.substring(1); pageId = decodeURIComponent(pageId); pageId = pageId.replace(/&.*$/, ''); this.activePage = pageId || this.objectId2htmlId(this.root[0]); this.renderPage(this.activePage); this.renderIndicatorSettings(); $.mobile.initializePage(); var that = this; setTimeout(function () { $.mobile.navigate((!url.hash || url.hash === '#') ? '#' + that.objectId2htmlId(that.root[0]) : url.hash); }, 500); $('#nav-panel').panel(); // manage enumerations this.renderEditButtons(); // init all indicator settings $('.mobile-indicator-settings').change(function () { var ind = $(this).data('name'); that.config.indicators[ind] = $(this).prop('checked'); that.saveConfig(); // destroy all second level pages to re-render the indicators $('div[data-role="page"]').each(function () { if ($(this).attr('id').split('*').length > 2) { $(this).remove(); } }); }); }, renderInfoPage: function () { if (this.editMode) { $('.mobile-noedit').hide(); $('.mobile-edit').show(); $('.mobile-refresh').hide(); } else { $('.mobile-noedit').show(); $('.mobile-edit').hide(); $('.mobile-refresh').show(); } $('#info').show(); var theme = this.config.theme || 'light'; setTimeout(function () { $('.mobile-theme-selector').each(function () { if ($(this).data('theme') === theme) { $(this).parent().addClass('ui-btn-active'); } else { $(this).parent().removeClass('ui-btn-active'); } }); }, 500); }, htmlId2objectId: function(id) { return id.replace(/\*/g, '.').replace(/§§/g, ' '); }, objectId2htmlId: function(id) { return id.replace(/\./g, '*').replace(/\s/g, '§§'); }, convertValue: function (id, val) { if (typeof val === 'string') { if (val === 'true') { val = true; } else if (val === 'false') { val = false; } else { var f = parseFloat(val); if (f.toString() === val) val = f; } } return val; }, renderPage: function (id) { if (!id) { console.log('no id given'); return; } if (id === 'info') { return this.renderInfoPage(); } else { $('#info').hide(); } if ($('div[id="' + id + '"]').html()) { console.log(id + ' already rendered'); $('div[id="' + id + '"]').trigger('create'); return; } var name; var parentId; var parentName = id.split('*'); id = this.htmlId2objectId(id); parentName.pop(); parentName = parentName.join('.'); var common = this.enums[id] ? this.enums[id].common : null; if (this.enums[parentName]) { if (!common) debugger; parentId = parentName; parentName = (this.enums[parentId].common.mobile && this.enums[parentId].common.mobile[this.user]) ? this.enums[parentId].common.mobile[this.user].name || this.enums[parentId].common.name : this.enums[parentId].common.name; name = (common && common.mobile && common.mobile[this.user]) ? common.mobile[this.user].name || common.name || id : common.name || id; } else { parentId = ''; name = ''; } var hrefId = this.objectId2htmlId(id); // header var page = '
    ' + // responsive-grid '
    ' + ' ' + ' ' + _(parentName) + '' + '

    ' + _(name) + '

    ' + ' ' + '
    '; // content page += '
    ' + ' ' + '
    '; page += '
    '; if (this.panelActive !== parentId) { this.panelActive = parentId; // build sidebar var members = this.enums[parentId] && this.enums[parentId].children ? this.enums[parentId].children : null; var panel = ''; if (members) { var order = (this.enums[parentId].common.mobile && this.enums[parentId].common.mobile[this.user]) ? this.enums[parentId].common.mobile[this.user].order || [] : members; // check if all members are still in order for (var i = 0; i < members.length; i++) { if (order.indexOf(members[i]) === -1) order.push(members[i]); } // build content for (var k = 0; k < order.length; k++) { var _id = order[k]; var common = this.objects[_id] ? this.objects[_id].common : null; // hide if invisible if (!this.editMode && common && common.mobile && common.mobile[this.user] && common.mobile[this.user].visible === false) continue; var name = common.mobile && common.mobile[this.user] ? common.mobile[this.user].name || common.name || _id : common.name || _id; panel +='
  • '; panel +=' ' + _(name) + ''; panel +='
  • '; } } $('#nav-panel-ul').html(panel); try { $('#nav-panel-ul').listview('refresh'); } catch (e) { $('#nav-panel-ul').listview(); } } var $page = $(page); $('body').append($page); var that = this; $page.find('.nav-panel-btn').click(function () { $('#nav-panel').show().panel('open'); }); if (!this.enums[id] || !this.enums[id].common) { return; } var members = this.enums[id].common.members || []; var $ul = $('div[id="' + this.objectId2htmlId(id) + '"] ul'); $ul.listview(); if (members) { var order = (this.enums[id].common.mobile && this.enums[id].common.mobile[this.user]) ? this.enums[id].common.mobile[this.user].order || [] : members; // check if all members are still in order for (var i = 0; i < members.length; i++) { if (order.indexOf(members[i]) === -1) order.push(members[i]); } // check if all orders are still in members for (var i = order.length - 1; i >= 0; i--) { if (members.indexOf(order[i]) === -1) { order.splice(i, 1); } } for (var i = 0; i < order.length; i++) { var children = this.objects[order[i]] ? this.objects[order[i]].children : undefined; this.conn.getObject(order[i], !this.refresh, function (err, obj) { if (err) console.error(err); if (obj) { obj.children = children; that.objects[obj._id] = obj; that.renderWidget(obj, $ul, that.enums[id].common.name); } }); } } if (this.updateStates.length) { for (var i = 0; i < this.updateStates.length; i++) { this.updateState(this.updateStates[i], true); } this.updateStates = []; } // read states if (this.queueStates.length) { this.conn.getStates(this.queueStates, function (err, states) { for (var id in states) { that.states[id] = states[id]; that.updateState(id, true); } that.queueStates = []; }); } // on change $('.mobile-control').unbind('change').change(function (e) { if ($(this).prop('tagName') !== 'SELECT') return; var id = $(this).data('mobile-id'); var val = that.convertValue(id, $(this).val()); if (id && that.states[id].val !== val) { that.conn.setState(id, val); } }).unbind('click').click(function (e) { if ($(this).prop('tagName') === 'SELECT') return; var id = $(this).data('mobile-id'); if ($(this).attr('type') === 'button') { if ($(this).data('type') === 'set') { var val = that.convertValue(id, $('.mobile-value[data-mobile-id="' + id + '"]').val()); if (id) that.conn.setState(id, val); } else { if (id) that.conn.setState(id, true); } } else { var val = that.convertValue(id, $(this).val()); if (id && that.states[id].val !== val) { that.conn.setState(id, val); } } }); setTimeout(function () { $('.pages').unbind('swipeleft').on('swipeleft', function(){ $('#nav-panel').panel('close'); }).unbind('swiperight').on('swiperight', function(){ $('#nav-panel').panel('open'); }); // set slider width $('select[data-role="slider"]').each(function () { $(this).next().css('width', $(this).data('width')); }); $('select[data-role="select"]').each(function () { $(this).prev().removeClass('ui-body-inherit'); $(this).on('change', function () { $(this) .prev() .removeClass('ui-body-inherit ui-focus') .parent() .removeClass('ui-body-inherit ui-focus'); }); }); $('.mobile-control').on('slidestop stop', function () { var id = $(this).data('mobile-id'); if (id) { var val = that.convertValue(id, $(this).val()); if (that.states[id].val != val) { that.states[id].lc = (new Date()).getTime(); that.states[id].val = val; } that.states[id].ack = false; that.states[id].ts = (new Date()).getTime(); that.conn.setState(id, that.states[id].val); } }); }, 500); }, destroyPage: function (id) { var that = this; // do not destroy first level pages if (!this.erasePage[id] && id.split('*').length > 2) { console.log('Add to destroy: ' + id); this.erasePage[id] = setTimeout(function () { delete that.erasePage[id]; if ($.mobile.activePage.attr('id') !== id) { console.log('Destroy page: ' + id); $('div[id="' + id + '"]').remove(); } else { console.log('Trying to destroy active page!'); } }, 15000); } }, detectHiddenStates: function (id) { var $elem = $('.mobile-checkbox-visibility[data-edit-id="' + id + '"]'); if (this.objects[id] && this.objects[id].common && this.objects[id].common.mobile && this.objects[id].common.mobile[this.user] && this.objects[id].common.mobile[this.user].visible) { var isAllInvisible = true; if (!this.objects[id].children) { console.error(id); return; } for (var i = 0; i < this.objects[id].children.length; i++) { var cid = this.objects[id].children[i]; if (this.objects[cid] && this.objects[cid].common && this.objects[cid].common.mobile && this.objects[cid].common.mobile[this.user] && this.objects[cid].common.mobile[this.user].visible) { isAllInvisible = false; break; } } if (isAllInvisible) { $elem.prop('checked', false); this.objects[id].common.mobile[this.user].visible = false; $elem.parent().addClass('mobile-invisible'); this.saveSettings(id); } } if (this.objects[id] && this.objects[id].common && this.objects[id].common.mobile && this.objects[id].common.mobile[this.user] && !this.objects[id].common.mobile[this.user].visible) { $elem.parent().parent().find('.mobile-edit-state').hide(); } }, detectAllHiddenStates: function () { var that = this; $('.mobile-edit-channel').each(function () { var id = $(this).find('div').data('edit-id'); that.detectHiddenStates(id, $(this)); }); }, compareVersion: function (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; }, calcChildren: function (objs) { var ids = []; var indicators = {}; for (var ob in objs) { if (!objs.hasOwnProperty(ob)) continue; ids.push(ob); if (!objs[ob]) { console.log('Please sync project, because object "' + ob + '" does not exist anymore'); continue; } if (objs[ob].common && objs[ob].common.role && this.ignoreIndicators.indexOf(objs[ob].common.role) === -1 && objs[ob].common.role.match(/^indicator\./)) { var name = ob.split('.').pop(); if (!indicators[name]) indicators[name] = this.config.indicators[name] === undefined ? true : this.config.indicators[name]; } } this.config.indicators = indicators; ids.sort(); for (var i = 0; i < ids.length; i++) { if (!objs[ids[i]]) continue; if (objs[ids[i]].type === 'device' || objs[ids[i]].type === 'channel') { var _id = ids[i] + '.'; var parts = ids[i].split('.'); var j = i + 1; var len = _id.length; while (j < ids.length && ids[j].substring(0, len) === _id) { var ps = ids[j].split('.'); if (ps.length === parts.length + 1) { objs[ids[i]].children = objs[ids[i]].children || []; if (objs[ids[i]].children.indexOf(ids[j]) === -1) { objs[ids[i]].children.push(ids[j]); } } j++; } } } return objs; }, loadStyle: function (theme) { theme = theme || this.config.theme || 'light'; $('#css-mobile').attr('href', 'lib/css/' + theme + '/jquery.mobile.min.css'); $('#css-mobile-icons').attr('href', 'lib/css/' + theme + '/jquery.mobile.icons.min.css'); } }; function onEditName(e) { e.stopPropagation(); var that = mobile; var id = $(this).data('edit-id'); //$(this).parent().append(''); var name; if (that.objects[id].common.mobile && that.objects[id].common.mobile[that.user] && that.objects[id].common.mobile[that.user].name) { name = that.objects[id].common.mobile[that.user].name; } else { name = that.objects[id].common.name; } $('#name_edit').val(name).data('id', id); $('#popupEditName').popup('open'); setTimeout(function () { $('#name_edit').focus(); }, 200); } function onStateVisibility(e) { e.stopPropagation(); var that = mobile; var id = $(this).data('edit-id'); var checked = $(this).html(); checked = !checked; $(this).html(checked ? '✓' : ''); if (!id || !that.objects[id]) { console.error('Cannot find object ' + id); return; } if (that.objects[id].common && that.objects[id].common.mobile && that.objects[id].common.mobile[that.user] && that.objects[id].common.mobile[that.user].visible !== checked) { that.objects[id].common.mobile[that.user].visible = checked; that.saveSettings(id); } if (that.objects[id].common && that.objects[id].common.mobile && that.objects[id].common.mobile[that.user] && that.objects[id].common.mobile[that.user].visible) { $(this).parent().removeClass('mobile-invisible'); var $states; if ($(this).parent().hasClass('mobile-edit-channel')) { $states = $(this).parent().parent().find('.mobile-edit-state').show(); } // at least one state of the channel must be visible if ($states && $states.length) { var oneVisible = false; $states.each(function () { var $input = $(this).find('div'); var cid = $input.data('edit-id'); if (that.objects[cid].common.mobile[that.user].visible) { oneVisible = true; return false; } }); if (!oneVisible) { $states = $($states[0]); var $input = $states.find('div'); var cid = $input.data('edit-id'); if (!that.objects[cid].common.mobile[that.user].visible) { // show only one element of channel that.objects[cid].common.mobile[that.user].visible = true; that.saveSettings(cid); $input.html('✓'); $states.removeClass('mobile-invisible'); } } } } else { $(this).parent().addClass('mobile-invisible'); if ($(this).parent().hasClass('mobile-edit-channel')) { $(this).parent().parent().find('.mobile-edit-state').hide(); } // try to find some in device itself var parent = id.split('.'); parent.pop(); parent = parent.join('.'); if (that.objects[parent]) that.detectHiddenStates(parent); } } function onTypeChanged(e) { e.stopPropagation(); var that = mobile; var id = $(this).data('edit-id'); var val = $(this).val(); if (!id || !that.objects[id]) { console.error('Cannot find object ' + id); return; } if (that.objects[id].common && that.objects[id].common.mobile && that.objects[id].common.mobile[that.user] && that.objects[id].common.mobile[that.user].type !== val) { that.objects[id].common.mobile[that.user].type = val; that.saveSettings(id); } } function onSubEnumVisibility(e) { var that = mobile; e.stopPropagation(); var id = $(this).data('edit-id'); var checked = $(this).html(); checked = !checked; $(this).html(checked ? '✓' : ''); if (that.objects[id].common.mobile[that.user].visible !== checked) { that.objects[id].common.mobile[that.user].visible = checked; that.saveSettings(id); } if (that.objects[id].common.mobile[that.user].visible) { $(this).parent().removeClass('mobile-invisible'); } else { $(this).parent().addClass('mobile-invisible'); } } function onEnumVisibility(e) { var that = mobile; e.stopPropagation(); var id = $(this).data('edit-id'); var checked = $(this).html(); checked = !checked; $(this).html(checked ? '✓' : ''); if (that.objects[id].common.mobile[that.user].visible !== checked) { that.objects[id].common.mobile[that.user].visible = checked; that.saveSettings(id); } if (that.objects[id].common.mobile[that.user].visible) { $(this).parent().removeClass('mobile-invisible'); } else { $(this).parent().addClass('mobile-invisible'); } } function onForward(e) { document.location.href = $(this).data('href'); e.stopPropagation(); } if ('applicationCache' in window) { window.addEventListener('load', function (e) { window.applicationCache.addEventListener('updateready', function (e) { if (window.applicationCache.status == window.applicationCache.UPDATEREADY) { try { window.applicationCache.swapCache(); } catch (ex) { console.error(ex); } window.location.reload(); } }, false); }, false); } // Start of initialisation: main () (function ($) { $(document).ready(function () { // force loading menu $('body').css('visibility', 'visible'); mobile.init(); }); })(jQuery);