UNPKG

yunkong2.admin

Version:

The adapter opens a webserver for the yunkong2 admin UI.

1,237 lines (1,130 loc) 175 kB
/* global jQuery */ /* global document */ /* jshint -W097 */ /* jshint strict: false */ /* MIT, Copyright 2014-2018 bluefox <dogafox@gmail.com>, soef <soef@gmx.net> version: 1.1.6 (2018.08.03) To use this dialog as standalone in yunkong2 environment include: <link type="text/css" rel="stylesheet" href="lib/css/redmond/jquery-ui.min.css"> <link rel="stylesheet" type="text/css" href="lib/css/fancytree/ui.fancytree.min.css"/> <script type="text/javascript" src="lib/js/jquery-1.11.1.min.js"></script> <script type="text/javascript" src="lib/js/jquery-ui-1.10.3.full.min.js"></script> <script type="text/javascript" src="lib/js/jquery.fancytree-all.min.js"></script> <script type="text/javascript" src="js/translate.js"></script> <script type="text/javascript" src="js/words.js"></script><!--this file must be after translate.js --> <script type="text/javascript" src="js/selectID.js"></script> <script src="lib/js/socket.io.js"></script> <script src="/_socket/info.js"></script> To use as part, just <link rel="stylesheet" type="text/css" href="lib/css/fancytree/ui.fancytree.min.css"/> <script type="text/javascript" src="lib/js/jquery.fancytree-all.min.js"></script> <script type="text/javascript" src="js/selectID.js"></script> Interface: + init(options) - init select ID dialog. Following options are supported { currentId: '', // Current ID or empty if nothing preselected objects: null, // All objects that should be shown. It can be empty if connCfg used. getObjects: null, // null or function to read all objects anew on refresh (because of subscripitons): funsubscriptionsjects) {} states: null, // All states of objects. It can be empty if connCfg used. If objects are set and no states, states will no be shown. filter: null, // filter imgPath: 'lib/css/fancytree/', // Path to images device.png, channel.png and state.png connCfg: null, // configuration for dialog, ti read objects itself: {socketUrl: socketUrl, socketSession: socketSession} onSuccess: null, // callback function to be called if user press "Select". Can be overwritten in "show" - function (newId, oldId, newObj) onChange: null, // called every time the new object selected - function (newId, oldId, newObj) noDialog: false, // do not make dialog stats: false, // show objects statistics noMultiselect: false, // do not make multiselect useValues: false, // show button to toggle objects<=>values buttons: null, // array with buttons, that should be shown in last column // if array is not empty it can has following fields // [{ // text: false, // same as jquery button // icons: { // same as jquery bdata.columnsutton // primary: 'ui-icon-gear' // }, // click: function (id) { // // do on click // }, // match: function (id) { // // you have here object "this" pointing to $('button') // }, // width: 26, // same as jquery button // height: 20 // same as jquery button // }], panelButtons: null, // array with buttons, that should be shown at the top of dialog (near expand all) list: false, // tree view or list view name: null, // name of the dialog to store filter settings noCopyToClipboard: false, // do not show button for copy to clipboard root: null, // root node, e.g. "script.js" useNameAsId: false, // use name of object as ID noColumnResize: false, // do not allow column resize firstMinWidth: null, // width if ID column, default 400 showButtonsForNotExistingObjects: false, webServer: null, // link to webserver, by default ":8082" filterPresets: null, // Object with predefined filters, eg {role: 'level.dimmer'} or {type: 'state'} roleExactly: false, // If the role must be equal or just content the filter value sortConfig: { statesFirst: true, // Show states before folders ignoreSortOrder: false // Ignore standard sort order of fancytree }, texts: { select: 'Select', cancel: 'Cancel', all: 'All', id: 'ID', name: 'Name', role: 'Role', type: 'Type', room: 'Room', 'function': 'Function', enum: 'Members', value: 'Value', selectid: 'Select ID', from: 'From', user: 'user', lc: 'Last changed', ts: 'Time stamp', ack: 'Acknowledged', expand: 'Expand all nodes', collapse: 'Collapse all nodes', refresh: 'Rebuild tree', edit: 'Edit', ok: 'Ok', push: 'Trigger event' wait: 'Processing...', list: 'Show list view', tree: 'Show tree view', selectAll: 'Select all', unselectAll: 'Unselect all', invertSelection: 'Invert selection', copyToClipboard: 'Copy to clipboard', expertMode: 'Toggle expert mode', button: 'Settings', noData: 'No data', Objects: 'Objects', States: 'States', toggleValues: 'Toggle states view' }, columns: ['image', 'name', 'type', 'role', 'enum', 'room', 'function', 'value', 'button', 'value.val', 'value.ts', 'value.lc', 'value.from', 'value.q'], // some elements of columns could be an object {name: field, data: function (id, name){}, title: function (id, name) {}} widths: null, // array with width for every column editEnd: null, // function (id, newValues) for edit lines (only id and name can be edited) editStart: null, // function (id, $inputs) called after edit start to correct input fields (inputs are jquery objects), zindex: null, // z-index of dialog or table customButtonFilter: null, // if in the filter over the buttons some specific button must be shown. It has type like {icons:{primary: 'ui-icon-close'}, text: false, callback: function ()} expertModeRegEx: null // list of regex with objects, that will be shown only in expert mode, like /^system\.|^yunkong2\.|^_|^[\w-]+$|^enum\.|^[\w-]+\.admin/ quickEdit: null, // list of fields with edit on click. Elements can be just names from standard list or objects like: // {name: 'field', options: {a1: 'a111_Text', a2: 'a22_Text'}}, options can be a function (id, name), that give back such an object quickEditCallback: null, // function (id, attr, newValue, oldValue), readyCallback: null // called when objects and states are read from server (only if connCfg is not null). function (err, objects, states) expandedCallback: null, // called when some node was expanded. function (id, childrenCount, statesCount) collapsedCallback: null, // called when some node was expanded. function (id, childrenCount, statesCount) } + show(currentId, filter, callback) - all arguments are optional if set by "init". Callback is like function (newId, oldId) {}. If multiselect, so the arguments are arrays. + clear() - clear object tree to read and build anew (used only if objects set by "init") + getInfo(id) - get information about ID + getTreeInfo(id) - get {id, parent, children, object} + state(id, val) - update states in tree + object(id, obj) - update object info in tree + reinit() - draw tree anew filter is like: common: { history: true } or type: "state" */ var addAll2FilterCombobox = false; function tdp(x, decimals) { // TODO support of US format too return isNaN(x) ? '' : x.toFixed(decimals || 0).replace('.', ',').replace(/\B(?=(\d{3})+(?!\d))/g, '.'); } function removeImageFromSettings(data) { if (!data || !data.columns) return; var idx = data.columns.indexOf('image'); if (idx >= 0) data.columns.splice(idx, 1); } var lineIndent = '5px'; function span(txt, attr) { //if (txt === undefined) txt = ''; //return txt; var style = 'padding-left: ' + lineIndent + ';'; if (attr) style += attr; return '<span style="' + style + '">' + txt + '</span>'; } function filterChanged(e) { var $e = $(e); var val = $e.val(); var td = $e.parent(); if (val) { td.addClass('filter-active'); } else { td.removeClass('filter-active'); } } (function ($) { 'use strict'; if ($.fn.selectId) return; var isMaterial; function getNameObj(obj, id) { if (obj && obj.common) { return getName(obj.common.name || (id || '').split('.').pop()); } else { return (id || '').split('.').pop(); } } function formatDate(dateObj) { //return dateObj.getFullYear() + '-' + // ('0' + (dateObj.getMonth() + 1).toString(10)).slice(-2) + '-' + // ('0' + (dateObj.getDate()).toString(10)).slice(-2) + ' ' + // ('0' + (dateObj.getHours()).toString(10)).slice(-2) + ':' + // ('0' + (dateObj.getMinutes()).toString(10)).slice(-2) + ':' + // ('0' + (dateObj.getSeconds()).toString(10)).slice(-2); // Following implementation is 5 times faster if (!dateObj) return ''; var text = dateObj.getFullYear(); var v = dateObj.getMonth() + 1; if (v < 10) { text += '-0' + v; } else { text += '-' + v; } v = dateObj.getDate(); if (v < 10) { text += '-0' + v; } else { text += '-' + v; } v = dateObj.getHours(); if (v < 10) { text += ' 0' + v; } else { text += ' ' + v; } v = dateObj.getMinutes(); if (v < 10) { text += ':0' + v; } else { text += ':' + v; } v = dateObj.getSeconds(); if (v < 10) { text += ':0' + v; } else { text += ':' + v; } v = dateObj.getMilliseconds(); if (v < 10) { text += '.00' + v; } else if (v < 100) { text += '.0' + v; } else { text += '.' + v; } return text; } function filterId(data, id) { if (data.rootExp) { if (!data.rootExp.test(id)) return false; } // ignore system objects in expert mode if (data.expertModeRegEx && !data.expertMode && data.expertModeRegEx.test(id)) { return false; } // ignore exeprt objects in expert mode if (!data.expertMode && data.objects[id] && data.objects[id].common && data.objects[id].common.expert) { return; } if (data.filter) { if (data.filter.type && data.filter.type !== data.objects[id].type) return false; if (data.filter.common && data.filter.common.custom) { if (!data.objects[id].common) return false; // todo: remove history sometime 09.2016 var custom = data.objects[id].common.custom || data.objects[id].common.history; if (!custom) return false; if (data.filter.common.custom === true) { return true; } else { if (!custom[data.filter.common.custom]) return false; } } } return true; } function getExpandeds(data) { if (!data.$tree) return null; var expandeds = {}; (function getIt(nodes) { if (!Array.isArray(nodes.children)) return; for (var i = 0, len = nodes.children.length; i < len; i++) { var node = nodes.children[i]; if (node.expanded) { expandeds[node.key] = true; } getIt(node); } })(data.$tree.fancytree('getRootNode')); return expandeds; } function restoreExpandeds(data, expandeds) { if (!expandeds || !data.$tree) return; (function setIt(nodes) { if (!Array.isArray(nodes.children)) return; for (var i = 0, len = nodes.children.length; i < len; i++) { var node = nodes.children[i]; if (expandeds[node.key]) { try { node.setExpanded(); } catch (e) { console.log('Cannot expand: ' + e); } //node.setActive(); } setIt(node); } })(data.$tree.fancytree('getRootNode')); expandeds = null; } function sortTree(data) { var objects = data.objects; var checkStatesFirst; switch (data.sortConfig.statesFirst) { case undefined: checkStatesFirst = function () { return 0 }; break; case true: checkStatesFirst = function (child1, child2) { return ((~~child2.folder) - (~~child1.folder))}; break; case false: checkStatesFirst = function (child1, child2) { return ((~~child1.folder) - (~~child2.folder))}; break; } // function compAdapterAndInstance(c1, c2) { // var s1 = c1.key.substr(0, c1.key.lastIndexOf('.')); // var s2 = c2.key.substr(0, c2.key.lastIndexOf('.')); // // if (s1 > s2) return 1; // if (s1 < s2) return -1; // return 0; // } function sortByName(child1, child2) { var ret = checkStatesFirst(child1, child2); if (ret) return ret; var o1 = objects[child1.key], o2 = objects[child2.key]; if (o1 && o2) { var c1 = o1.common, c2 = o2.common; if (c1 && c2) { // var s1 = child1.key.substr(0, child1.key.lastIndexOf('.')); // faster than regexp. // var s2 = child2.key.substr(0, child2.key.lastIndexOf('.')); // if (s1 > s2) return 1; // if (s1 < s2) return -1; if (!data.sortConfig.ignoreSortOrder && c1.sortOrder && c2.sortOrder) { if (c1.sortOrder > c2.sortOrder) return 1; if (c1.sortOrder < c2.sortOrder) return -1; return 0; } var name1; var name2; if (c1.name) { name1 = c1.name; if (typeof name1 === 'object') { name1 = (name1[systemLang] || name1.en).toLowerCase(); } else { name1 = name1.toLowerCase(); } } else { name1 = child1.key; } if (c2.name) { name2 = c2.name; if (typeof name2 === 'object') { name2 = (name2[systemLang] || name2.en).toLowerCase(); } else { name2 = name2.toLowerCase(); } } else { name2 = child1.key; } if (name1 > name2) return 1; if (name1 < name2) return -1; } } if (child1.key > child2.key) return 1; if (child1.key < child2.key) return -1; return 0; } function sortByKey(child1, child2) { var ret = checkStatesFirst(child1, child2); if (ret) return ret; if (!data.sortConfig.ignoreSortOrder) { var o1 = objects[child1.key], o2 = objects[child2.key]; if (o1 && o2) { var c1 = o1.common, c2 = o2.common; if (c1 && c2 && c1.sortOrder && c2.sortOrder) { // var s1 = child1.key.substr(0, child1.key.lastIndexOf('.')); // faster than regexp. // var s2 = child2.key.substr(0, child2.key.lastIndexOf('.')); // if (s1 > s2) return 1; // if (s1 < s2) return -1; if (c1.sortOrder > c2.sortOrder) return 1; if (c1.sortOrder < c2.sortOrder) return -1; return 0; } } } if (child1.key > child2.key) return 1; if (child1.key < child2.key) return -1; return 0; } var sortFunc = data.sort ? sortByName : sortByKey; var sfunc = sortByKey; // sort the root always by key return (function sort(tree) { if (!tree || !tree.children) return; try { tree.sortChildren(sfunc); } catch (e) { console.log(e); } sfunc = sortFunc; for (var i=tree.children.length-1; i>=0; i--) { sort(tree.children[i]); } })(data.$tree.fancytree('getRootNode')); // var sortFunc = data.sort ? sortByName : sortByKey; // var root = data.$tree.fancytree('getRootNode'); // root.sortChildren(sortByKey, false); // return (function sort(tree) { // if (!tree) return; // for (var i=tree.children.length-1; i>=0; i--) { // var child = tree.children[i]; // if (!child) return; // child.sortChildren(sortFunc); // sort(child.children); // } // })(root.children); //data.$tree.fancytree('getRootNode').sortChildren(data.sort ? sortByName : sortByKey, true); //var tree = data.$tree.fancytree('getTree'); //var node = tree.getActiveNode(); } function getAllStates(data) { var stats = data.stats ? {objs: 0, states: 0} : null; var objects = data.objects; var isType = data.columns.indexOf('type') !== -1; var isRoom = data.columns.indexOf('room') !== -1; var isFunc = data.columns.indexOf('function') !== -1; var isRole = data.columns.indexOf('role') !== -1; var isHist = data.columns.indexOf('button') !== -1; data.tree = {title: '', children: [], count: 0, root: true}; data.roomEnums = []; data.funcEnums = []; data.ids = []; for (var id in objects) { if (!objects.hasOwnProperty(id)) continue; if (!id) { console.error('Invalid empty ID found! Please fix it'); continue; } stats && stats.objs++; if (objects[id].type === 'state') { stats && stats.states++; } else if (data.valuesActive) { continue; } if (isRoom) { if (objects[id].type === 'enum' && data.regexEnumRooms.test(id) && data.roomEnums.indexOf(id) === -1) data.roomEnums.push(id); if (objects[id].enums) { for (var e in objects[id].enums) { if (data.regexEnumRooms.test(e) && data.roomEnums.indexOf(e) === -1) { data.roomEnums.push(e); } data.objects[e] = data.objects[e] || { _id: e, common: { name: objects[id].enums[e], members: [id] } }; data.objects[e].common.members = data.objects[e].common.members || []; if (data.objects[e].common.members.indexOf(id) === -1) { data.objects[e].common.members.push(id); } } } } if (isFunc) { if (objects[id].type === 'enum' && data.regexEnumFuncs.test(id) && data.funcEnums.indexOf(id) === -1) { data.funcEnums.push(id); } if (objects[id].enums) { for (var e in objects[id].enums) { if (data.regexEnumFuncs.test(e) && data.funcEnums.indexOf(e) === -1) { data.funcEnums.push(e); } data.objects[e] = data.objects[e] || { _id: e, common: { name: objects[id].enums[e], members: [id] } }; data.objects[e].common.members = data.objects[e].common.members || []; if (data.objects[e].common.members.indexOf(id) === -1) { data.objects[e].common.members.push(id); } } } } if (isType && objects[id].type && data.types.indexOf(objects[id].type) === -1) data.types.push(objects[id].type); if (isRole && objects[id].common && objects[id].common.role) { try { var parts = objects[id].common.role.split('.'); var role = ''; for (var u = 0; u < parts.length; u++) { role += (role ? '.' : '') + parts[u]; if (data.roles.indexOf(role) === -1) data.roles.push(role); } } catch (e) { console.error('Cannot parse role "' + objects[id].common.role + '" by ' + id); } } if (isHist && objects[id].type === 'instance' && (objects[id].common.type === 'storage' || objects[id].common.supportCustoms)) { var h = id.substring('system.adapter.'.length); if (data.histories.indexOf(h) === -1) { data.histories.push(h); } } if (!filterId(data, id)) continue; treeInsert(data, id, data.currentId === id); if (objects[id].enums) { for (var ee in objects[id].enums) { if (objects[id].enums.hasOwnProperty(ee) && objects[ee] && objects[ee].common && objects[ee].common.members && objects[ee].common.members.indexOf(id) === -1) { objects[ee].common.members.push(id); } } } // fill counters if (data.expertMode) { data.ids.push(id); } } data.inited = true; data.roles.sort(); data.types.sort(); data.roomEnums.sort(); data.funcEnums.sort(); data.histories.sort(); data.ids.sort(); if (stats) { data.stats = stats; } } function treeSplit(data, id) { if (!id) return null; if (data.root) { id = id.substring(data.root.length); } var parts = id.split('.'); if (data.regexSystemAdapter.test(id)) { if (parts.length > 3) { parts[0] = 'system.adapter.' + parts[2] + '.' + parts[3]; parts.splice(1, 3); } else { parts[0] = 'system.adapter.' + parts[2]; parts.splice(1, 2); } } else if (data.regexSystemHost.test(id)) { parts[0] = 'system.host.' + parts[2]; parts.splice(1, 2); } else if (parts.length > 1 && !data.root) { parts[0] = parts[0] + '.' + parts[1]; parts.splice(1, 1); } /*if (optimized) { parts = treeOptimizePath(parts); }*/ return parts; } function _deleteTree(node, deletedNodes) { if (node.parent) { if (deletedNodes && node.id) { deletedNodes.push(node); } var p = node.parent; if (p.children.length <= 1) { _deleteTree(node.parent); } else { for (var z = 0; z < p.children.length; z++) { if (node.key === p.children[z].key) { p.children.splice(z, 1); break; } } } } else { //error } } function deleteTree(data, id, deletedNodes) { var node = findTree(data, id); if (!node) { console.log('deleteTree: Id ' + id + ' not found'); return; } _deleteTree(node, deletedNodes); } function findTree(data, id) { return (function find(tree) { if (!tree.children) return; for (var i = tree.children.length - 1; i >= 0; i--) { var child = tree.children[i]; if (id === child.key) return child; if (id.startsWith(child.key + '.')) { //if (id === child.key) return child; return find(child); } } return null; })(data.tree); } // function xfindTree(data, id) { // return _findTree(data.tree, treeSplit(data, id, false), 0); // } // function _findTree(tree, parts, index) { // var num = -1; // for (var j = 0; j < tree.children.length; j++) { // if (tree.children[j].title === parts[index]) { // num = j; // break; // } // //if (tree.children[j].title > parts[index]) break; // } // // if (num === -1) return null; // // if (parts.length - 1 === index) { // return tree.children[num]; // } else { // return _findTree(tree.children[num], parts, index + 1); // } // } /* function treeInsert(data, id, isExpanded, addedNodes) { var idArr = data.list ? [id] : treeSplit(data, id); if (!idArr) return console.error('Empty object ID!'); (function insert(tree, idx) { for ( ; idx < idArr.length; idx += 1) { for (var i = tree.children.length - 1; i >= 0; i--) { var child = tree.children[i]; if (id === child.key) return child; if (id.startsWith (child.key + '.')) { //if (id === child.key) return child; child.expanded = child.expanded || isExpanded; return insert (child, idx + 1); } } tree.folder = true; tree.expanded = isExpanded; var obj = { key: (data.root || '') + idArr.slice (0, idx + 1).join ('.'), children: [], title: idArr[idx], folder: false, expanded: false, parent: tree }; //data.objects[obj.key].node = obj; tree.children.push (obj); if (addedNodes) { addedNodes.push (obj); } tree = obj; } tree.id = id; })(data.tree, 0); } */ function treeInsert(data, id, isExpanded, addedNodes) { return _treeInsert(data.tree, data.list ? [id] : treeSplit(data, id, false), id, 0, isExpanded, addedNodes, data); } function _treeInsert(tree, parts, id, index, isExpanded, addedNodes, data) { index = index || 0; if (!parts) { console.error('Empty object ID!'); return; } var num = -1; var j; for (j = 0; j < tree.children.length; j++) { if (tree.children[j].title === parts[index]) { num = j; break; } //if (tree.children[j].title > parts[index]) break; } if (num === -1) { tree.folder = true; tree.expanded = isExpanded; var fullName = ''; for (var i = 0; i <= index; i++) { fullName += ((fullName) ? '.' : '') + parts[i]; } var obj = { key: (data.root || '') + fullName, children: [], title: parts[index], folder: false, expanded: false, parent: tree }; if (j === tree.children.length) { num = tree.children.length; tree.children.push(obj); } else { num = j; tree.children.splice(num, 0, obj); } if (addedNodes) { addedNodes.push(tree.children[num]); } } if (parts.length - 1 === index) { tree.children[num].id = id; } else { tree.children[num].expanded = tree.children[num].expanded || isExpanded; _treeInsert(tree.children[num], parts, id, index + 1, isExpanded, addedNodes, data); } } function showActive($dlg, scrollIntoView) { var data = $dlg.data('selectId'); // Select current element if (data.selectedID) { data.$tree.fancytree('getTree').visit(function (node) { if (node.key === data.selectedID) { try { node.setActive(); node.makeVisible({scrollIntoView: scrollIntoView || false}); //$(node).find('table.fancytree-ext-table tbody tr td') //xxx } catch (err) { console.error(err); } return false; } }); } } function syncHeader($dlg) { var data = $dlg.data('selectId'); if (!data) return; var $header = $dlg.find('.main-header-table'); var thDest = $header.find('>tbody>tr>th'); //if table headers are specified in its semantically correct tag, are obtained var thSrc = data.$tree.find('>tbody>tr>td'); var x, o; for (var i = 0; i < thDest.length - 1; i++) { if ((x = $(thSrc[i]).width())) { $(thDest[i]).attr('width', x); if ((o = $(thSrc[i + 1]).offset().left)) { if ((o -= $(thDest[i + 1]).offset().left)) { $(thDest[i]).attr('width', x + o); } } } } } function getName(name) { if (name && typeof name === 'object') { return name[systemLang] || name.en; } else { return name || ''; } } function findRoomsForObject(data, id, withParentInfo, rooms) { if (!id) { return []; } rooms = rooms || []; for (var i = 0; i < data.roomEnums.length; i++) { var common = data.objects[data.roomEnums[i]] && data.objects[data.roomEnums[i]].common; var name = getName(common.name); if (common.members && common.members.indexOf(id) !== -1 && rooms.indexOf(name) === -1) { if (!withParentInfo) { rooms.push(name); } else { rooms.push({name: name, origin: id}); } } } var parts = id.split('.'); parts.pop(); id = parts.join('.'); if (data.objects[id]) findRoomsForObject(data, id, withParentInfo, rooms); return rooms; } function findRoomsForObjectAsIds(data, id, rooms) { if (!id) { return []; } rooms = rooms || []; for (var i = 0; i < data.roomEnums.length; i++) { var common = data.objects[data.roomEnums[i]] && data.objects[data.roomEnums[i]].common; if (common && common.members && common.members.indexOf(id) !== -1 && rooms.indexOf(data.roomEnums[i]) === -1) { rooms.push(data.roomEnums[i]); } } return rooms; } function findFunctionsForObject(data, id, withParentInfo, funcs) { if (!id) { return []; } funcs = funcs || []; for (var i = 0; i < data.funcEnums.length; i++) { var common = data.objects[data.funcEnums[i]] && data.objects[data.funcEnums[i]].common; var name = getName(common.name); if (common && common.members && common.members.indexOf(id) !== -1 && funcs.indexOf(name) === -1) { if (!withParentInfo) { funcs.push(name); } else { funcs.push({name: name, origin: id}); } } } var parts = id.split('.'); parts.pop(); id = parts.join('.'); if (data.objects[id]) findFunctionsForObject(data, id, withParentInfo, funcs); return funcs; } function findFunctionsForObjectAsIds(data, id, funcs) { if (!id) { return []; } funcs = funcs || []; for (var i = 0; i < data.funcEnums.length; i++) { var common = data.objects[data.funcEnums[i]] && data.objects[data.funcEnums[i]].common; if (common && common.members && common.members.indexOf(id) !== -1 && funcs.indexOf(data.funcEnums[i]) === -1) { funcs.push(data.funcEnums[i]); } } return funcs; } function clippyCopy(e) { var $input = $('<input>'); $(this).append($input); $input.val($(this).parent().data('clippy')); $input.trigger('select'); document.execCommand('copy'); $input.remove(); e.preventDefault(); e.stopPropagation(); } function editValueDialog() { var data = $(this).data('data'); var $parent = $(this).parent(); var value = $parent.data('clippy'); var id = $parent.data('id'); var $dlg = $('#dialog-value-edit'); if (typeof M !== 'undefined' && $dlg.length) { $dlg.find('textarea').val(value); $dlg.find('input[type="checkbox"]').prop('checked', false); // workaround for materialize checkbox problem $dlg.find('input[type="checkbox"]+span').off('click').on('click', function () { var $input = $(this).prev(); if (!$input.prop('disabled')) { $input.prop('checked', !$input.prop('checked')).trigger('change'); } }); $dlg.find('.btn-set').off('click').on('click', function () { var val = $dlg.find('textarea').val(); var ack = $dlg.find('input[type="checkbox"]').prop('checked'); if (val !== value || ack) { data.quickEditCallback(id, 'value', val, value, ack); value = '<span style="color: darkviolet; width: 100%;">' + value + '</span>'; $parent.html(value); } $dlg.modal('close'); }); $dlg.modal().modal('open'); } else { $('<div style="position: absolute;left: 5px; top: 5px; right: 5px; bottom: 5px; border: 1px solid #CCC;">' + '<textarea style="margin: 0; border: 0;background: white; width: 100%; height: calc(100% - 50px); resize: none;" ></textarea><br>' + '<input type="checkbox" /><span>' + _('ack') + '</span></div>') .dialog({ autoOpen: true, modal: true, title: data.texts.edit, width: '50%', height: 200, open: function (event) { $(this).find('textarea').val(value); $(this).find('input[type="checkbox"]').prop('checked', false); $(event.target).parent().find('.ui-dialog-titlebar-close .ui-button-text').html(''); }, buttons: [ { text: data.texts.select, click: function () { var val = $(this).find('textarea').val(); var ack = $(this).find('input[type="checkbox"]').prop('checked'); if (val !== value || ack) { data.quickEditCallback(id, 'value', val, value, ack); value = '<span style="color: darkviolet; width: 100%;">' + value + '</span>'; $parent.html(value); } $(this).dialog('close').dialog('destroy').remove(); } }, { text: data.texts.cancel, click: function () { $(this).dialog('close').dialog('destroy').remove(); } } ] }); } } function editEnumsDialog() { var data = $(this).data('data'); var $parent = $(this).parent(); var id = $parent.data('id'); var oldVal = $parent.data('old-value'); var $dlg = $('#dialog-enum-edit'); var attr = $parent.data('name'); if (typeof M !== 'undefined' && $dlg.length) { var funcs = []; var text = ''; $dlg.find('.name').html(getNameObj(data.objects[id], id)); var enums = attr === 'function' ? data.funcEnums : data.roomEnums; for (var i = 0; i < enums.length; i++) { var common = data.objects[enums[i]] && data.objects[enums[i]].common; var name = getName(common.name); if (funcs.indexOf(name) === -1) { funcs.push(name); var checked = common && common.members && common.members.indexOf(id) !== -1; text += '<li class="collection-item">' + getSelectIdIcon(data, data.objects[enums[i]]) + ' <span class="title">' + name + '<span class="dialog-enum-list-id">' + enums[i] + '</span></span>' + ' ' + ' <label class="secondary-content">' + ' <input class="filled-in" type="checkbox" ' + (checked ? 'checked' : '') + ' data-id="' + enums[i] + '" data-name="' + name + '"/>' + ' <span></span>' + ' </label>' + '</li>'; } } $dlg.find('.collection').html(text); // workaround for materialize checkbox problem $dlg.find('input[type="checkbox"]').off('click').on('click', function () { var $input = $(this).prev(); if (!$input.prop('disabled')) { $input.prop('checked', !$input.prop('checked')).trigger('change'); } }); $dlg.find('.btn-set').off('click').on('click', function () { var checks = $dlg.find('input[type="checkbox"]'); var val = []; var names = []; checks.each(function () { if ($(this).prop('checked')) { val.push($(this).data('id')); names.push($(this).data('name')) } }); if (JSON.stringify(oldVal) !== JSON.stringify(val)) { data.quickEditCallback(id, attr, val, oldVal); var value = '<span style="color: darkviolet; width: 100%;">' + names.join(', ') + '</span>'; $parent.html(value); } $dlg.modal('close'); }); $dlg.modal().modal('open'); } else { // todo } } function getSelectIdIcon(data, obj, key) { var icon = ''; var alt = ''; var _id_ = 'system.adapter.' + key; if (key && data.objects[_id_] && data.objects[_id_].common && data.objects[_id_].common.icon) { // if not BASE64 if (!data.objects[_id_].common.icon.match(/^data:image\//)) { if (data.objects[_id_].common.icon.indexOf('.') !== -1) { icon = '/adapter/' + data.objects[_id_].common.name + '/' + data.objects[_id_].common.icon; } else { return '<i class="material-icons iob-list-icon">' + data.objects[_id_].common.icon + '</i>'; } } else { icon = data.objects[_id_].common.icon; } } else if (obj && obj.common) { if (obj.common.icon) { if (!obj.common.icon.match(/^data:image\//)) { if (obj.common.icon.indexOf('.') !== -1) { var instance; if (obj.type === 'instance') { icon = '/adapter/' + obj.common.name + '/' + obj.common.icon; } else if (key && key.match(/^system\.adapter\./)) { instance = key.split('.', 3); if (obj.common.icon[0] === '/') { instance[2] += obj.common.icon; } else { instance[2] += '/' + obj.common.icon; } icon = '/adapter/' + instance[2]; } else { instance = key.split('.', 2); if (obj.common.icon[0] === '/') { instance[0] += obj.common.icon; } else { instance[0] += '/' + obj.common.icon; } icon = '/adapter/' + instance[0]; } } else { return '<i class="material-icons iob-list-icon">' + obj.common.icon + '</i>'; } } else { // base 64 image icon = obj.common.icon; } } else if (obj.type === 'device') { icon = data.imgPath + 'device.png'; alt = 'device'; } else if (obj.type === 'channel') { icon = data.imgPath + 'channel.png'; alt = 'channel'; } else if (obj.type === 'state') { icon = data.imgPath + 'state.png'; alt = 'state'; } } if (icon) { return '<img class="iob-list-icon" src="' + icon + '" alt="' + alt + '"/>'; } else { return ''; } } function clippyShow(e) { var text; var data; if ($(this).hasClass('clippy') && !$(this).find('.clippy-button').length) { data = data || $(this).data('data'); text = '<button class="clippy-button ui-button ui-widget ui-state-default ui-corner-all ui-button-icon-only td-button m" ' + 'role="button" title="' + data.texts.copyToClipboard + '">'; if (typeof M !== 'undefined') { text += '<i class="material-icons tiny">content_copy</i>' } else { text += '<span class="ui-button-icon-primary ui-icon ui-icon-clipboard"></span>' } text += '</button>'; $(this).append(text); var $clippy = $(this).find('.clippy-button'); $clippy.on('click', clippyCopy); } if ($(this).hasClass('edit-dialog') && !$(this).find('.edit-dialog-button').length) { data = data || $(this).data('data'); text = '<button class="edit-dialog-button ui-button ui-widget ui-state-default ui-corner-all ui-button-icon-only td-button m" ' + 'role="button" title="' + (data.texts.editDialog || '') + '">'; if (typeof M !== 'undefined') { text += '<i class="material-icons tiny">edit</i>' } else { text += '<span class="ui-button-icon-primary ui-icon ui-icon-pencil"></span>'; } text += '</button>'; $(this).append(text); var name = $(this).data('name'); if (name === 'function' || name === 'room') { $(this).find('.edit-dialog-button').on('click', editEnumsDialog).data('data', data); } else { $(this).find('.edit-dialog-button').on('click', editValueDialog).data('data', data); } } } function clippyHide(e) { $(this).find('.clippy-button').remove(); $(this).find('.edit-dialog-button').remove(); } function installColResize(data, $dlg) { if (data.noColumnResize || !$.fn.colResizable) return; if (data.$tree.is(':visible')) { data.$tree.colResizable({ liveDrag: true, //resizeMode: 'flex', resizeMode: 'fit', minWidth: 50, partialRefresh: true, marginLeft: 5, postbackSafe: true, onResize: function (/* event */) { syncHeader($dlg); } }); syncHeader($dlg); } else { setTimeout(function () { installColResize(data, $dlg); }, 400) } } function getStates(data, id) { var states; if (data.objects[id] && data.objects[id].common && data.objects[id].common.states) { states = data.objects[id].common.states; } if (states) { if (typeof states === 'string' && states[0] === '{') { try { states = JSON.parse(states); } catch (ex) { console.error('Cannot parse states: ' + states); states = null; } } else // if old format val1:text1;val2:text2 if (typeof states === 'string') { var parts = states.split(';'); states = {}; for (var p = 0; p < parts.length; p++) { var s = parts[p].split(':'); states[s[0]] = s[1]; } } } return states; } function setFilterVal(data, field, val) { if (!field) return; data.$dlg.find('.filter[data-index="' + field + '"]').val(val).trigger('change'); } function onQuickEditField(event) { var $this = $(this); var id = $this.data('id'); var attr = $this.data('name'); var data = $this.data('selectId'); var type = $this.data('type'); var innerHTML = this.innerHTML; var $parentTR = $(event.currentTarget).parent(); // actually $parentTR === $thisParent, but I dont know var $thisParent = $this.parent(); var clippy = $thisParent.hasClass('clippy'); var editDialog = $thisParent.hasClass('edit-dialog'); var options = $this.data('options'); var oldVal = $this.data('old-value'); var states = null; //var activeNode = $(this).fancytree('getTree').getActiveNode(); if (clippy) { $thisParent.removeClass('clippy'); $thisParent.find('.clippy-button').remove(); // delete clippy buttons because they overlay the edit field } if (editDialog) { $thisParent.removeClass('edit-dialog'); $thisParent.find('.edit-dialog-button').remove(); // delete edit buttons because they overlay the edit field } $thisParent.css({overflow: 'visible'}); $this.css({overflow: 'visible'}); $this.closest('td').css({overflow: 'visible'}); $this.off('cl