fhem-tablet-ui.js 95 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543
  1. /* FHEM tablet ui */
  2. /* FHEM tablet ui */
  3. /**
  4. * UI builder framework for FHEM
  5. *
  6. * Version: 2.7.6
  7. *
  8. * Copyright (c) 2015-2018 Mario Stephan <mstephan@shared-files.de>
  9. * Under MIT License (http://www.opensource.org/licenses/mit-license.php)
  10. * https://github.com/knowthelist/fhem-tablet-ui
  11. */
  12. /* global Framework7:true, jQuery:true, Dom7:true */
  13. "use strict";
  14. // -------- Framework7---------
  15. // https://framework7.io/docs/
  16. if (typeof Framework7 === 'function') {
  17. var f7 = {
  18. ftui: new Framework7({
  19. animateNavBackIcon: true
  20. }),
  21. options: {
  22. dynamicNavbar: true,
  23. domCache: true
  24. },
  25. views: []
  26. };
  27. Dom7('.view').each(function (index) {
  28. var view = f7.ftui.addView('#' + Dom7(this)[0].id, {
  29. dynamicNavbar: true
  30. });
  31. f7.ftui.views.push(view);
  32. });
  33. f7.ftui.onPageInit('*', function (page) {
  34. ftui.log(1, 'f7: ' + page.name + ' initialized');
  35. ftui.initWidgets('.page[data-page="' + page.name + '"]');
  36. });
  37. }
  38. // -------- Widget Base---------
  39. var Modul_widget = function () {
  40. var subscriptions = {};
  41. var elements = [];
  42. function update_lock(dev, par) {
  43. $.each(['lock', 'lock-on', 'lock-off'], function (index, key) {
  44. me.elements.filterDeviceReading(key, dev, par)
  45. .each(function (idx) {
  46. var elem = $(this);
  47. var value = elem.getReading('lock').val;
  48. if (elem.matchingState('lock', value) === 'on') {
  49. elem.addClass('lock');
  50. }
  51. if (elem.matchingState('lock', value) === 'off') {
  52. elem.removeClass('lock');
  53. }
  54. });
  55. });
  56. }
  57. function update_hide(dev, par) {
  58. $.each(['hide', 'hide-on', 'hide-off'], function (index, key) {
  59. me.elements.filterDeviceReading(key, dev, par)
  60. .each(function (idx) {
  61. var elem = $(this);
  62. var value = elem.getReading('hide').val;
  63. if (elem.matchingState('hide', value) === 'on') {
  64. if (ftui.isValid(elem.data('hideparents'))) {
  65. elem.parents(elem.data('hideparents')).addClass('hide');
  66. } else {
  67. elem.addClass('hide');
  68. }
  69. }
  70. if (elem.matchingState('hide', value) === 'off') {
  71. if (ftui.isValid(elem.data('hideparents'))) {
  72. elem.parents(elem.data('hideparents')).removeClass('hide');
  73. } else {
  74. elem.removeClass('hide');
  75. }
  76. }
  77. });
  78. });
  79. }
  80. function updateHide(elem, dev, par) {
  81. $.each(['hide', 'hide-on', 'hide-off'], function (index, key) {
  82. if (elem.matchDeviceReading(key, dev, par)) {
  83. var value = elem.getReading('hide').val;
  84. if (elem.matchingState('hide', value) === 'on') {
  85. if (ftui.isValid(elem.data('hideparents'))) {
  86. elem.parents(elem.data('hideparents')).addClass('hide');
  87. } else {
  88. elem.addClass('hide');
  89. }
  90. }
  91. if (elem.matchingState('hide', value) === 'off') {
  92. if (ftui.isValid(elem.data('hideparents'))) {
  93. elem.parents(elem.data('hideparents')).removeClass('hide');
  94. } else {
  95. elem.removeClass('hide');
  96. }
  97. }
  98. }
  99. });
  100. }
  101. function updateLock(elem, dev, par) {
  102. $.each(['lock', 'lock-on', 'lock-off'], function (index, key) {
  103. if (elem.matchDeviceReading(key, dev, par)) {
  104. var value = elem.getReading('lock').val;
  105. if (elem.matchingState('lock', value) === 'on') {
  106. elem.addClass('lock');
  107. }
  108. if (elem.matchingState('lock', value) === 'off') {
  109. elem.removeClass('lock');
  110. }
  111. }
  112. });
  113. }
  114. function updateReachable(elem, dev, par) {
  115. $.each(['reachable', 'reachable-on', 'reachable-off'], function (index, key) {
  116. if (elem.matchDeviceReading(key, dev, par)) {
  117. var value = elem.getReading('reachable').val;
  118. if (elem.matchingState('reachable', value) === 'on') {
  119. elem.removeClass('unreachable');
  120. }
  121. if (elem.matchingState('reachable', value) === 'off') {
  122. elem.addClass('unreachable');
  123. }
  124. }
  125. });
  126. }
  127. function update_reachable(dev, par) {
  128. $.each(['reachable', 'reachable-on', 'reachable-off'], function (index, key) {
  129. me.elements.filterDeviceReading(key, dev, par)
  130. .each(function (idx) {
  131. var elem = $(this);
  132. var value = elem.getReading('reachable').val;
  133. if (elem.matchingState('reachable', value) === 'on') {
  134. elem.removeClass('unreachable');
  135. }
  136. if (elem.matchingState('reachable', value) === 'off') {
  137. elem.addClass('unreachable');
  138. }
  139. });
  140. });
  141. }
  142. function substitution(value, subst) {
  143. if (ftui.isValid(subst) && ftui.isValid(value)) {
  144. if ($.isArray(subst)) {
  145. for (var i = 0, len = subst.length; i < len; i += 2) {
  146. if (i + 1 < len) {
  147. value = value.replace(new RegExp(String(subst[i]), "g"), String(subst[i + 1]));
  148. }
  149. }
  150. } else if (subst.match(/^s/)) {
  151. var f = subst.substr(1, 1);
  152. var sub = subst.split(f);
  153. return (value) ? value.replace(new RegExp(sub[1], sub[3]), sub[2]) : '';
  154. } else if (subst.match(/weekdayshort/))
  155. return ftui.dateFromString(value).ee();
  156. else if (subst.match(/.*\(.*\)/))
  157. return eval('value.' + subst);
  158. }
  159. return value;
  160. }
  161. function round(value, precision) {
  162. return ($.isNumeric(value) && precision) ? ftui.round(Number(value), precision) : value;
  163. }
  164. function fix(value, len) {
  165. return ($.isNumeric(value) && len >= 0) ? Number(value).toFixed(len) : value;
  166. }
  167. function factor(value, fac) {
  168. return ($.isNumeric(value) && fac >= 0) ? Number(value) * fac : value;
  169. }
  170. function map(mapObj, readval, defaultVal) {
  171. if ((typeof mapObj === 'object') && (mapObj !== null)) {
  172. for (var key in mapObj) {
  173. if (readval === key || readval.match(new RegExp('^' + key + '$'))) {
  174. return mapObj[key];
  175. }
  176. }
  177. }
  178. return defaultVal;
  179. }
  180. function init_attr(elem) {
  181. elem.initData('get', 'STATE');
  182. var get = elem.data('get');
  183. elem.initData('set', (get !== 'STATE') ? get : '');
  184. elem.initData('cmd', 'set');
  185. elem.initData('get-on', '(true|1|on|open|ON)');
  186. elem.initData('get-off', '!on');
  187. me.addReading(elem, 'get');
  188. if (elem.isDeviceReading('get-on')) {
  189. me.addReading(elem, 'get-on');
  190. }
  191. if (elem.isDeviceReading('get-off')) {
  192. me.addReading(elem, 'get-off');
  193. }
  194. // reachable parameter
  195. elem.initData('reachable-on', '!off');
  196. elem.initData('reachable-off', '(false|0)');
  197. me.addReading(elem, 'reachable');
  198. // if hide reading is defined, set defaults for comparison
  199. if (elem.isValidData('hide')) {
  200. elem.initData('hide-on', '(true|1|on)');
  201. }
  202. elem.initData('hide', 'STATE');
  203. if (elem.isValidData('hide-on')) {
  204. elem.initData('hide-off', '!on');
  205. }
  206. me.addReading(elem, 'hide');
  207. // if lock reading is defined, set defaults for comparison
  208. if (elem.isValidData('lock')) {
  209. elem.initData('lock-on', '(true|1|on)');
  210. }
  211. elem.initData('lock', elem.data('get'));
  212. if (elem.isValidData('lock-on')) {
  213. elem.initData('lock-off', '!on');
  214. }
  215. me.addReading(elem, 'lock');
  216. }
  217. function init_ui(elem) {
  218. elem.text(me.widgetname);
  219. }
  220. function reinit() {}
  221. function init() {
  222. ftui.log(1, "init widget: name=" + me.widgetname + " area=" + me.area);
  223. me.elements = $('[data-type="' + me.widgetname + '"]:not([data-ready])', me.area);
  224. me.elements.each(function (index) {
  225. var elem = $(this);
  226. elem.attr("data-ready", "");
  227. me.init_attr(elem);
  228. elem = me.init_ui(elem);
  229. });
  230. }
  231. function addReading(elem, key) {
  232. var data = elem.data(key);
  233. if (ftui.isValid(data)) {
  234. if ($.isArray(data) || !data.toString().match(/^[#\.\[][^:]*$/)) {
  235. var device = elem.data('device');
  236. if (!$.isArray(data)) {
  237. data = new Array(data.toString());
  238. }
  239. for (var i = data.length - 1; i >= 0; i -= 1) {
  240. var reading = data[i];
  241. // fully qualified readings => DEVICE:READING
  242. if (reading.match(/:/)) {
  243. var fqreading = reading.split(':');
  244. device = fqreading[0].replace('[', '');
  245. reading = fqreading[1].replace(']', '');
  246. }
  247. // fill objects for mapping from FHEMWEB paramid to device + reading
  248. if (ftui.isValid(device) && ftui.isValid(reading) &&
  249. device !== '' && reading !== '' &&
  250. device !== ' ' && reading !== ' ') {
  251. device = device.toString();
  252. var paramid = (reading === 'STATE') ? device : [device, reading].join('-');
  253. subscriptions[paramid] = {};
  254. subscriptions[paramid].device = device;
  255. subscriptions[paramid].reading = reading;
  256. }
  257. }
  258. }
  259. }
  260. }
  261. function extractReadings(elem, key) {
  262. var data = elem.data(key);
  263. if (ftui.isValid(data)) {
  264. if ($.isArray(data) || !data.toString().match(/^[#\.\[][^:]*$/)) {
  265. if (!$.isArray(data)) {
  266. data = new Array(data.toString());
  267. }
  268. for (var i = data.length - 1; i >= 0; i -= 1) {
  269. var device, reading, item = data[i];
  270. // only fully qualified readings => DEVICE:READING
  271. if (item.match(/:/)) {
  272. var fqreading = item.split(':');
  273. device = fqreading[0].replace('[', '');
  274. reading = fqreading[1].replace(']', '');
  275. }
  276. // fill objects for mapping from FHEMWEB paramid to device + reading
  277. if (ftui.isValid(device) && ftui.isValid(reading) &&
  278. device !== '' && reading !== '' &&
  279. device !== ' ' && reading !== ' ') {
  280. device = device.toString();
  281. var paramid = (reading === 'STATE') ? device : [device, reading].join('-');
  282. subscriptions[paramid] = {};
  283. subscriptions[paramid].device = device;
  284. subscriptions[paramid].reading = reading;
  285. }
  286. }
  287. }
  288. }
  289. }
  290. function update(dev, par) {
  291. ftui.log(1, 'warning: ' + me.widgetname + ' has not implemented update function');
  292. }
  293. var me = {
  294. widgetname: 'widget',
  295. area: '',
  296. init: init,
  297. reinit: reinit,
  298. init_attr: init_attr,
  299. init_ui: init_ui,
  300. update: update,
  301. update_lock: update_lock,
  302. update_reachable: update_reachable,
  303. update_hide: update_hide,
  304. updateHide: updateHide,
  305. updateLock: updateLock,
  306. updateReachable: updateReachable,
  307. substitution: substitution,
  308. fix: fix,
  309. factor: factor,
  310. round: round,
  311. map: map,
  312. addReading: addReading,
  313. extractReadings: extractReadings,
  314. subscriptions: subscriptions,
  315. elements: elements
  316. };
  317. return me;
  318. };
  319. // ------- Plugins --------
  320. var plugins = {
  321. modules: [],
  322. addModule: function (module) {
  323. this.modules.push(module);
  324. },
  325. removeArea: function (area) {
  326. for (var i = this.modules.length - 1; i >= 0; i -= 1) {
  327. if (this.modules[i].area === area) {
  328. this.modules.splice(i, 1);
  329. }
  330. }
  331. },
  332. updateParameters: function () {
  333. ftui.subscriptions = {};
  334. ftui.subscriptionTs = {};
  335. ftui.devs = [ftui.config.webDevice];
  336. ftui.reads = ['STATE', 'longpoll'];
  337. for (var i = this.modules.length - 1; i >= 0; i -= 1) {
  338. var module = this.modules[i];
  339. for (var key in module.subscriptions) {
  340. ftui.subscriptions[key] = module.subscriptions[key];
  341. ftui.subscriptionTs[key + '-ts'] = module.subscriptions[key];
  342. var d = ftui.subscriptions[key].device;
  343. if (ftui.devs.indexOf(d) < 0) {
  344. ftui.devs.push(d);
  345. }
  346. var r = ftui.subscriptions[key].reading;
  347. if (ftui.reads.indexOf(r) < 0) {
  348. ftui.reads.push(r);
  349. }
  350. }
  351. }
  352. // build filter
  353. var devicelist = (ftui.devs.length) ? $.map(ftui.devs, $.trim).join() : '.*';
  354. var readinglist = (ftui.reads.length) ? $.map(ftui.reads, $.trim).join(' ') : '';
  355. if (!ftui.config.longPollFilter) {
  356. ftui.poll.long.filter = devicelist + ', ' + readinglist;
  357. } else {
  358. ftui.poll.long.filter = ftui.config.longPollFilter;
  359. }
  360. if (!ftui.config.shortPollFilter) {
  361. ftui.poll.short.filter = devicelist + ' ' + readinglist;
  362. } else {
  363. ftui.poll.short.filter = ftui.config.shortPollFilter;
  364. }
  365. // force shortpoll
  366. ftui.states.lastShortpoll = 0;
  367. },
  368. load: function (name, area) {
  369. ftui.log(1, 'Load plugin "' + name + '" for area "' + area + '"');
  370. return ftui.loadPlugin(name, area);
  371. },
  372. reinit: function () {
  373. $.each(this.modules, function (index, module) {
  374. // Iterate each module and run update function if module is available
  375. if (typeof module === 'object') {
  376. module.reinit();
  377. }
  378. });
  379. },
  380. update: function (dev, par) {
  381. $.each(this.modules, function (index, module) {
  382. // Iterate each module and run update function if module is available
  383. if (typeof module === 'object') {
  384. module.update(dev, par);
  385. }
  386. });
  387. // update data-bind elements
  388. ftui.updateBindElements('ftui.deviceStates');
  389. ftui.log(1, 'call "plugins.update" done for "' + dev + ':' + par + '"');
  390. }
  391. };
  392. // -------- FTUI ----------
  393. var ftui = {
  394. version: '2.7.6',
  395. config: {
  396. DEBUG: false,
  397. DEMO: false,
  398. ICONDEMO: false,
  399. dir: '',
  400. filename: '',
  401. basedir: '',
  402. fhemDir: '',
  403. debuglevel: 0,
  404. doLongPoll: true,
  405. lang: 'de',
  406. toastPosition: 'bottom-left',
  407. shortpollInterval: 0,
  408. styleCollection: {},
  409. stdColors: ["green", "orange", "red", "ligthblue", "blue", "gray", "white", "mint"]
  410. },
  411. poll: {
  412. short: {
  413. lastTimestamp: new Date(),
  414. timer: null,
  415. request: null,
  416. result: null,
  417. lastErrorToast: null
  418. },
  419. long: {
  420. xhr: null,
  421. currLine: 0,
  422. lastUpdateTimestamp: new Date(),
  423. lastEventTimestamp: new Date(),
  424. timer: null,
  425. result: null,
  426. lastErrorToast: null
  427. }
  428. },
  429. states: {
  430. width: 0,
  431. lastSetOnline: 0,
  432. lastShortpoll: 0,
  433. longPollRestart: false,
  434. inits: []
  435. },
  436. deviceStates: {},
  437. paramIdMap: {},
  438. timestampMap: {},
  439. subscriptions: {},
  440. subscriptionTs: {},
  441. scripts: [],
  442. gridster: {
  443. instances: {},
  444. instance: null,
  445. baseX: 0,
  446. baseY: 0,
  447. margins: 5,
  448. mincols: 0,
  449. cols: 0,
  450. rows: 0
  451. },
  452. init: function () {
  453. ftui.hideWidgets();
  454. ftui.paramIdMap = {};
  455. ftui.timestampMap = {};
  456. ftui.config.longPollType = $("meta[name='longpoll_type']").attr("content") || 'websocket';
  457. var longpoll = $("meta[name='longpoll']").attr("content") || '1';
  458. ftui.config.doLongPoll = (longpoll != '0');
  459. ftui.config.shortPollFilter = $("meta[name='shortpoll_filter']").attr("content");
  460. ftui.config.longPollFilter = $("meta[name='longpoll_filter']").attr("content");
  461. ftui.config.DEMO = ($("meta[name='demo']").attr("content") == '1');
  462. ftui.config.ICONDEMO = ($("meta[name='icondemo']").attr("content") == '1');
  463. ftui.config.debuglevel = $("meta[name='debug']").attr("content") || 0;
  464. ftui.config.fadeTime = $("meta[name='fade_time']").attr("content") || 200;
  465. if (ftui.config.fadeTime === '0') {
  466. ftui.log(1, 'fadeTime=0 => disable jQuery animation');
  467. jQuery.fx.off = true;
  468. }
  469. ftui.config.webDevice = $("meta[name='web_device']").attr("content") || $.trim($('body').data('webname')) || 'WEB';
  470. ftui.config.maxLongpollAge = $("meta[name='longpoll_maxage']").attr("content") || 240;
  471. ftui.config.DEBUG = (ftui.config.debuglevel > 0);
  472. ftui.config.TOAST = $("meta[name='toast']").attr("content") || 5; //1,2,3...= n Toast-Messages, 0: No Toast-Messages
  473. ftui.config.toastPosition = $("meta[name='toast_position']").attr("content") || 'bottom-left';
  474. ftui.config.shortpollInterval = $("meta[name='shortpoll_only_interval']").attr("content") || 30;
  475. ftui.config.shortPollDelay = $("meta[name='shortpoll_restart_delay']").attr("content") || 3000;
  476. //self path
  477. var url = window.location.pathname;
  478. ftui.config.filename = url.substring(url.lastIndexOf('/') + 1);
  479. ftui.log(1, 'Filename: ' + ftui.config.filename);
  480. var fhemUrl = $("meta[name='fhemweb_url']").attr("content");
  481. ftui.config.fhemDir = fhemUrl || location.origin + "/fhem/";
  482. if (fhemUrl && new RegExp("^((?!http:\/\/|https:\/\/).)*$").test(fhemUrl)) {
  483. ftui.config.fhemDir = location.origin + "/" + fhemUrl + "/";
  484. }
  485. ftui.config.fhemDir = ftui.config.fhemDir.replace('///', '//');
  486. ftui.log(1, 'FHEM dir: ' + ftui.config.fhemDir);
  487. // lang
  488. var userLang = navigator.language || navigator.userLanguage;
  489. ftui.config.lang = $("meta[name='lang']").attr("content") || ((ftui.isValid(userLang)) ? userLang.split('-')[0] : 'de');
  490. // credentials
  491. ftui.config.username = $("meta[name='username']").attr("content");
  492. ftui.config.password = $("meta[name='password']").attr("content");
  493. // subscriptions
  494. ftui.devs = [ftui.config.webDevice];
  495. ftui.reads = ['STATE'];
  496. // Get CSFS Token
  497. ftui.getCSrf();
  498. // init Toast
  499. function configureToast() {
  500. if ($.toast && !$('link[href$="lib/jquery.toast.min.css"]').length)
  501. $('head').append('<link rel="stylesheet" href="' + ftui.config.basedir +
  502. 'lib/jquery.toast.min.css" type="text/css" />');
  503. }
  504. if (!$.fn.toast) {
  505. ftui.dynamicload(ftui.config.basedir + "lib/jquery.toast.min.js", false).done(function () {
  506. configureToast();
  507. });
  508. } else {
  509. configureToast();
  510. }
  511. // after the page became visible, check server connection
  512. $(document).on('visibilitychange', function () {
  513. if (document.visibilityState === 'hidden') {
  514. // page is hidden
  515. } else {
  516. // page is visible
  517. ftui.log(1, 'Page became visible again -> start healthCheck in 3 secondes ');
  518. setTimeout(function () {
  519. ftui.healthCheck();
  520. }, 3000);
  521. }
  522. });
  523. try {
  524. // try to use localStorage
  525. localStorage.setItem('ftui_version', ftui.version);
  526. localStorage.removeItem('ftui_version');
  527. } catch (e) {
  528. // there was an error so...
  529. ftui.toast('You are in Privacy Mode<br>Please deactivate Privacy Mode and then reload the page.', 'error');
  530. }
  531. // detect clickEventType
  532. var android = ftui.getAndroidVersion();
  533. var iOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
  534. var onlyTouch = ((android && parseFloat(android) < 5) || iOS);
  535. ftui.config.clickEventType = (onlyTouch) ? 'touchstart' : 'touchstart mousedown';
  536. ftui.config.moveEventType = ((onlyTouch) ? 'touchmove' : 'touchmove mousemove');
  537. ftui.config.releaseEventType = ((onlyTouch) ? 'touchend' : 'touchend mouseup');
  538. ftui.config.leaveEventType = ((onlyTouch) ? 'touchleave' : 'touchleave mouseout');
  539. ftui.config.enterEventType = ((onlyTouch) ? 'touchenter' : 'touchenter mouseenter');
  540. // add background for modal dialogs
  541. $("<div id='shade' />").prependTo('body').hide();
  542. $('#shade').on(ftui.config.clickEventType, function (e) {
  543. $(document).trigger("shadeClicked");
  544. });
  545. //remove old storages
  546. var arr = [];
  547. for (var i = 0; i < localStorage.length; i++) {
  548. if (localStorage.key(i).match(/[0-9abcdef]{4}-[0-9abcdef]{4}-[0-9abcdef]{4}/)) {
  549. arr.push(localStorage.key(i));
  550. }
  551. }
  552. for (i = 0; i < arr.length; i++) {
  553. localStorage.removeItem(arr[i]);
  554. }
  555. ftui.readStatesLocal();
  556. // init FTUI CSS if not already loaded
  557. if ($('link[href$="css/fhem-tablet-ui.css"]').length === 0 &&
  558. $('link[href$="css/fhem-tablet-ui.min.css"]').length === 0 &&
  559. !f7) {
  560. var cssUrl = ftui.config.basedir + 'css/fhem-tablet-ui.css';
  561. $.when($.get(cssUrl, function () {
  562. $('<link>', {
  563. rel: 'stylesheet',
  564. type: 'text/css',
  565. 'href': cssUrl
  566. }).prependTo('head');
  567. })).then(function () {
  568. var ii = 0;
  569. var cssListener = setInterval(function () {
  570. ftui.log(1, 'fhem-tablet-ui.css dynamically loaded. Waiting until it is ready to use...');
  571. if ($("body").css("text-align") === "center") {
  572. ftui.log(1, 'fhem-tablet-ui.css is ready to use.');
  573. clearInterval(cssListener);
  574. ftui.loadStyleSchema();
  575. ftui.initPage();
  576. }
  577. ii++;
  578. if (ii > 120) {
  579. clearInterval(cssListener);
  580. ftui.toast("fhem-tablet-ui.css not ready to use", 'error');
  581. }
  582. }, 50);
  583. });
  584. } else {
  585. ftui.loadStyleSchema();
  586. ftui.initPage();
  587. }
  588. $(document).on("changedSelection", function () {
  589. $(
  590. '.gridster li > header ~ .hbox:only-of-type, ' +
  591. '.dialog > header ~ .hbox:first-of-type:nth-last-of-type(1), ' +
  592. '.gridster li > header ~ .center:not([data-type]):only-of-type, ' +
  593. '.card > header ~ div:not([data-type]):only-of-type, ' +
  594. '.gridster li header ~ div:first-of-type:nth-last-of-type(1)'
  595. ).each(function (index) {
  596. var heightHeader = $(this).siblings('header').outerHeight();
  597. if (heightHeader > 0) {
  598. $(this).css({
  599. 'height': 'calc(100% - ' + $(this).siblings('header').outerHeight() + 'px)'
  600. });
  601. }
  602. });
  603. });
  604. $(document).on("initWidgetsDone", function () {
  605. // restart longpoll
  606. ftui.states.longPollRestart = true;
  607. ftui.restartLongPoll();
  608. ftui.initHeaderLinks();
  609. ftui.saveStatesLocal("subscriptions");
  610. ftui.saveStatesLocal("modules");
  611. // calculate full line height
  612. $(".line-full").each(function () {
  613. $(this).css({
  614. 'line-height': $(this).parent().height() + 'px'
  615. });
  616. });
  617. // start shortpoll delayed
  618. ftui.startShortPollInterval(500);
  619. // trigger refreshs
  620. $(document).trigger('changedSelection');
  621. if (!ftui.config.ICONDEMO) {
  622. ftui.disableSelection();
  623. }
  624. });
  625. if (!f7) {
  626. // dont show focus frame
  627. $("*:not(select):not(textarea)").focus(function () {
  628. $(this).blur();
  629. });
  630. }
  631. ftui.saveStatesLocal("version");
  632. ftui.saveStatesLocal("config");
  633. setInterval(function () {
  634. ftui.healthCheck();
  635. }, 60000);
  636. },
  637. initGridster: function (area) {
  638. ftui.gridster.minX = parseInt($("meta[name='widget_min_width'],meta[name='gridster_min_width']").attr("content") || 0);
  639. ftui.gridster.minY = parseInt($("meta[name='widget_min_height'],meta[name='gridster_min_height']").attr("content") || 0);
  640. ftui.gridster.baseX = parseInt($("meta[name='widget_base_width'],meta[name='gridster_base_width']").attr("content") || 0);
  641. ftui.gridster.baseY = parseInt($("meta[name='widget_base_height'],meta[name='gridster_base_height']").attr("content") || 0);
  642. ftui.gridster.cols = parseInt($("meta[name='gridster_cols']").attr("content") || 0);
  643. ftui.gridster.rows = parseInt($("meta[name='gridster_rows']").attr("content") || 0);
  644. ftui.gridster.resize = parseInt($("meta[name='gridster_resize']").attr("content") || (ftui.gridster.baseX + ftui.gridster.baseY) >
  645. 0 ? 0 : 1);
  646. if ($("meta[name='widget_margin'],meta[name='gridster_margin']").attr("content"))
  647. ftui.gridster.margins = parseInt($("meta[name='widget_margin'],meta[name='gridster_margin']").attr("content"));
  648. function configureGridster() {
  649. var highestCol = -1;
  650. var highestRow = -1;
  651. var baseX = 0;
  652. var baseY = 0;
  653. var cols = 0;
  654. var rows = 0;
  655. $(".gridster > ul > li").each(function () {
  656. var colVal = $(this).data("col") + $(this).data("sizex") - 1;
  657. if (colVal > highestCol)
  658. highestCol = colVal;
  659. var rowVal = $(this).data("row") + $(this).data("sizey") - 1;
  660. if (rowVal > highestRow)
  661. highestRow = rowVal;
  662. });
  663. //console.log(ftui.gridster.cols, ftui.gridster.rows, ftui.gridster.baseX, ftui.gridster.baseY);
  664. cols = (ftui.gridster.cols > 0) ? ftui.gridster.cols : highestCol;
  665. rows = (ftui.gridster.rows > 0) ? ftui.gridster.rows : highestRow;
  666. var colMargins = 2 * cols * ftui.gridster.margins;
  667. var rowMargins = 2 * rows * ftui.gridster.margins;
  668. baseX = (ftui.gridster.baseX > 0) ? ftui.gridster.baseX : (window.innerWidth - colMargins) / cols;
  669. baseY = (ftui.gridster.baseY > 0) ? ftui.gridster.baseY : (window.innerHeight - rowMargins) / rows;
  670. if (baseX < ftui.gridster.minX) {
  671. baseX = ftui.gridster.minX;
  672. }
  673. if (baseY < ftui.gridster.minY) {
  674. baseY = ftui.gridster.minY;
  675. }
  676. ftui.gridster.mincols = parseInt($("meta[name='widget_min_cols'],meta[name='gridster_min_cols']").attr("content") ||
  677. cols);
  678. if (ftui.gridster.instances[area])
  679. ftui.gridster.instances[area].destroy();
  680. ftui.gridster.instances[area] = $(".gridster > ul", area).gridster({
  681. widget_base_dimensions: [baseX, baseY],
  682. widget_margins: [ftui.gridster.margins, ftui.gridster.margins],
  683. draggable: {
  684. handle: '.gridster li > header'
  685. },
  686. min_cols: parseInt(ftui.gridster.mincols),
  687. }).data('gridster');
  688. if (ftui.gridster.instances[area]) {
  689. if ($("meta[name='gridster_disable']").attr("content") == '1') {
  690. ftui.gridster.instances[area].disable();
  691. }
  692. if ($("meta[name='gridster_starthidden']").attr("content") == '1') {
  693. $('.gridster').hide();
  694. }
  695. }
  696. // corrections for gridster in gridster element
  697. $('.gridster > ul > li:has(* .gridster)').each(function () {
  698. var gridgrid = $(this);
  699. gridgrid.css({
  700. 'background-color': 'transparent',
  701. 'margin': '-' + ftui.gridster.margins + 'px',
  702. // 'width': gridgrid.parent().width() - gridgrid.position().left,
  703. // 'height': '100%'
  704. });
  705. });
  706. $('.gridster > ul > li >.center', area).parent().addClass('has_center');
  707. // max height for inner boxes
  708. $('.gridster > ul > li > .vbox', area).parent().addClass('has_vbox');
  709. }
  710. if ($('.gridster', area).length) {
  711. if (!$('link[href$="lib/jquery.gridster.min.css"]').length)
  712. $('head').append('<link rel="stylesheet" href="' + ftui.config.basedir +
  713. 'lib/jquery.gridster.min.css" type="text/css" />');
  714. if (!$.fn.gridster) {
  715. ftui.dynamicload(ftui.config.basedir + "lib/jquery.gridster.min.js", false).done(function () {
  716. configureGridster();
  717. });
  718. } else {
  719. configureGridster();
  720. }
  721. if (ftui.gridster.resize) {
  722. $(window).on('resize', function () {
  723. if (ftui.states.width !== window.innerWidth) {
  724. clearTimeout(ftui.states.delayResize);
  725. ftui.states.delayResize = setTimeout(configureGridster, 500);
  726. ftui.states.width = window.innerWidth;
  727. }
  728. });
  729. }
  730. }
  731. },
  732. initPage: function (area) {
  733. // hideWidgets
  734. ftui.hideWidgets(area);
  735. // init gridster
  736. area = (ftui.isValid(area)) ? area : 'html';
  737. console.time('initPage-' + area);
  738. ftui.states.startTime = new Date();
  739. ftui.log(2, 'initPage - area=' + area);
  740. ftui.initGridster(area);
  741. // include extern html code
  742. var deferredArr = $.map($('[data-template]', area), function (templ, i) {
  743. var templElem = $(templ);
  744. return $.get(
  745. templElem.data('template'), {},
  746. function (data) {
  747. var parValues = templElem.data('parameter');
  748. for (var key in parValues) {
  749. data = data.replace(new RegExp(key, 'g'), parValues[key]);
  750. }
  751. templElem.html(data);
  752. }
  753. );
  754. });
  755. // get current values of readings not before all widgets are loaded
  756. $.when.apply(this, deferredArr).then(function () {
  757. //continue after loading the includes
  758. ftui.log(1, 'init templates - Done');
  759. ftui.initWidgets(area).done(function () {
  760. console.timeEnd('initPage-' + area);
  761. var dur = 'initPage (' + area + '): in ' + (new Date() - ftui.states.startTime) + 'ms';
  762. if (ftui.config.debuglevel > 1) ftui.toast(dur);
  763. ftui.log(1, dur);
  764. });
  765. });
  766. },
  767. initWidgets: function (area) {
  768. var defer = new $.Deferred();
  769. area = (ftui.isValid(area)) ? area : 'html';
  770. var types = [];
  771. ftui.log(2, 'initWidgets before- area=' + area);
  772. ftui.log(2, $.map(plugins.modules, function (m) {
  773. return (m.area + ':' + m.widgetname);
  774. }).join(' '));
  775. plugins.removeArea(area);
  776. ftui.log(2, 'initWidgets after removed- area=' + area);
  777. ftui.log(2, $.map(plugins.modules, function (m) {
  778. return (m.area + ':' + m.widgetname);
  779. }).join(' '));
  780. // collect required widgets types
  781. $('[data-type] ', area).each(function (index) {
  782. var type = $(this).data("type");
  783. if (types.indexOf(type) < 0) {
  784. types.push(type);
  785. }
  786. });
  787. // init widgets
  788. var deferredArr = $.map(types, function (type, i) {
  789. return plugins.load(type, area);
  790. });
  791. // get current values of readings not before all widgets are loaded
  792. $.when.apply(this, deferredArr).then(function () {
  793. plugins.updateParameters();
  794. ftui.log(1, 'initWidgets - Done');
  795. $(document).trigger("initWidgetsDone", [area]);
  796. defer.resolve();
  797. });
  798. return defer.promise();
  799. },
  800. initHeaderLinks: function () {
  801. if (($('[class*=fa-]').length ||
  802. $('[data-type="select"]').length ||
  803. $('[data-type="homestatus"]').length) &&
  804. !$('link[href$="lib/font-awesome.min.css"]').length
  805. )
  806. $('head').append('<link rel="stylesheet" href="' + ftui.config.basedir + 'lib/font-awesome.min.css" type="text/css" />');
  807. if ($('[class*=oa-]').length && !$('link[href$="lib/openautomation.css"]').length)
  808. $('head').append('<link rel="stylesheet" href="' + ftui.config.basedir + 'lib/openautomation.css" type="text/css" />');
  809. if ($('[class*=fs-]').length && !$('link[href$="lib/fhemSVG.css"]').length)
  810. $('head').append('<link rel="stylesheet" href="' + ftui.config.basedir + 'lib/fhemSVG.css" type="text/css" />');
  811. if ($('[class*=mi-]').length && !$('link[href$="lib/material-icons.min.css"]').length)
  812. $('head').append('<link rel="stylesheet" href="' + ftui.config.basedir +
  813. 'lib/material-icons.min.css" type="text/css" />');
  814. if ($('[class*=wi-]').length && !$('link[href$="lib/weather-icons.min.css"]').length)
  815. $('head').append('<link rel="stylesheet" href="' + ftui.config.basedir +
  816. 'lib/weather-icons.min.css" type="text/css" />');
  817. if ($('[class*=wi-wind]').length && !$('link[href$="lib/weather-icons-wind.min.css"]').length)
  818. $('head').append('<link rel="stylesheet" href="' + ftui.config.basedir +
  819. 'lib/weather-icons-wind.min.css" type="text/css" />');
  820. },
  821. startLongpoll: function () {
  822. ftui.log(2, 'startLongpoll: ' + ftui.config.doLongPoll);
  823. ftui.poll.long.lastEventTimestamp = new Date();
  824. if (ftui.config.doLongPoll) {
  825. ftui.config.shortpollInterval = $("meta[name='shortpoll_interval']").attr("content") || 15 * 60; // 15 minutes
  826. ftui.poll.long.timer = setTimeout(function () {
  827. ftui.longPoll();
  828. }, 0);
  829. }
  830. },
  831. stopLongpoll: function () {
  832. ftui.log(2, 'stopLongpoll');
  833. clearInterval(ftui.poll.long.timer);
  834. if (ftui.poll.long.request)
  835. ftui.poll.long.request.abort();
  836. if (ftui.poll.long.websocket) {
  837. //ftui.poll.long.websocket.send('bye');
  838. ftui.poll.long.websocket.close();
  839. ftui.poll.long.websocket = undefined;
  840. ftui.log(2, 'stopped websocket');
  841. }
  842. },
  843. restartLongPoll: function (msg, error) {
  844. ftui.log(2, 'restartLongpoll');
  845. var delay;
  846. clearTimeout(ftui.poll.long.timer);
  847. if (msg) {
  848. ftui.toast("Disconnected from FHEM<br>" + msg, error);
  849. }
  850. ftui.stopLongpoll();
  851. if (ftui.states.longPollRestart) {
  852. delay = 0;
  853. } else {
  854. ftui.toast("Retry to connect in 10 seconds");
  855. delay = 10000;
  856. }
  857. ftui.poll.long.timer = setTimeout(function () {
  858. ftui.startLongpoll();
  859. }, delay);
  860. },
  861. forceRefresh: function () {
  862. ftui.states.lastShortpoll = 0;
  863. ftui.shortPoll();
  864. },
  865. startShortPollInterval: function (delay) {
  866. ftui.log(1, 'shortpoll: start in (ms):' + (delay || ftui.config.shortpollInterval * 1000));
  867. clearInterval(ftui.poll.short.timer);
  868. ftui.poll.short.timer = setTimeout(function () {
  869. // get current values of readings every x seconds
  870. ftui.shortPoll();
  871. ftui.startShortPollInterval();
  872. }, (delay || ftui.config.shortpollInterval * 1000));
  873. },
  874. shortPoll: function (silent) {
  875. var ltime = new Date().getTime() / 1000;
  876. if ((ltime - ftui.states.lastShortpoll) < ftui.config.shortpollInterval)
  877. return;
  878. ftui.log(1, 'start shortpoll');
  879. var startTime = new Date();
  880. // invalidate all readings for detection of outdated ones
  881. for (var i = 0, len = ftui.devs.length; i < len; i++) {
  882. var params = ftui.deviceStates[ftui.devs[i]];
  883. for (var reading in params) {
  884. params[reading].valid = false;
  885. }
  886. }
  887. console.time('get jsonlist2');
  888. ftui.poll.short.request =
  889. ftui.sendFhemCommand('jsonlist2 ' + ftui.poll.short.filter)
  890. .done(function (fhemJSON) {
  891. console.timeEnd('get jsonlist2');
  892. console.time('read jsonlist2');
  893. ftui.log(3, 'fhemJSON: 0=' + Object.keys(fhemJSON)[0] + ' 1=' + Object.keys(fhemJSON)[1]);
  894. // function to import data
  895. function checkReading(device, section) {
  896. for (var reading in section) {
  897. var isUpdated = false;
  898. var paramid = (reading === 'STATE') ? device : [device, reading].join('-');
  899. var newParam = section[reading];
  900. if (typeof newParam !== 'object') {
  901. //ftui.log(5,'paramid='+paramid+' newParam='+newParam);
  902. newParam = {
  903. "Value": newParam,
  904. "Time": ""
  905. };
  906. }
  907. // is there a subscription, then check and update widgets
  908. if (ftui.subscriptions[paramid]) {
  909. var oldParam = ftui.getDeviceParameter(device, reading);
  910. isUpdated = (!oldParam || oldParam.val !== newParam.Value || oldParam.date !== newParam.Time);
  911. ftui.log(5, 'isUpdated=' + isUpdated);
  912. }
  913. // write into internal cache object
  914. var params = ftui.deviceStates[device] || {};
  915. var param = params[reading] || {};
  916. param.date = newParam.Time;
  917. param.val = newParam.Value;
  918. // console.log('*****',device);
  919. param.valid = true;
  920. params[reading] = param;
  921. ftui.deviceStates[device] = params;
  922. ftui.paramIdMap[paramid] = {};
  923. ftui.paramIdMap[paramid].device = device;
  924. ftui.paramIdMap[paramid].reading = reading;
  925. ftui.timestampMap[paramid + '-ts'] = {};
  926. ftui.timestampMap[paramid + '-ts'].device = device;
  927. ftui.timestampMap[paramid + '-ts'].reading = reading;
  928. // update widgets only if necessary
  929. if (isUpdated) {
  930. ftui.log(5, '[shortPoll] do update for ' + device + ',' + reading);
  931. plugins.update(device, reading);
  932. }
  933. }
  934. }
  935. // import the whole fhemJSON
  936. if (fhemJSON && fhemJSON.Results) {
  937. var len = fhemJSON.Results.length;
  938. ftui.log(2, 'shortpoll: fhemJSON.Results.length=' + len);
  939. var results = fhemJSON.Results;
  940. for (var i = len - 1; i >= 0; i -= 1) {
  941. var res = results[i];
  942. var devName = res.Name;
  943. if (devName.indexOf('FHEMWEB') < 0 && !devName.match(/WEB_\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}_\d{5}/)) {
  944. checkReading(devName, res.Internals);
  945. checkReading(devName, res.Attributes);
  946. checkReading(devName, res.Readings);
  947. }
  948. }
  949. // finished
  950. var duration = ftui.diffSeconds(startTime, new Date());
  951. if (ftui.config.debuglevel > 1) {
  952. var paramCount = Object.keys(ftui.paramIdMap).length;
  953. ftui.toast("Full refresh done in " +
  954. duration + "s for " +
  955. paramCount + " parameter(s)");
  956. }
  957. ftui.log(1, 'shortPoll: Done');
  958. if (ftui.poll.short.lastErrorToast) {
  959. ftui.poll.short.lastErrorToast.reset();
  960. }
  961. ftui.states.lastShortpoll = ltime;
  962. ftui.poll.short.duration = duration * 1000;
  963. ftui.poll.short.lastTimestamp = new Date();
  964. ftui.poll.short.result = 'ok';
  965. ftui.saveStatesLocal("deviceStates");
  966. ftui.saveStatesLocal("shortPoll");
  967. ftui.updateBindElements('ftui.');
  968. if (!silent) {
  969. ftui.onUpdateDone();
  970. }
  971. } else {
  972. var err = "request failed: Result is null";
  973. ftui.log(1, "shortPoll: " + err);
  974. ftui.poll.short.result = err;
  975. ftui.toast("<u>ShortPoll " + err + " </u><br>", 'error');
  976. ftui.saveStatesLocal("shortPoll");
  977. }
  978. console.timeEnd('read jsonlist2');
  979. })
  980. .fail(function (jqxhr, textStatus, error) {
  981. var err = textStatus + ", " + error;
  982. ftui.log(1, "shortPoll: request failed: " + err);
  983. ftui.poll.short.result = err;
  984. ftui.states.lastSetOnline = 0;
  985. ftui.states.lastShortpoll = 0;
  986. ftui.saveStatesLocal("deviceStates");
  987. ftui.saveStatesLocal("shortPoll");
  988. if (textStatus.indexOf('parsererror') < 0) {
  989. ftui.poll.short.lastErrorToast = ftui.toast("<u>ShortPoll Request Failed, will retry in " + ftui.config.shortPollDelay / 1000 + "s</u><br>" +
  990. err, 'error');
  991. ftui.getCSrf();
  992. ftui.startShortPollInterval(ftui.config.shortPollDelay);
  993. } else {
  994. ftui.toast("<u>ShortPoll Request Failed</u><br>" + err, 'error');
  995. }
  996. });
  997. },
  998. longPoll: function () {
  999. if (ftui.config.DEMO) {
  1000. console.log('DEMO-Mode: no longpoll');
  1001. return;
  1002. }
  1003. if ('WebSocket' in window &&
  1004. ftui.config.longPollType === 'websocket' &&
  1005. ftui.deviceStates[ftui.config.webDevice] &&
  1006. ftui.deviceStates[ftui.config.webDevice].longpoll &&
  1007. ftui.deviceStates[ftui.config.webDevice].longpoll.val &&
  1008. ftui.deviceStates[ftui.config.webDevice].longpoll.val === 'websocket') {
  1009. if (ftui.poll.long.websocket) {
  1010. ftui.log(3, 'valid ftui.poll.long.websocket found');
  1011. return;
  1012. }
  1013. if (ftui.poll.long.lastErrorToast) {
  1014. ftui.poll.long.lastErrorToast.reset();
  1015. }
  1016. if (ftui.config.debuglevel > 1) {
  1017. ftui.toast("Longpoll (WebSocket) started");
  1018. }
  1019. ftui.poll.long.URL = ftui.config.fhemDir.replace(/^http/i, "ws") + "?XHR=1&inform=type=status;filter=" +
  1020. ftui.poll.long.filter + ";since=" + ftui.poll.long.lastEventTimestamp.getTime() + ";fmt=JSON" +
  1021. "&timestamp=" + new Date().getTime();
  1022. //"&fwcsrf=" + ftui.config.csrf;
  1023. ftui.log(1, 'websockets URL=' + ftui.poll.long.URL);
  1024. ftui.states.longPollRestart = false;
  1025. ftui.poll.long.websocket = new WebSocket(ftui.poll.long.URL);
  1026. ftui.poll.long.websocket.onclose = function (event) {
  1027. var reason;
  1028. if (event.code == 1000)
  1029. reason =
  1030. "Normal closure, meaning that the purpose for which the connection was established has been fulfilled.";
  1031. else if (event.code == 1001)
  1032. reason =
  1033. "An endpoint is \"going away\", such as a server going down or a browser having navigated away from a page.";
  1034. else if (event.code == 1002)
  1035. reason = "An endpoint is terminating the connection due to a protocol error";
  1036. else if (event.code == 1003)
  1037. reason =
  1038. "An endpoint is terminating the connection because it has received a type of data it cannot accept (e.g., an endpoint that understands only text data MAY send this if it receives a binary message).";
  1039. else if (event.code == 1004)
  1040. reason = "Reserved. The specific meaning might be defined in the future.";
  1041. else if (event.code == 1005)
  1042. reason = "No status code was actually present.";
  1043. else if (event.code == 1006)
  1044. reason = "The connection was closed abnormally, e.g., without sending or receiving a Close control frame";
  1045. else if (event.code == 1007)
  1046. reason =
  1047. "An endpoint is terminating the connection because it has received data within a message that was not consistent with the type of the message (e.g., non-UTF-8 [http://tools.ietf.org/html/rfc3629] data within a text message).";
  1048. else if (event.code == 1008)
  1049. reason =
  1050. "An endpoint is terminating the connection because it has received a message that \"violates its policy\". This reason is given either if there is no other sutible reason, or if there is a need to hide specific details about the policy.";
  1051. else if (event.code == 1009)
  1052. reason =
  1053. "An endpoint is terminating the connection because it has received a message that is too big for it to process.";
  1054. else if (event.code == 1010) // Note that this status code is not used by the server, because it can fail the WebSocket handshake instead.
  1055. reason =
  1056. "An endpoint (client) is terminating the connection because it has expected the server to negotiate one or more extension, but the server didn't return them in the response message of the WebSocket handshake. <br /> Specifically, the extensions that are needed are: " +
  1057. event.reason;
  1058. else if (event.code == 1011)
  1059. reason =
  1060. "A server is terminating the connection because it encountered an unexpected condition that prevented it from fulfilling the request.";
  1061. else if (event.code == 1015)
  1062. reason =
  1063. "The connection was closed due to a failure to perform a TLS handshake (e.g., the server certificate can't be verified).";
  1064. else
  1065. reason = "Unknown reason";
  1066. ftui.log(1, "websocket (url=" + event.target.url + ") closed! reason=" + reason);
  1067. // if current socket closes then restart websocket
  1068. if (event.target.url === ftui.poll.long.URL) {
  1069. ftui.restartLongPoll(reason);
  1070. }
  1071. };
  1072. ftui.poll.long.websocket.onerror = function (event) {
  1073. ftui.log(1, "Error while longpoll: " + event.data);
  1074. if (ftui.config.debuglevel > 1 && event.target.url === ftui.poll.long.URL) {
  1075. ftui.poll.long.lastErrorToast = ftui.toast("Error while longpoll (websocket)", 'error');
  1076. }
  1077. if (ftui.config.DEBUG) ftui.saveStatesLocal("longPoll");
  1078. };
  1079. ftui.poll.long.websocket.onmessage = function (msg) {
  1080. ftui.handleUpdates(msg.data);
  1081. };
  1082. } else {
  1083. ftui.log(1, 'longpoll: websockets not supportetd or not activated > fall back to AJAX');
  1084. if (ftui.poll.long.xhr) {
  1085. ftui.log(3, 'longpoll: valid ftui.poll.long.xhr found');
  1086. return;
  1087. }
  1088. if (ftui.poll.long.request) {
  1089. ftui.log(3, 'longpoll: valid ftui.poll.long.request found');
  1090. return;
  1091. }
  1092. ftui.poll.long.currLine = 0;
  1093. if (ftui.poll.long.lastErrorToast) {
  1094. ftui.poll.long.lastErrorToast.reset();
  1095. }
  1096. if (ftui.config.DEBUG) {
  1097. if (ftui.states.longPollRestart)
  1098. ftui.toast("Longpoll (AJAX) re-started");
  1099. else
  1100. ftui.toast("Longpoll (AJAX) started");
  1101. }
  1102. ftui.log(1, (ftui.states.longPollRestart) ? "longpoll: re-started" : "longpoll: started");
  1103. ftui.states.longPollRestart = false;
  1104. ftui.poll.long.request = $.ajax({
  1105. url: ftui.config.fhemDir,
  1106. cache: false,
  1107. async: true,
  1108. method: 'GET',
  1109. data: {
  1110. XHR: 1,
  1111. inform: "type=status;filter=" + ftui.poll.long.filter + ";since=" +
  1112. ftui.poll.long.lastEventTimestamp.getTime() + ";fmt=JSON",
  1113. fwcsrf: ftui.config.csrf
  1114. },
  1115. username: ftui.config.username,
  1116. password: ftui.config.password,
  1117. xhr: function () {
  1118. ftui.poll.long.xhr = new window.XMLHttpRequest();
  1119. ftui.poll.long.xhr.addEventListener("readystatechange", function (e) {
  1120. var data = e.target.responseText;
  1121. if (e.target.readyState === 4) {
  1122. return;
  1123. }
  1124. if (e.target.readyState === 3) {
  1125. ftui.handleUpdates(data);
  1126. }
  1127. }, false);
  1128. ftui.log(1, 'longpoll: ajax responseURL=' + ftui.poll.long.xhr.responseURL);
  1129. ftui.log(1, 'longpoll: ajax statusText=' + ftui.poll.long.xhr.statusText);
  1130. return ftui.poll.long.xhr;
  1131. }
  1132. })
  1133. .done(function (data) {
  1134. if (ftui.poll.long.xhr) {
  1135. ftui.poll.long.xhr.abort();
  1136. ftui.poll.long.xhr = null;
  1137. }
  1138. ftui.poll.long.request = null;
  1139. if (ftui.states.longPollRestart) {
  1140. ftui.longPoll();
  1141. } else {
  1142. ftui.log(1, "longpoll: Disconnected from FHEM - poll done - " + data);
  1143. ftui.restartLongPoll(data);
  1144. }
  1145. })
  1146. .fail(function (jqXHR, textStatus, errorThrown) {
  1147. if (ftui.poll.long.xhr) {
  1148. ftui.poll.long.xhr.abort();
  1149. ftui.poll.long.xhr = null;
  1150. }
  1151. ftui.poll.long.request = null;
  1152. if (ftui.states.longPollRestart) {
  1153. ftui.longPoll();
  1154. } else {
  1155. ftui.log(1, "longpoll: Error - text=" + textStatus + ": " + errorThrown);
  1156. if (ftui.config.debuglevel > 1) {
  1157. ftui.poll.long.lastErrorToast = ftui.toast("Error while longpoll (ajax)<br>" + textStatus + ": " + errorThrown, 'error');
  1158. }
  1159. ftui.restartLongPoll(textStatus + ": " + errorThrown);
  1160. }
  1161. });
  1162. }
  1163. },
  1164. handleUpdates: function (data) {
  1165. var lines = data.split(/\n/);
  1166. for (var i = ftui.poll.long.currLine, len = lines.length; i < len; i++) {
  1167. ftui.log(5, lines[i]);
  1168. ftui.poll.long.lastLine = lines[i];
  1169. var lastChar = lines[i].slice(-1);
  1170. if (ftui.isValid(lines[i]) && lines[i] !== '' && lastChar === "]") {
  1171. try {
  1172. var dataJSON = JSON.parse(lines[i]);
  1173. var params = null;
  1174. var param = null;
  1175. var isSTATE = (dataJSON[1] !== dataJSON[2]);
  1176. var isTrigger = (dataJSON[1] === '' && dataJSON[2] === '');
  1177. ftui.log(4, dataJSON);
  1178. var pmap = ftui.paramIdMap[dataJSON[0]];
  1179. var tmap = ftui.timestampMap[dataJSON[0]];
  1180. var subscription = ftui.subscriptions[dataJSON[0]];
  1181. // update for a parameter
  1182. if (pmap) {
  1183. if (isSTATE) {
  1184. pmap.reading = 'STATE';
  1185. }
  1186. params = ftui.deviceStates[pmap.device] || {};
  1187. param = params[pmap.reading] || {};
  1188. param.val = dataJSON[1];
  1189. param.valid = true;
  1190. params[pmap.reading] = param;
  1191. ftui.deviceStates[pmap.device] = params;
  1192. // dont wait for timestamp for STATE paramters
  1193. if (isSTATE && ftui.subscriptions[dataJSON[0]]) {
  1194. ftui.poll.long.lastDevice = pmap.device;
  1195. ftui.poll.long.lastReading = pmap.reading;
  1196. ftui.poll.long.lastValue = param.val;
  1197. plugins.update(pmap.device, pmap.reading);
  1198. }
  1199. }
  1200. // update for a timestamp
  1201. // STATE updates has no timestamp
  1202. if (tmap && !isSTATE) {
  1203. params = ftui.deviceStates[tmap.device] || {};
  1204. param = params[tmap.reading] || {};
  1205. param.date = dataJSON[1];
  1206. params[tmap.reading] = param;
  1207. ftui.poll.long.lastUpdateTimestamp = param.date.toDate();
  1208. ftui.deviceStates[tmap.device] = params;
  1209. // paramter + timestamp update now completed -> update widgets
  1210. if (ftui.subscriptionTs[dataJSON[0]]) {
  1211. ftui.poll.long.lastDevice = tmap.device;
  1212. ftui.poll.long.lastReading = tmap.reading;
  1213. ftui.poll.long.lastValue = param.val;
  1214. plugins.update(tmap.device, tmap.reading);
  1215. }
  1216. }
  1217. // it is just a trigger
  1218. if (isTrigger && subscription) {
  1219. plugins.update(subscription.device, subscription.reading);
  1220. }
  1221. } catch (err) {
  1222. ftui.poll.long.lastError = err;
  1223. ftui.log(1, "longpoll: Error=" + err);
  1224. ftui.log(1, "longpoll: Bad line=" + lines[i]);
  1225. }
  1226. }
  1227. }
  1228. ftui.poll.long.lastEventTimestamp = new Date();
  1229. if (ftui.config.DEBUG) {
  1230. ftui.saveStatesLocal("longPoll");
  1231. ftui.saveStatesLocal("deviceStates");
  1232. }
  1233. ftui.updateBindElements('ftui.poll');
  1234. if (!ftui.poll.long.websocket) {
  1235. // Ajax longpoll
  1236. // cumulative data -> remember last line
  1237. // restart after 9999 lines to avoid overflow
  1238. ftui.poll.long.currLine = lines.length - 1;
  1239. if (ftui.poll.long.currLine > 9999) {
  1240. ftui.states.longPollRestart = true;
  1241. ftui.poll.long.request.abort();
  1242. }
  1243. }
  1244. },
  1245. setFhemStatus: function (cmdline) {
  1246. if (ftui.config.DEMO) {
  1247. console.log('DEMO-Mode: no setFhemStatus');
  1248. return;
  1249. }
  1250. // postpone update
  1251. ftui.startShortPollInterval();
  1252. ftui.sendFhemCommand(cmdline);
  1253. },
  1254. sendFhemCommand: function (cmdline) {
  1255. cmdline = cmdline.replace(' ', ' ');
  1256. var dataType = (cmdline.substr(0, 8) === 'jsonlist') ? 'json' : 'text';
  1257. ftui.log(1, 'send to FHEM: ' + cmdline);
  1258. return $.ajax({
  1259. async: true,
  1260. cache: false,
  1261. method: 'GET',
  1262. dataType: dataType,
  1263. url: ftui.config.fhemDir,
  1264. username: ftui.config.username,
  1265. password: ftui.config.password,
  1266. data: {
  1267. cmd: cmdline,
  1268. fwcsrf: ftui.config.csrf,
  1269. XHR: "1"
  1270. },
  1271. error: function (jqXHR, textStatus, errorThrown) {
  1272. ftui.toast("<u>FHEM Command failed</u><br>" + textStatus + ": " + errorThrown + " cmd=" + cmdline, 'error');
  1273. }
  1274. });
  1275. },
  1276. loadStyleSchema: function () {
  1277. $.each($('link[href$="-ui.css"],link[href$="-ui.min.css"]'), function (index, thisSheet) {
  1278. if (!thisSheet || !thisSheet.sheet || !thisSheet.sheet.cssRules || thisSheet.getAttribute('disabled')) return;
  1279. var rules = thisSheet.sheet.cssRules;
  1280. for (var r in rules) {
  1281. if (rules[r].style) {
  1282. var styles = rules[r].style.cssText.split(';');
  1283. styles.pop();
  1284. var elmName = rules[r].selectorText;
  1285. var params = {};
  1286. for (var s in styles) {
  1287. var param = styles[s].toString().split(':');
  1288. if (param[0].match(/color/)) {
  1289. params[$.trim(param[0])] = ftui.rgbToHex($.trim(param[1]).replace('! important', '').replace(
  1290. '!important', ''));
  1291. }
  1292. }
  1293. if (Object.keys(params).length)
  1294. ftui.config.styleCollection[elmName] = params;
  1295. }
  1296. }
  1297. });
  1298. },
  1299. onUpdateDone: function () {
  1300. $(document).trigger("updateDone");
  1301. ftui.checkInvalidElements();
  1302. ftui.updateBindElements();
  1303. },
  1304. checkInvalidElements: function () {
  1305. $('.autohide[data-get]').each(function (index) {
  1306. var elem = $(this);
  1307. var valid = elem.getReading('get').valid;
  1308. if (valid && valid === true)
  1309. elem.removeClass('invalid');
  1310. else
  1311. elem.addClass('invalid');
  1312. });
  1313. },
  1314. updateBindElements: function (filter) {
  1315. $('[data-bind*="' + filter + '"]').each(function (index) {
  1316. var elem = $(this);
  1317. var variable = elem.data('bind');
  1318. if (variable) {
  1319. elem.text(eval(variable));
  1320. }
  1321. });
  1322. },
  1323. setOnline: function () {
  1324. var ltime = new Date().getTime() / 1000;
  1325. if ((ltime - ftui.states.lastSetOnline) > 60) {
  1326. if (ftui.config.DEBUG) ftui.toast("FHEM connected");
  1327. ftui.states.lastSetOnline = ltime;
  1328. // force shortpoll
  1329. ftui.states.lastShortpoll = 0;
  1330. ftui.startShortPollInterval(1000);
  1331. if (!ftui.config.doLongPoll) {
  1332. var longpoll = $("meta[name='longpoll']").attr("content") || '1';
  1333. ftui.config.doLongPoll = (longpoll != '0');
  1334. ftui.states.longPollRestart = false;
  1335. if (ftui.config.doLongPoll) {
  1336. ftui.startLongpoll();
  1337. }
  1338. }
  1339. ftui.log(1, 'FTUI is online');
  1340. }
  1341. },
  1342. setOffline: function () {
  1343. if (ftui.config.DEBUG) ftui.toast("Lost connection to FHEM");
  1344. ftui.config.doLongPoll = false;
  1345. ftui.states.longPollRestart = true;
  1346. clearInterval(ftui.poll.short.timer);
  1347. ftui.stopLongpoll();
  1348. ftui.log(1, 'FTUI is offline');
  1349. },
  1350. readStatesLocal: function () {
  1351. if (!ftui.config.DEMO)
  1352. ftui.deviceStates = JSON.parse(localStorage.getItem('ftui.deviceStates')) || {};
  1353. else {
  1354. $.ajax({
  1355. async: false,
  1356. method: 'GET',
  1357. url: "/fhem/tablet/data/" + ftui.config.filename.replace(".html", ".dat"),
  1358. })
  1359. .done(function (data) {
  1360. ftui.deviceStates = JSON.parse(data) || {};
  1361. });
  1362. }
  1363. },
  1364. saveStatesLocal: function (key) {
  1365. //save variables into localStorage
  1366. try {
  1367. switch (key) {
  1368. case "deviceStates":
  1369. localStorage.setItem("ftui.deviceStates", JSON.stringify(ftui.deviceStates));
  1370. break;
  1371. case "shortPoll":
  1372. localStorage.setItem("ftui.poll.short", JSON.stringify(ftui.poll.short));
  1373. break;
  1374. case "longPoll":
  1375. localStorage.setItem("ftui.poll.long", JSON.stringify(ftui.poll.long));
  1376. break;
  1377. case "subscriptions":
  1378. localStorage.setItem("ftui.subscriptions", JSON.stringify(ftui.subscriptions));
  1379. break;
  1380. case "config":
  1381. localStorage.setItem("ftui.config", JSON.stringify(ftui.config));
  1382. break;
  1383. case "version":
  1384. localStorage.setItem("ftui.version", JSON.stringify(ftui.version));
  1385. break;
  1386. case "modules":
  1387. var checkModule = [];
  1388. for (var i = 0, len = plugins.modules.length; i < len; i++) {
  1389. var name = plugins.modules[i].widgetname,
  1390. area = plugins.modules[i].area;
  1391. checkModule.push({
  1392. name: name,
  1393. area: area
  1394. });
  1395. }
  1396. localStorage.setItem("modules", JSON.stringify(checkModule));
  1397. break;
  1398. }
  1399. } catch (e) {}
  1400. },
  1401. getDeviceParameter: function (devname, paraname) {
  1402. if (devname && devname.length) {
  1403. var params = ftui.deviceStates[devname];
  1404. return (params && params[paraname]) ? params[paraname] : null;
  1405. }
  1406. return null;
  1407. },
  1408. loadPlugin: function (name, area) {
  1409. var deferredLoad = new $.Deferred();
  1410. ftui.log(2, 'Start load plugin "' + name + '" for area "' + area + '"');
  1411. //ftui.toast(name);
  1412. // get the plugin
  1413. ftui.dynamicload(ftui.config.basedir + "js/widget_" + name + ".js", true).done(function () {
  1414. // get all dependencies of this plugin
  1415. var depsPromises = [];
  1416. var getDependencies = window["depends_" + name];
  1417. // load all dependencies recursive before
  1418. if ($.isFunction(getDependencies)) {
  1419. var deps = getDependencies();
  1420. if (deps) {
  1421. deps = ($.isArray(deps)) ? deps : [deps];
  1422. $.map(deps, function (dep, i) {
  1423. if (dep.match(new RegExp('^.*\.(js|css)$'))) {
  1424. depsPromises.push(ftui.dynamicload(dep, false));
  1425. } else {
  1426. depsPromises.push(ftui.loadPlugin(dep));
  1427. }
  1428. });
  1429. }
  1430. } else {
  1431. ftui.log(2, "function depends_" + name + " not found (maybe ok)");
  1432. }
  1433. $.when.apply(this, depsPromises).always(function () {
  1434. var module = (window["Modul_" + name]) ? new window["Modul_" + name]() : null;
  1435. if (module) {
  1436. if (area !== void 0) {
  1437. // add only real widgets not dependencies
  1438. plugins.addModule(module);
  1439. if (ftui.isValid(area))
  1440. module.area = area;
  1441. ftui.log(1, 'Try to init plugin: ' + name);
  1442. module.init();
  1443. // update all what we have until now
  1444. for (var key in module.subscriptions) {
  1445. module.update(module.subscriptions[key].device, module.subscriptions[key].reading);
  1446. }
  1447. }
  1448. ftui.log(1, 'Finished load plugin "' + name + '" for area "' + area + '"');
  1449. $('[data-type="' + name + '"]', area).removeClass('widget-hide');
  1450. } else {
  1451. ftui.log(1, 'Failed to load plugin "' + name + '" for area "' + area + '"');
  1452. }
  1453. deferredLoad.resolve();
  1454. });
  1455. })
  1456. .fail(function () {
  1457. ftui.toast('Failed to load plugin : ' + name);
  1458. ftui.log(1, 'Failed to load plugin : ' + name + ' - add <script src="js/widget_' + name +
  1459. '.js" defer></script> do your page, to see more informations about this failure');
  1460. deferredLoad.resolve();
  1461. });
  1462. // return with promise to deliver the plugin deferred
  1463. return deferredLoad.promise();
  1464. },
  1465. dynamicload: function (url, async) {
  1466. ftui.log(3, 'dynamic load file:' + url + ' / async:' + async);
  1467. var deferred = new $.Deferred();
  1468. var isAdded = false;
  1469. // check if it is already included
  1470. for (var i = 0, len = ftui.scripts.length; i < len; i++) {
  1471. if (ftui.scripts[i].url === url) {
  1472. isAdded = true;
  1473. break;
  1474. }
  1475. }
  1476. if (!isAdded) {
  1477. // not yet -> load
  1478. if (url.match(new RegExp('^.*\.(js)$'))) {
  1479. var script = document.createElement("script");
  1480. script.type = "text/javascript";
  1481. script.async = (async) ? true : false;
  1482. script.src = url;
  1483. script.onload = function () {
  1484. ftui.log(3, 'dynamidynamic load done:' + url);
  1485. deferred.resolve();
  1486. };
  1487. document.getElementsByTagName('head')[0].appendChild(script);
  1488. } else {
  1489. var link = document.createElement('link');
  1490. link.rel = 'stylesheet';
  1491. link.type = 'text/css';
  1492. link.href = url;
  1493. link.media = 'all';
  1494. deferred.resolve();
  1495. document.getElementsByTagName('head')[0].appendChild(link);
  1496. }
  1497. var scriptObject = {};
  1498. scriptObject.deferred = deferred;
  1499. scriptObject.url = url;
  1500. ftui.scripts.push(scriptObject);
  1501. } else {
  1502. // already loaded
  1503. ftui.log(3, 'dynamic load not neccesary for:' + url);
  1504. deferred = ftui.scripts[i].deferred;
  1505. }
  1506. return deferred.promise();
  1507. },
  1508. getCSrf: function () {
  1509. $.ajax({
  1510. 'url': ftui.config.fhemDir,
  1511. 'type': 'GET',
  1512. cache: false,
  1513. username: ftui.config.username,
  1514. password: ftui.config.password,
  1515. data: {
  1516. XHR: "1"
  1517. },
  1518. 'success': function (data, textStatus, jqXHR) {
  1519. ftui.config.csrf = jqXHR.getResponseHeader('X-FHEM-csrfToken');
  1520. ftui.log(1, 'Got csrf from FHEM:' + ftui.config.csrf);
  1521. }
  1522. }).fail(function (jqXHR, textStatus, errorThrown) {
  1523. ftui.log(1, "Failed to get csrfToken: " + textStatus + ": " + errorThrown);
  1524. ftui.config.shortPollDelay = 30000;
  1525. });
  1526. },
  1527. healthCheck: function () {
  1528. var timeDiff = new Date() - ftui.poll.long.lastEventTimestamp;
  1529. if (timeDiff / 1000 > ftui.config.maxLongpollAge &&
  1530. ftui.config.maxLongpollAge > 0 &&
  1531. !ftui.config.DEMO &&
  1532. ftui.config.doLongPoll) {
  1533. ftui.log(1, 'No longpoll event since ' + timeDiff / 1000 + 'secondes -> restart polling');
  1534. ftui.setOnline();
  1535. ftui.restartLongPoll();
  1536. }
  1537. },
  1538. FS20: {
  1539. 'dimmerArray': [0, 6, 12, 18, 25, 31, 37, 43, 50, 56, 62, 68, 75, 81, 87, 93, 100],
  1540. 'dimmerValue': function (value) {
  1541. var idx = ftui.indexOfNumeric(this.dimmerArray, value);
  1542. return (idx > -1) ? this.dimmerArray[idx] : 0;
  1543. }
  1544. },
  1545. rgbToHsl: function (rgb) {
  1546. var r = parseInt(rgb.substring(0, 2), 16);
  1547. var g = parseInt(rgb.substring(2, 4), 16);
  1548. var b = parseInt(rgb.substring(4, 6), 16);
  1549. r /= 255;
  1550. g /= 255;
  1551. b /= 255;
  1552. var max = Math.max(r, g, b),
  1553. min = Math.min(r, g, b);
  1554. var h, s, l = (max + min) / 2;
  1555. if (max == min) {
  1556. h = s = 0; // achromatic
  1557. } else {
  1558. var d = max - min;
  1559. s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
  1560. switch (max) {
  1561. case r:
  1562. h = (g - b) / d + (g < b ? 6 : 0);
  1563. break;
  1564. case g:
  1565. h = (b - r) / d + 2;
  1566. break;
  1567. case b:
  1568. h = (r - g) / d + 4;
  1569. break;
  1570. }
  1571. h /= 6;
  1572. }
  1573. return [h, s, l];
  1574. },
  1575. hslToRgb: function (h, s, l) {
  1576. var r, g, b;
  1577. var hex = function (x) {
  1578. return ("0" + parseInt(x).toString(16)).slice(-2);
  1579. };
  1580. var hue2rgb;
  1581. if (s === 0) {
  1582. r = g = b = l; // achromatic
  1583. } else {
  1584. hue2rgb = function (p, q, t) {
  1585. if (t < 0) t += 1;
  1586. if (t > 1) t -= 1;
  1587. if (t < 1 / 6) return p + (q - p) * 6 * t;
  1588. if (t < 1 / 2) return q;
  1589. if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
  1590. return p;
  1591. };
  1592. var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
  1593. var p = 2 * l - q;
  1594. r = hue2rgb(p, q, h + 1 / 3);
  1595. g = hue2rgb(p, q, h);
  1596. b = hue2rgb(p, q, h - 1 / 3);
  1597. }
  1598. return [hex(Math.round(r * 255)), hex(Math.round(g * 255)), hex(Math.round(b * 255))].join('');
  1599. },
  1600. rgbToHex: function (rgb) {
  1601. var tokens = rgb.match(/^rgba?[\s+]?\([\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?/i);
  1602. return (tokens && tokens.length === 4) ? "#" +
  1603. ("0" + parseInt(tokens[1], 10).toString(16)).slice(-2) +
  1604. ("0" + parseInt(tokens[2], 10).toString(16)).slice(-2) +
  1605. ("0" + parseInt(tokens[3], 10).toString(16)).slice(-2) : rgb;
  1606. },
  1607. getGradientColor: function (start_color, end_color, percent) {
  1608. // strip the leading # if it's there
  1609. start_color = this.rgbToHex(start_color).replace(/^\s*#|\s*$/g, '');
  1610. end_color = this.rgbToHex(end_color).replace(/^\s*#|\s*$/g, '');
  1611. // convert 3 char codes --> 6, e.g. `E0F` --> `EE00FF`
  1612. if (start_color.length == 3) {
  1613. start_color = start_color.replace(/(.)/g, '$1$1');
  1614. }
  1615. if (end_color.length == 3) {
  1616. end_color = end_color.replace(/(.)/g, '$1$1');
  1617. }
  1618. // get colors
  1619. var start_red = parseInt(start_color.substr(0, 2), 16),
  1620. start_green = parseInt(start_color.substr(2, 2), 16),
  1621. start_blue = parseInt(start_color.substr(4, 2), 16);
  1622. var end_red = parseInt(end_color.substr(0, 2), 16),
  1623. end_green = parseInt(end_color.substr(2, 2), 16),
  1624. end_blue = parseInt(end_color.substr(4, 2), 16);
  1625. // calculate new color
  1626. var diff_red = end_red - start_red;
  1627. var diff_green = end_green - start_green;
  1628. var diff_blue = end_blue - start_blue;
  1629. diff_red = ((diff_red * percent) + start_red).toString(16).split('.')[0];
  1630. diff_green = ((diff_green * percent) + start_green).toString(16).split('.')[0];
  1631. diff_blue = ((diff_blue * percent) + start_blue).toString(16).split('.')[0];
  1632. // ensure 2 digits by color
  1633. if (diff_red.length == 1)
  1634. diff_red = '0' + diff_red;
  1635. if (diff_green.length == 1)
  1636. diff_green = '0' + diff_green;
  1637. if (diff_blue.length == 1)
  1638. diff_blue = '0' + diff_blue;
  1639. return '#' + diff_red + diff_green + diff_blue;
  1640. },
  1641. getPart: function (value, part) {
  1642. if (ftui.isValid(part)) {
  1643. if ($.isNumeric(part)) {
  1644. var tokens = (ftui.isValid(value)) ? value.toString().split(" ") : '';
  1645. return (tokens.length >= part && part > 0) ? tokens[part - 1] : value;
  1646. } else {
  1647. var ret = '';
  1648. if (ftui.isValid(value)) {
  1649. var matches = value.match(new RegExp('^' + part + '$'));
  1650. if (matches) {
  1651. for (var i = 1, len = matches.length; i < len; i++) {
  1652. ret += matches[i];
  1653. }
  1654. }
  1655. }
  1656. return ret;
  1657. }
  1658. }
  1659. return value;
  1660. },
  1661. showModal: function (modal) {
  1662. if (modal)
  1663. $("#shade").fadeIn(ftui.config.fadeTime);
  1664. else
  1665. $("#shade").fadeOut(ftui.config.fadeTime);
  1666. },
  1667. precision: function (a) {
  1668. var s = a + "",
  1669. d = s.indexOf('.') + 1;
  1670. return !d ? 0 : s.length - d;
  1671. },
  1672. // 1. numeric, 2. regex, 3. negation double, 4. indexof
  1673. indexOfGeneric: function (array, find) {
  1674. if (!array) return -1;
  1675. for (var i = 0, len = array.length; i < len; i++) {
  1676. // leave the loop on first none numeric item
  1677. if (!$.isNumeric(array[i]))
  1678. return ftui.indexOfRegex(array, find);
  1679. }
  1680. return ftui.indexOfNumeric(array, find);
  1681. },
  1682. indexOfNumeric: function (array, val) {
  1683. var ret = -1;
  1684. for (var i = 0, len = array.length; i < len; i++) {
  1685. if (Number(val) >= Number(array[i]))
  1686. ret = i;
  1687. }
  1688. return ret;
  1689. },
  1690. indexOfRegex: function (array, find) {
  1691. var len = array.length;
  1692. for (var i = 0; i < len; i++) {
  1693. try {
  1694. var match = find.match(new RegExp('^' + array[i] + '$'));
  1695. if (match)
  1696. return i;
  1697. } catch (e) {}
  1698. }
  1699. // negation double
  1700. if (len === 2 && array[0] === '!' + array[1] && find !== array[0]) {
  1701. return 0;
  1702. }
  1703. if (len === 2 && array[1] === '!' + array[0] && find !== array[1]) {
  1704. return 1;
  1705. }
  1706. // last chance: index of
  1707. return array.indexOf(find);
  1708. },
  1709. isValid: function (v) {
  1710. return (v !== void 0 && typeof v !== typeof notusedvar);
  1711. },
  1712. // global date format functions
  1713. dateFromString: function (str) {
  1714. var m = str.match(/(\d+)-(\d+)-(\d+)[_\s](\d+):(\d+):(\d+).*/);
  1715. var m2 = str.match(/^(\d+)$/);
  1716. var m3 = str.match(/(\d\d).(\d\d).(\d\d\d\d)/);
  1717. var offset = new Date().getTimezoneOffset();
  1718. return (m) ? new Date(+m[1], +m[2] - 1, +m[3], +m[4], +m[5], +m[6]) :
  1719. (m2) ? new Date(70, 0, 1, 0, 0, m2[1], 0) :
  1720. (m3) ? new Date(+m3[3], +m3[2] - 1, +m3[1], 0, -offset, 0, 0) : new Date();
  1721. },
  1722. diffMinutes: function (date1, date2) {
  1723. var diff = new Date(date2 - date1);
  1724. return (diff / 1000 / 60).toFixed(0);
  1725. },
  1726. diffSeconds: function (date1, date2) {
  1727. var diff = new Date(date2 - date1);
  1728. return (diff / 1000).toFixed(1);
  1729. },
  1730. durationFromSeconds: function (time) {
  1731. var hrs = Math.floor(time / 3600);
  1732. var mins = Math.floor((time % 3600) / 60);
  1733. var secs = time % 60;
  1734. var ret = "";
  1735. if (hrs > 0) {
  1736. ret += "" + hrs + ":" + (mins < 10 ? "0" : "");
  1737. }
  1738. ret += "" + mins + ":" + (secs < 10 ? "0" : "");
  1739. ret += "" + secs;
  1740. return ret;
  1741. },
  1742. mapColor: function (value) {
  1743. return ftui.getStyle('.' + value, 'color') || value;
  1744. },
  1745. round: function (number, precision) {
  1746. var shift = function (number, precision, reverseShift) {
  1747. if (reverseShift) {
  1748. precision = -precision;
  1749. }
  1750. var numArray = ("" + number).split("e");
  1751. return +(numArray[0] + "e" + (numArray[1] ? (+numArray[1] + precision) : precision));
  1752. };
  1753. return shift(Math.round(shift(number, precision, false)), precision, true);
  1754. },
  1755. parseJsonFromString: function (str) {
  1756. return JSON.parse(str);
  1757. },
  1758. getAndroidVersion: function (ua) {
  1759. ua = (ua || navigator.userAgent).toLowerCase();
  1760. var match = ua.match(/android\s([0-9\.]*)/);
  1761. return match ? match[1] : false;
  1762. },
  1763. getStyle: function (selector, prop) {
  1764. var props = ftui.config.styleCollection[selector];
  1765. var style = (props && props[prop]) ? props[prop] : null;
  1766. if (style === null) {
  1767. var reverseSelector = '.' + selector.split('.').reverse().join('.');
  1768. reverseSelector = reverseSelector.substring(0, reverseSelector.length - 1);
  1769. props = ftui.config.styleCollection[reverseSelector];
  1770. style = (props && props[prop]) ? props[prop] : null;
  1771. }
  1772. return style;
  1773. },
  1774. getClassColor: function (elem) {
  1775. for (var i = ftui.config.stdColors.length - 1; i >= 0; i -= 1) {
  1776. if (elem.hasClass(ftui.config.stdColors[i])) {
  1777. return ftui.getStyle('.' + ftui.config.stdColors[i], 'color');
  1778. }
  1779. }
  1780. return null;
  1781. },
  1782. getIconId: function (iconName) {
  1783. if (!iconName || iconName === '' || !$('link[href$="lib/font-awesome.min.css"]').length)
  1784. return "?";
  1785. var cssFile = $('link[href$="lib/font-awesome.min.css"]')[0];
  1786. if (cssFile && cssFile.sheet && cssFile.sheet.cssRules) {
  1787. var rules = cssFile.sheet.cssRules;
  1788. for (var rule in rules) {
  1789. if (rules[rule].selectorText && rules[rule].selectorText.match(new RegExp(iconName + ':'))) {
  1790. var id = rules[rule].style.content;
  1791. if (!id)
  1792. return iconName;
  1793. id = id.replace(/"/g, '').replace(/'/g, "");
  1794. return (/[^\u0000-\u00ff]/.test(id)) ? id :
  1795. String.fromCharCode(parseInt(id.replace('\\', ''), 16));
  1796. }
  1797. }
  1798. }
  1799. },
  1800. disableSelection: function () {
  1801. $("body").each(function () {
  1802. this.onselectstart = function () {
  1803. return false;
  1804. };
  1805. this.unselectable = "on";
  1806. $(this).css('-moz-user-select', 'none');
  1807. $(this).css('-webkit-user-select', 'none');
  1808. });
  1809. },
  1810. hideWidgets: function (area) {
  1811. $('[data-type]', area).addClass('widget-hide');
  1812. },
  1813. toast: function (text, error) {
  1814. //https://github.com/kamranahmedse/jquery-toast-plugin
  1815. if (ftui.config.TOAST !== 0) {
  1816. var tstack = ftui.config.TOAST;
  1817. if (ftui.config.TOAST == 1)
  1818. tstack = false;
  1819. if (error && error === 'error') {
  1820. if (f7) {
  1821. f7.ftui.addNotification({
  1822. title: 'FTUI',
  1823. message: text,
  1824. hold: 1500
  1825. });
  1826. } else if ($.toast) {
  1827. return $.toast({
  1828. heading: 'Error',
  1829. text: text,
  1830. hideAfter: 20000, // in milli seconds
  1831. icon: 'error',
  1832. loader: false,
  1833. position: ftui.config.toastPosition,
  1834. stack: tstack
  1835. });
  1836. }
  1837. } else
  1838. if (f7) {
  1839. f7.ftui.addNotification({
  1840. title: 'FTUI',
  1841. message: text,
  1842. hold: 1500
  1843. });
  1844. } else if ($.toast) {
  1845. return $.toast({
  1846. text: text,
  1847. loader: false,
  1848. position: ftui.config.toastPosition,
  1849. stack: tstack
  1850. });
  1851. }
  1852. }
  1853. },
  1854. log: function (level, text) {
  1855. if (ftui.config.debuglevel >= level)
  1856. console.log(text);
  1857. },
  1858. };
  1859. // global helper functions
  1860. String.prototype.toDate = function () {
  1861. return ftui.dateFromString(this);
  1862. };
  1863. String.prototype.parseJson = function () {
  1864. return ftui.parseJsonFromString(this);
  1865. };
  1866. String.prototype.toMinFromMs = function () {
  1867. var x = Number(this) / 1000;
  1868. var ss = (Math.floor(x % 60)).toString();
  1869. var mm = (Math.floor(x /= 60)).toString();
  1870. return mm + ":" + (ss[1] ? ss : "0" + ss[0]);
  1871. };
  1872. String.prototype.toMinFromSec = function () {
  1873. var x = Number(this);
  1874. var ss = (Math.floor(x % 60)).toString();
  1875. var mm = (Math.floor(x /= 60)).toString();
  1876. return mm + ":" + (ss[1] ? ss : "0" + ss[0]);
  1877. };
  1878. String.prototype.toHoursFromMin = function () {
  1879. var x = Number(this);
  1880. var hh = (Math.floor(x / 60)).toString();
  1881. var mm = (x - (hh * 60)).toString();
  1882. return hh + ":" + (mm[1] ? mm : "0" + mm[0]);
  1883. };
  1884. String.prototype.toHoursFromSec = function () {
  1885. var x = Number(this);
  1886. var hh = (Math.floor(x / 3600)).toString();
  1887. var ss = (Math.floor(x % 60)).toString();
  1888. var mm = (Math.floor(x / 60) - (hh * 60)).toString();
  1889. return hh + ":" + (mm[1] ? mm : "0" + mm[0]) + ":" + (ss[1] ? ss : "0" + ss[0]);
  1890. };
  1891. String.prototype.addFactor = function (factor) {
  1892. var x = Number(this);
  1893. return x * factor;
  1894. };
  1895. Date.prototype.addMinutes = function (minutes) {
  1896. return new Date(this.getTime() + minutes * 60000);
  1897. };
  1898. Date.prototype.ago = function (format) {
  1899. var now = new Date();
  1900. var ms = (now - this);
  1901. var x = ms / 1000;
  1902. var seconds = Math.floor(x % 60);
  1903. x /= 60;
  1904. var minutes = Math.floor(x % 60);
  1905. x /= 60;
  1906. var hours = Math.floor(x % 24);
  1907. x /= 24;
  1908. var days = Math.floor(x);
  1909. var strUnits = (ftui.config.lang === 'de') ? ['Tag(e)', 'Stunde(n)', 'Minute(n)', 'Sekunde(n)'] : ['day(s)', 'hour(s)', 'minute(s)',
  1910. 'second(s)'];
  1911. var ret;
  1912. if (ftui.isValid(format)) {
  1913. ret = format.replace('dd', days);
  1914. ret = ret.replace('hh', (hours > 9) ? hours : '0' + hours);
  1915. ret = ret.replace('mm', (minutes > 9) ? minutes : '0' + minutes);
  1916. ret = ret.replace('ss', (seconds > 9) ? seconds : '0' + seconds);
  1917. ret = ret.replace('h', hours);
  1918. ret = ret.replace('m', minutes);
  1919. ret = ret.replace('s', seconds);
  1920. } else {
  1921. ret = (days > 0) ? days + " " + strUnits[0] + " " : "";
  1922. ret += (hours > 0) ? hours + " " + strUnits[1] + " " : "";
  1923. ret += (minutes > 0) ? minutes + " " + strUnits[2] + " " : "";
  1924. ret += seconds + " " + strUnits[3];
  1925. }
  1926. return ret;
  1927. };
  1928. Date.prototype.format = function (format) {
  1929. var YYYY = this.getFullYear().toString();
  1930. var YY = this.getYear().toString();
  1931. var MM = (this.getMonth() + 1).toString(); // getMonth() is zero-based
  1932. var dd = this.getDate().toString();
  1933. var hh = this.getHours().toString();
  1934. var mm = this.getMinutes().toString();
  1935. var ss = this.getSeconds().toString();
  1936. var eeee = this.eeee();
  1937. var eee = this.eee();
  1938. var ee = this.ee();
  1939. var ret = format;
  1940. ret = ret.replace('DD', (dd > 9) ? dd : '0' + dd);
  1941. ret = ret.replace('D', dd);
  1942. ret = ret.replace('MM', (MM > 9) ? MM : '0' + MM);
  1943. ret = ret.replace('M', MM);
  1944. ret = ret.replace('YYYY', YYYY);
  1945. ret = ret.replace('YY', YY);
  1946. ret = ret.replace('hh', (hh > 9) ? hh : '0' + hh);
  1947. ret = ret.replace('mm', (mm > 9) ? mm : '0' + mm);
  1948. ret = ret.replace('ss', (ss > 9) ? ss : '0' + ss);
  1949. ret = ret.replace('h', hh);
  1950. ret = ret.replace('m', mm);
  1951. ret = ret.replace('s', ss);
  1952. ret = ret.replace('eeee', eeee);
  1953. ret = ret.replace('eee', eee);
  1954. ret = ret.replace('ee', ee);
  1955. return ret;
  1956. };
  1957. Date.prototype.yyyymmdd = function () {
  1958. var yyyy = this.getFullYear().toString();
  1959. var mm = (this.getMonth() + 1).toString(); // getMonth() is zero-based
  1960. var dd = this.getDate().toString();
  1961. return yyyy + '-' + (mm[1] ? mm : "0" + mm[0]) + '-' + (dd[1] ? dd : "0" + dd[0]); // padding
  1962. };
  1963. Date.prototype.ddmmyyyy = function () {
  1964. var yyyy = this.getFullYear().toString();
  1965. var mm = (this.getMonth() + 1).toString(); // getMonth() is zero-based
  1966. var dd = this.getDate().toString();
  1967. return (dd[1] ? dd : "0" + dd[0]) + '.' + (mm[1] ? mm : "0" + mm[0]) + '.' + yyyy; // padding
  1968. };
  1969. Date.prototype.hhmm = function () {
  1970. var hh = this.getHours().toString();
  1971. var mm = this.getMinutes().toString();
  1972. return (hh[1] ? hh : "0" + hh[0]) + ':' + (mm[1] ? mm : "0" + mm[0]); // padding
  1973. };
  1974. Date.prototype.hhmmss = function () {
  1975. var hh = this.getHours().toString();
  1976. var mm = this.getMinutes().toString();
  1977. var ss = this.getSeconds().toString();
  1978. return (hh[1] ? hh : "0" + hh[0]) + ':' + (mm[1] ? mm : "0" + mm[0]) + ':' + (ss[1] ? ss : "0" + ss[0]); // padding
  1979. };
  1980. Date.prototype.ddmm = function () {
  1981. var mm = (this.getMonth() + 1).toString(); // getMonth() is zero-based
  1982. var dd = this.getDate().toString();
  1983. return (dd[1] ? dd : "0" + dd[0]) + '.' + (mm[1] ? mm : "0" + mm[0]) + '.'; // padding
  1984. };
  1985. Date.prototype.ddmmhhmm = function () {
  1986. var MM = (this.getMonth() + 1).toString(); // getMonth() is zero-based
  1987. var dd = this.getDate().toString();
  1988. var hh = this.getHours().toString();
  1989. var mm = this.getMinutes().toString();
  1990. return (dd[1] ? dd : "0" + dd[0]) + '.' + (MM[1] ? MM : "0" + MM[0]) + '. ' +
  1991. (hh[1] ? hh : "0" + hh[0]) + ':' + (mm[1] ? mm : "0" + mm[0]);
  1992. };
  1993. Date.prototype.eeee = function () {
  1994. var weekday_de = ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'];
  1995. var weekday = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
  1996. if (ftui.config.lang === 'de')
  1997. return weekday_de[this.getDay()];
  1998. return weekday[this.getDay()];
  1999. };
  2000. Date.prototype.eee = function () {
  2001. var weekday_de = ['Son', 'Mon', 'Die', 'Mit', 'Don', 'Fre', 'Sam'];
  2002. var weekday = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
  2003. if (ftui.config.lang === 'de')
  2004. return weekday_de[this.getDay()];
  2005. return weekday[this.getDay()];
  2006. };
  2007. Date.prototype.ee = function () {
  2008. var weekday_de = ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'];
  2009. var weekday = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'];
  2010. if (ftui.config.lang === 'de')
  2011. return weekday_de[this.getDay()];
  2012. return weekday[this.getDay()];
  2013. };
  2014. function onjQueryLoaded() {
  2015. /* EVENTS */
  2016. // event "page is loaded" -> start FTUI
  2017. ftui.init();
  2018. $('.menu').on('click', function () {
  2019. $('.menu').toggleClass('show');
  2020. });
  2021. $(window).on('beforeunload', function () {
  2022. ftui.log(5, 'beforeunload');
  2023. ftui.setOffline();
  2024. });
  2025. $(window).on('online offline', function () {
  2026. ftui.log(5, 'online offline');
  2027. if (navigator.onLine)
  2028. ftui.setOnline();
  2029. else
  2030. ftui.setOffline();
  2031. });
  2032. window.onerror = function (msg, url, lineNo, columnNo, error) {
  2033. var file = url.split('/').pop();
  2034. ftui.toast([file + ':' + lineNo, error].join('<br/>'), 'error');
  2035. return false;
  2036. };
  2037. $.fn.once = function (a, b) {
  2038. return this.each(function () {
  2039. $(this).off(a).on(a, b);
  2040. });
  2041. };
  2042. // for widget
  2043. $.fn.widgetId = function () {
  2044. return [$(this).data('type'), ($(this).data('device') ? $(this).data('device').replace(' ', 'default') : 'default'), $(this).data('get'), $(this).index()].join('.');
  2045. };
  2046. $.fn.wgid = function () {
  2047. var elem = $(this);
  2048. if (!elem.isValidData('wgid')) {
  2049. var wgid = elem.data('type') + '_xxxx-xxxx-xxxx'.replace(/[xy]/g, function (c) {
  2050. var r = Math.random() * 16 | 0,
  2051. v = c == 'x' ? r : (r & 0x3 | 0x8);
  2052. return v.toString(16);
  2053. });
  2054. elem.attr('data-wgid', wgid);
  2055. }
  2056. return elem.data('wgid');
  2057. };
  2058. $.fn.filterData = function (key, value) {
  2059. return this.filter(function () {
  2060. return $(this).data(key) == value;
  2061. });
  2062. };
  2063. $.fn.filterDeviceReading = function (key, device, param) {
  2064. return $(this).filter(function () {
  2065. return $(this).matchDeviceReading(key, device, param);
  2066. });
  2067. };
  2068. $.fn.matchDeviceReading = function (key, device, param) {
  2069. var elem = $(this);
  2070. var value = elem.data(key);
  2071. return (String(value) === param && String(elem.data('device')) === device) ||
  2072. (value === device + ':' + param || value === '[' + device + ':' + param + ']') ||
  2073. ($.inArray(param, value) > -1 && String(elem.data('device')) === device) ||
  2074. ($.inArray(device + ':' + param, value) > -1);
  2075. };
  2076. $.fn.isValidData = function (key) {
  2077. return ($(this).data(key) !== void 0);
  2078. };
  2079. $.fn.isValidAttr = function (key) {
  2080. return ($(this).attr(key) !== void 0);
  2081. };
  2082. $.fn.initData = function (key, value) {
  2083. var elem = $(this);
  2084. elem.data(key, elem.isValidData(key) ? elem.data(key) : value);
  2085. return elem;
  2086. };
  2087. $.fn.reinitData = function (key, value) {
  2088. var elem = $(this),
  2089. attrKey = 'data-' + key;
  2090. elem.data(key, elem.isValidAttr(attrKey) ? elem.attr(attrKey) : value);
  2091. return elem;
  2092. };
  2093. $.fn.initClassColor = function (key) {
  2094. var elem = $(this),
  2095. value = ftui.getClassColor(elem);
  2096. if (value) elem.attr('data-' + key, value);
  2097. };
  2098. $.fn.mappedColor = function (key) {
  2099. return ftui.getStyle('.' + $(this).data(key), 'color') || $(this).data(key);
  2100. };
  2101. $.fn.matchingState = function (key, value) {
  2102. if (!ftui.isValid(value)) {
  2103. return '';
  2104. }
  2105. var elm = $(this);
  2106. var state = String(ftui.getPart(value, elm.data(key + '-part')));
  2107. var onData = elm.data(key + '-on');
  2108. var offData = elm.data(key + '-off');
  2109. var on = String(onData);
  2110. var temp, device, reading, param;
  2111. if (on.match(/:/)) {
  2112. temp = on.split(':');
  2113. device = temp[0].replace('[', '');
  2114. reading = temp[1].replace(']', '');
  2115. param = ftui.getDeviceParameter(device, reading);
  2116. if (param && ftui.isValid(param)) {
  2117. on = param.val;
  2118. }
  2119. }
  2120. var off = String(offData);
  2121. if (off.match(/:/)) {
  2122. temp = off.split(':');
  2123. device = temp[0].replace('[', '');
  2124. reading = temp[1].replace(']', '');
  2125. param = ftui.getDeviceParameter(device, reading);
  2126. if (param && ftui.isValid(param)) {
  2127. off = param.val;
  2128. }
  2129. }
  2130. if (ftui.isValid(onData)) {
  2131. if (state === on) {
  2132. return 'on';
  2133. } else if (state.match(new RegExp('^' + on + '$'))) {
  2134. return 'on';
  2135. }
  2136. }
  2137. if (ftui.isValid(offData)) {
  2138. if (state === off) {
  2139. return 'off';
  2140. } else if (state.match(new RegExp('^' + off + '$'))) {
  2141. return 'off';
  2142. }
  2143. }
  2144. if (ftui.isValid(onData) && ftui.isValid(offData)) {
  2145. if (on === '!off' && !state.match(new RegExp('^' + off + '$'))) {
  2146. return 'on';
  2147. } else if (off === '!on' && !state.match(new RegExp('^' + on + '$'))) {
  2148. return 'off';
  2149. } else if (on === '!' + off && !state.match(new RegExp('^' + off + '$'))) {
  2150. return 'on';
  2151. } else if (off === '!' + on && !state.match(new RegExp('^' + on + '$'))) {
  2152. return 'off';
  2153. }
  2154. }
  2155. };
  2156. $.fn.isUrlData = function (key) {
  2157. var data = $(this).data(key);
  2158. var regExURL = /^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]+$/;
  2159. return data && data.match(regExURL);
  2160. };
  2161. $.fn.isDeviceReading = function (key) {
  2162. var reading = $(this).data(key);
  2163. return reading && !$.isNumeric(reading) && typeof reading === 'string' && reading.match(/^[\w\s-.]+:[\w\s-]+$/);
  2164. };
  2165. $.fn.isExternData = function (key) {
  2166. var data = $(this).data(key);
  2167. if (!data) return '';
  2168. return (data.match(/^[#\.\[][^:]*$/));
  2169. };
  2170. $.fn.cleanWhitespace = function () {
  2171. var textNodes = this.contents().filter(
  2172. function () {
  2173. return (this.nodeType == 3 && !/\S/.test(this.nodeValue));
  2174. })
  2175. .remove();
  2176. return this;
  2177. };
  2178. $.fn.getReading = function (key, idx) {
  2179. var devname = String($(this).data('device')),
  2180. paraname = $(this).data(key);
  2181. if ($.isArray(paraname)) {
  2182. paraname = paraname[idx];
  2183. }
  2184. paraname = String(paraname);
  2185. if (paraname && paraname.match(/:/)) {
  2186. var temp = paraname.split(':');
  2187. devname = temp[0].replace('[', '');
  2188. paraname = temp[1].replace(']', '');
  2189. }
  2190. if (devname && devname.length) {
  2191. var params = ftui.deviceStates[devname];
  2192. return (params && params[paraname]) ? params[paraname] : {};
  2193. }
  2194. return {};
  2195. };
  2196. $.fn.valOfData = function (key) {
  2197. var data = $(this).data(key);
  2198. if (!ftui.isValid(data)) return '';
  2199. return (data.toString().match(/^[#\.\[][^:]*$/)) ? $(data).data('value') : data;
  2200. };
  2201. $.fn.transmitCommand = function () {
  2202. if ($(this).hasClass('notransmit')) return;
  2203. var cmdl = [$(this).valOfData('cmd'), $(this).valOfData('device') + $(this).valOfData('filter'), $(this).valOfData('set'), $(
  2204. this).valOfData('value')].join(' ');
  2205. ftui.setFhemStatus(cmdl);
  2206. ftui.toast(cmdl);
  2207. };
  2208. $.fn.otherThen = function (elem) {
  2209. return $(this).filter(function () {
  2210. var eq1 = $(this).wgid(),
  2211. eq2 = elem.wgid();
  2212. return eq1 !== eq2;
  2213. });
  2214. };
  2215. }
  2216. // detect self location
  2217. var src = document.querySelector('script[src*="fhem-tablet-ui"]').getAttribute('src');
  2218. var file = src.split('/').pop();
  2219. src = src.replace('/' + file, '');
  2220. var dir = src.split('/').pop();
  2221. ftui.config.basedir = src.replace(dir, '');
  2222. if (ftui.config.basedir === '') ftui.config.basedir = './';
  2223. console.log('Base dir: ' + ftui.config.basedir);
  2224. // load jQuery lib
  2225. if (!ftui.isValid(window.jQuery)) {
  2226. var script = document.createElement('script');
  2227. script.type = 'text/javascript';
  2228. script.onload = function () {
  2229. (function ($) {
  2230. $(document).ready(function () {
  2231. console.log('jQuery dynamically loaded');
  2232. onjQueryLoaded();
  2233. });
  2234. })(jQuery);
  2235. };
  2236. script.src = ftui.config.basedir + "lib/jquery.min.js";
  2237. document.getElementsByTagName('head')[0].appendChild(script);
  2238. } else {
  2239. $(document).ready(function () {
  2240. onjQueryLoaded();
  2241. });
  2242. }