yunkong2.admin
Version:
The adapter opens a webserver for the yunkong2 admin UI.
1,237 lines (1,130 loc) • 175 kB
JavaScript
/* 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