fhemweb.js 57 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993
  1. "use strict";
  2. var FW_version={};
  3. FW_version["fhemweb.js"] = "$Id: fhemweb.js 17308 2018-09-09 13:43:37Z rudolfkoenig $";
  4. var FW_serverGenerated;
  5. var FW_serverFirstMsg = (new Date()).getTime()/1000;
  6. var FW_serverLastMsg = FW_serverFirstMsg;
  7. var FW_isIE = (navigator.appVersion.indexOf("MSIE") > 0);
  8. var FW_isiOS = navigator.userAgent.match(/(iPad|iPhone|iPod)/);
  9. var FW_scripts = {}, FW_links = {};
  10. var FW_docReady = false, FW_longpollType, FW_csrfToken, FW_csrfOk=true;
  11. var FW_root = "/fhem"; // root
  12. var FW_availableJs={};
  13. var FW_urlParams={};
  14. var embedLoadRetry = 100;
  15. // createFn returns an HTML Element, which may contain
  16. // - setValueFn, which is called when data via longpoll arrives
  17. // - activateFn, which is called after the HTML element is part of the DOM.
  18. var FW_widgets = {
  19. select: { createFn:FW_createSelect },
  20. selectnumbers: { createFn:FW_createSelectNumbers, },
  21. slider: { createFn:FW_createSlider },
  22. time: { createFn:FW_createTime },
  23. noArg: { createFn:FW_createNoArg },
  24. multiple: { createFn:FW_createMultiple },
  25. "multiple-strict": { createFn:FW_createMultiple, second:true },
  26. textField: { createFn:FW_createTextField },
  27. textFieldNL: { createFn:FW_createTextField, second:true },
  28. "textField-long": { createFn:FW_createTextField, second:true },
  29. "textFieldNL-long":{ createFn:FW_createTextField, second:true }
  30. };
  31. window.onbeforeunload = function(e)
  32. {
  33. FW_leaving = 1;
  34. return undefined;
  35. }
  36. window.onerror = function(errMsg, url, lineno)
  37. {
  38. url = url.replace(/.*\//,'');
  39. if($("body").attr("data-confirmJSError") != 0)
  40. FW_okDialog(url+" line "+lineno+":<br>"+errMsg);
  41. }
  42. function
  43. FW_replaceWidgets(parent)
  44. {
  45. parent.find("div.fhemWidget").each(function() {
  46. var dev=$(this).attr("dev");
  47. var cmd=$(this).attr("cmd");
  48. var rd=$(this).attr("reading");
  49. var params = cmd.split(" ");
  50. var type=$(this).attr("type");
  51. if(type == undefined)
  52. type = "set";
  53. FW_replaceWidget(this, dev, $(this).attr("arg").split(","),
  54. $(this).attr("current"), rd, params[0], params.slice(1),
  55. function(arg) {
  56. FW_cmd(FW_root+"?cmd="+type+" "+dev+
  57. (params[0]=="state" ? "":" "+params[0])+" "+arg+"&XHR=1");
  58. });
  59. });
  60. }
  61. function
  62. FW_jqueryReadyFn()
  63. {
  64. if(FW_docReady) // loading fhemweb.js twice is hard to debug
  65. return;
  66. FW_docReady = true;
  67. FW_serverGenerated = $("body").attr("generated");
  68. FW_longpollType = $("body").attr("longpoll");
  69. var ajs = $("body").attr("data-availableJs");
  70. if(ajs) {
  71. ajs = ajs.split(",");
  72. for(var i1=0; i1<ajs.length; i1++)
  73. FW_availableJs[ajs[i1]] = 1;
  74. }
  75. if(FW_longpollType != "0")
  76. setTimeout("FW_longpoll()", 100);
  77. FW_csrfToken = $("body").attr('fwcsrf');
  78. $("a").each(function() { FW_replaceLink(this); })
  79. $("head script").each(function() {
  80. var sname = $(this).attr("src"),
  81. p = FW_scripts[sname];
  82. if(!p) {
  83. FW_scripts[sname] = { loaded:true };
  84. return;
  85. }
  86. FW_scripts[sname].loaded = true;
  87. if(p.callbacks && !p.called) {
  88. p.called = true; // Avoid endless loop
  89. for(var i1=0; i1< p.callbacks.length; i1++)
  90. if(p.callbacks[i1]) // pushing undefined callbacks on the stack is ok
  91. p.callbacks[i1]();
  92. delete(p.callbacks);
  93. }
  94. });
  95. $("head link").each(function() { FW_links[$(this).attr("href")] = 1 });
  96. $("div.makeSelect select").each(function() {
  97. FW_detailSelect(this);
  98. $(this).change(FW_detailSelect);
  99. });
  100. // Activate the widgets
  101. var r = $("head").attr("root");
  102. if(r)
  103. FW_root = r;
  104. FW_replaceWidgets($("html"));
  105. FW_confirmDelete();
  106. // Fix the td count by setting colspan on the last column
  107. $("table.block.wide").each(function(){ // table
  108. var id = $(this).attr("id");
  109. if(!id || id.indexOf("TYPE") != 0)
  110. return;
  111. var maxTd=0, tdCount=[], tbl = $(this);
  112. $(tbl).find("> tbody > tr").each(function(){ // count the td's
  113. var cnt = 0, row=this;
  114. $(row).find("> td").each(function(){
  115. var cs = $(this).attr("colspan");
  116. cnt += parseInt(cs ? cs : 1);
  117. });
  118. if(maxTd < cnt)
  119. maxTd = cnt;
  120. tdCount.push(cnt);
  121. });
  122. $(tbl).find("> tbody> tr").each(function(){ // set the colspan
  123. var tdc = tdCount.shift();
  124. $(this).find("> td:last").each(function(){
  125. var cs = $(this).attr("colspan");
  126. $(this).attr("colspan", maxTd-tdc+(cs ? parseInt(cs) : 1));
  127. });
  128. });
  129. });
  130. $("form input.get[type=submit]").click(function(e) { //"get" via XHR to dialog
  131. e.preventDefault();
  132. var cmd = "", el=this;
  133. $(el).parent().find("input,[name]").each(function() {
  134. cmd += (cmd?"&":"")+encodeURIComponent($(this).attr("name"))+
  135. "="+encodeURIComponent($(this).val());
  136. });
  137. FW_cmd(FW_root+"?"+cmd+"&XHR=1&addLinks=1", function(data) {
  138. if(!data.match(/^[\r\n]*$/)) // ignore empty answers
  139. FW_okDialog('<pre>'+data+'</pre>', el);
  140. });
  141. });
  142. $("#saveCheck")
  143. .css("cursor", "pointer")
  144. .click(function(){
  145. var parent = this;
  146. FW_cmd(FW_root+"?cmd=save ?&XHR=1", function(data) {
  147. FW_okDialog('<pre>'+data+'</pre>',parent);
  148. });
  149. });
  150. $("form").each(function(){ // main input special cases
  151. var input = $(this).find("input.maininput");
  152. if(!input.length)
  153. return;
  154. $(this).on("submit", function(e) {
  155. var val = $(input).val();
  156. if(val.match(/^\s*ver.*/)) { // version
  157. e.preventDefault();
  158. $(input).val("");
  159. return FW_showVersion(val);
  160. } else if(val.match(/^\s*shutdown/)) { // shutdown
  161. FW_cmd(FW_root+"?XHR=1&cmd="+val);
  162. $(input).val("");
  163. return false;
  164. } else if(val.match(/^\s*l\s/)) { // l dev
  165. var m = val.match(/^\s*l\s+(.*)/);
  166. location.href = FW_root+"?detail="+m[1];
  167. e.preventDefault();
  168. return false;
  169. } else if(val.match(/^\s*get\s+/)) { // get
  170. // make get use xhr instead of reload
  171. //return true;
  172. FW_cmd(FW_root+"?cmd="+encodeURIComponent(val)+"&XHR=1", function(data){
  173. if( !data.match( /^<html>.*<\/html>/ ) ) {
  174. data = data.replace( '<', '&lt;' );
  175. data = '<pre>'+data+'</pre>';
  176. }
  177. if( location.href.indexOf('?') === -1 )
  178. $('#content').html(data);
  179. else
  180. FW_okDialog(data);
  181. });
  182. e.preventDefault();
  183. $(input).val("");
  184. return false;
  185. }
  186. return true;
  187. });
  188. });
  189. $("div.devSpecHelp a").each(function(){ // Help on detail window
  190. var dev = FW_getLink(this).split("#").pop();
  191. $(this).unbind("click");
  192. $(this).attr("href", "#"); // Desktop: show underlined Text
  193. $(this).removeAttr("onclick");
  194. $(this).click(function(evt){
  195. if($("#devSpecHelp").length) {
  196. $("#devSpecHelp").remove();
  197. return;
  198. }
  199. FW_getHelp(dev, function(data){
  200. $("#content").append('<div id="devSpecHelp"></div>');
  201. $("#devSpecHelp").html(data);
  202. var off = $("#devSpecHelp").position().top-20;
  203. $('body, html').animate({scrollTop:off}, 500);
  204. });
  205. });
  206. });
  207. $("table.attributes tr div.dname") // Click on attribute fills input value
  208. .each(function(){
  209. $(this)
  210. .html('<a>'+$(this).html()+'</a>')
  211. .css({cursor:"pointer"})
  212. .click(function(){
  213. var attrName = $(this).text();
  214. var sel = "#sel_attr"+$(this).attr("data-name").replace(/\./g,'_');
  215. if($(sel+" option[value='"+attrName+"']").length == 0)
  216. $(sel).append('<option value="'+attrName+'">'+attrName+'</option>');
  217. $(sel).val(attrName);
  218. FW_detailSelect(sel, true);
  219. });
  220. });
  221. $("[name=icon-filter]").on("change keyup paste", function() {
  222. clearTimeout($.data(this, 'delayTimer'));
  223. var wait = setTimeout(FW_filterIcons, 300);
  224. $(this).data('delayTimer', wait);
  225. });
  226. $("pre.motd").each(function(){ // add links for passwort setting
  227. var txt = $(this).text();
  228. txt = txt.replace(/(configuring|define|attr) .*/g, function(x) {
  229. return "<a href='#'>"+x+"</a>";
  230. });
  231. $(this).html(txt);
  232. $(this).find("a").click(function(){
  233. var txt = $(this).text();
  234. var ma = txt.match(/configuring.*device (.*)/); // ??
  235. if(ma)
  236. location.href = FW_root+"?detail="+ma[1];
  237. FW_cmd(FW_root+"?cmd="+encodeURIComponent(txt)+"&XHR=1",
  238. function(data){
  239. if(txt.indexOf("attr") == 0) $("pre.motd").html("");
  240. if(txt.indexOf("define") == 0)
  241. location.href = FW_root+"?detail=allowed";
  242. });
  243. });
  244. });
  245. var sa = location.search.substring(1).split("&");
  246. for(var i = 0; i < sa.length; i++) {
  247. var kv = sa[i].split("=");
  248. FW_urlParams[kv[0]] = kv[1];
  249. }
  250. $("select[id^=sel_attr],select[id^=sel_set],select[id^=sel_get]")
  251. .change(function(){ // online help
  252. var val = $(this).val();
  253. var m = $(this).attr("id").match(/sel_(set|get|attr)(.*)/);
  254. if(!m)
  255. return;
  256. $("#devSpecHelp").remove();
  257. var sel = this;
  258. FW_getHelp(m[2], function(data) {
  259. var mm = data.match(new RegExp('<a[^"]*"'+val+'[^A-Za-z_0-9]'));
  260. if(mm == null) {
  261. data = "";
  262. } else {
  263. data = data.substr(mm.index);
  264. var o1 = data.indexOf('<a', 1);
  265. if(o1 > 0)
  266. data = data.substr(0,o1);
  267. }
  268. if(data) {
  269. $(sel).closest("div[cmd='"+m[1]+"']")
  270. .after('<div class="makeTable" id="devSpecHelp"></div>')
  271. $("#devSpecHelp").html(data);
  272. }
  273. });
  274. });
  275. FW_smallScreenCommands();
  276. FW_inlineModify();
  277. FW_rawDef();
  278. FW_treeMenu();
  279. // automatic reload for style change
  280. if(location.search.indexOf("cmd=style%20select") > 0) {
  281. $('a[href*="style set"],a[onclick*="style set"]').each(function(){
  282. var href = $(this).attr("href");
  283. if(!href && (href = $(this).attr("onclick")))
  284. href = href.substr(15,href.length-16);
  285. $(this).click(function(e){
  286. e.preventDefault();
  287. FW_cmd(href+"&XHR=1", function(data) { location.reload(true); });
  288. });
  289. });
  290. }
  291. }
  292. var FW_helpData;
  293. function
  294. FW_getHelp(dev, fn)
  295. {
  296. if(FW_helpData)
  297. return fn(FW_helpData);
  298. FW_cmd(FW_root+"?cmd=help "+dev+"&XHR=1", function(data) {
  299. FW_helpData = data;
  300. return fn(FW_helpData);
  301. });
  302. }
  303. function
  304. FW_showVersion(val)
  305. {
  306. FW_cmd(FW_root+"?cmd="+encodeURIComponent(val)+"&XHR=1", function(data){
  307. var list = Object.keys(FW_version);
  308. list.sort();
  309. for(var i1=0; i1<list.length; i1++) {
  310. var ma = /\$Id: ([^ ]*) (.*) \$/.exec(FW_version[list[i1]]);
  311. if(ma) {
  312. if(ma[1].length < 26)
  313. ma[1] = (ma[1]+" ").substr(0,26);
  314. data += "\n"+ma[1]+" "+ma[2];
  315. }
  316. }
  317. FW_okDialog('<pre>'+data+'</pre>');
  318. });
  319. return false;
  320. }
  321. function
  322. FW_filterIcons()
  323. {
  324. var icons = $('.dist[title]');
  325. icons.show();
  326. var filterText = $('[name=icon-filter]').val();
  327. if (filterText != '') {
  328. var re = RegExp(filterText,"i");
  329. icons.filter(function() {
  330. return !re.test(this.title);
  331. }).hide();
  332. }
  333. }
  334. function
  335. FW_confirmDelete()
  336. {
  337. var b = $("body");
  338. var cd = $(b).attr("data-confirmDelete");
  339. if(!cd || cd == 0)
  340. return;
  341. var wn = $(b).attr("data-webName");
  342. $("div#content").find("a").each(function(){
  343. var href = $(this).attr("href");
  344. if(!href)
  345. return;
  346. var ma = $(this).attr("href").match(/.*cmd[^=]*=(delete[^&]*).*$/);
  347. if(!ma || ma.length != 2)
  348. return;
  349. $(this).attr("href", "#");
  350. $(this).unbind("click");
  351. $(this).click(function(e){
  352. e.preventDefault();
  353. var div = $("<div id='FW_okDialog'>");
  354. $(div).html("Do you really want to "+ma[1]+"?<br><br>"+
  355. "<input type='checkbox' name='noconf'> Skip this dialog in the future");
  356. $("body").append(div);
  357. function
  358. doClose()
  359. {
  360. if($(div).find("input:checked").length)
  361. FW_cmd(FW_root+"?cmd=attr "+wn+" confirmDelete 0&XHR=1");
  362. $(this).dialog("close"); $(div).remove();
  363. }
  364. $(div).dialog({
  365. dialogClass:"no-close", modal:true, width:"auto", closeOnEscape:true,
  366. maxWidth:$(window).width()*0.9, maxHeight:$(window).height()*0.9,
  367. buttons: [
  368. {text:"Yes", click:function(){ location.href = ma[0]; doClose(); }},
  369. {text:"No", click:function(){ doClose(); }}]
  370. });
  371. });
  372. });
  373. }
  374. // Show the webCmd list in a dialog if: smallScreen & hiddenroom=detail & room
  375. function
  376. FW_smallScreenCommands()
  377. {
  378. if($("div#menu select").length == 0 || // not SmallScreen
  379. $("div#content").attr("room") == undefined || // not room Overview
  380. $("div#content div.col1 a").length > 0) // no hiddenroom=detail
  381. return;
  382. $("div#content div.col1").each(function(){
  383. var tr = $(this).closest("tr");
  384. if($(tr).find("> td").length <= 2)
  385. return;
  386. $(this).html("<a href='#'>"+$(this).html()+"</a>");
  387. $(this).find("a").click(function(){
  388. var t = $("<table></table>"), row=0;
  389. $(tr).find("> td").each(function(){
  390. $(t).append("<tr></tr>");
  391. if(row++ == 0) {
  392. $(t).find("tr:last").append($(this).find("a").html());
  393. } else {
  394. $(this).attr("data-orig", 1);
  395. this.orig=$(this).parent();
  396. $(t).find("tr:last").append($(this).detach());
  397. }
  398. });
  399. FW_okDialog(t, this, function(){
  400. $("#FW_okDialog [data-orig]").each(function(){
  401. $(this).detach().appendTo(this.orig);
  402. });
  403. });
  404. });
  405. });
  406. }
  407. if(window.jQuery) {
  408. $(document).ready(FW_jqueryReadyFn);
  409. } else {
  410. // FLOORPLAN compatibility
  411. loadScript("pgm2/jquery.min.js", function() {
  412. loadScript("pgm2/jquery-ui.min.js", function() {
  413. FW_jqueryReadyFn();
  414. }, true);
  415. }, true);
  416. }
  417. // FLOORPLAN compatibility
  418. function
  419. FW_delayedStart()
  420. {
  421. setTimeout("FW_longpoll()", 100);
  422. }
  423. function
  424. log(txt)
  425. {
  426. var d = new Date();
  427. var ms = ("000"+(d.getMilliseconds()%1000));
  428. ms = ms.substr(ms.length-3,3);
  429. txt = d.toTimeString().substring(0,8)+"."+ms+" "+txt;
  430. if(typeof window.console != "undefined")
  431. console.log(txt);
  432. }
  433. function
  434. addcsrf(arg)
  435. {
  436. if(typeof FW_csrfToken != "undefined") {
  437. arg = arg.replace(/&fwcsrf=[^&]*/,'');
  438. arg += '&fwcsrf='+encodeURIComponent(FW_csrfToken);
  439. }
  440. return arg;
  441. }
  442. function
  443. FW_csrfRefresh(callback)
  444. {
  445. log("FW_csrfRefresh, last was "+(FW_csrfOk ? "ok":"bad"));
  446. if(!FW_csrfOk) // avoid endless loop
  447. return;
  448. $.ajax({
  449. url:location.pathname+"?XHR=1",
  450. success: function(data, textStatus, request){
  451. FW_csrfToken = request.getResponseHeader('x-fhem-csrftoken');
  452. FW_csrfOk = false;
  453. if(callback)
  454. callback();
  455. }
  456. });
  457. }
  458. function
  459. FW_cmd(arg, callback)
  460. {
  461. if(arg.length < 120)
  462. log("FW_cmd:"+arg);
  463. else
  464. log("FW_cmd:"+arg.substr(0,120)+"...");
  465. $.ajax({
  466. url:addcsrf(arg)+'&fw_id='+$("body").attr('fw_id'),
  467. method:'POST',
  468. success: function(data, textStatus, req){
  469. FW_csrfOk = true;
  470. if(callback)
  471. callback(req.responseText);
  472. else if(req.responseText)
  473. FW_errmsg(req.responseText, 5000);
  474. },
  475. error:function(xhr, status, err) {
  476. if(xhr.status == 400 && typeof FW_csrfToken != "undefined") {
  477. FW_csrfToken = "";
  478. FW_csrfRefresh(function(){FW_cmd(arg, callback)});
  479. }
  480. }
  481. });
  482. }
  483. function
  484. FW_errmsg(txt, timeout)
  485. {
  486. log("ERRMSG:"+txt+"<");
  487. var errmsg = document.getElementById("errmsg");
  488. if(!errmsg) {
  489. if(txt == "")
  490. return;
  491. errmsg = document.createElement('div');
  492. errmsg.setAttribute("id","errmsg");
  493. document.body.appendChild(errmsg);
  494. }
  495. if(txt == "") {
  496. document.body.removeChild(errmsg);
  497. return;
  498. }
  499. errmsg.innerHTML = txt;
  500. if(timeout)
  501. setTimeout("FW_errmsg('')", timeout);
  502. }
  503. function
  504. FW_okDialog(txt, parent, removeFn)
  505. {
  506. var div = $("<div id='FW_okDialog'>");
  507. $(div).html(txt);
  508. $("body").append(div);
  509. var oldPos = $("body").scrollTop();
  510. $(div).dialog({
  511. dialogClass:"no-close", modal:true, width:"auto", closeOnEscape:true,
  512. maxWidth:$(window).width()*0.9, maxHeight:$(window).height()*0.9,
  513. buttons: [{text:"OK", click:function(){
  514. $(this).dialog("close");
  515. if(removeFn)
  516. removeFn();
  517. $(div).remove();
  518. }}]
  519. });
  520. FW_replaceWidgets(div);
  521. $(div).find("a").each(function(){FW_replaceLink(this);}); //Forum #33766
  522. if(parent)
  523. $(div).dialog( "option", "position", {
  524. my: "left top", at: "right bottom",
  525. of: parent, collision: "flipfit"
  526. });
  527. setTimeout(function(){$("body").scrollTop(oldPos);}, 1); // Not ideal.
  528. }
  529. function
  530. FW_menu(evt, el, arr, dis, fn, embedEl)
  531. {
  532. if(!embedEl)
  533. evt.stopPropagation();
  534. if($("#fwmenu").length) {
  535. delfwmenu();
  536. return;
  537. }
  538. var html = '<ul id="fwmenu">';
  539. for(var i=0; i<arr.length; i++) {
  540. html+='<li class="'+ ((dis && dis[i]) ? 'ui-state-disabled' : '')+'">'+
  541. '<a row="'+i+'" href="#">'+arr[i]+'</a></li>';
  542. }
  543. html += '</ul>';
  544. $("body").append(html);
  545. function
  546. delfwmenu()
  547. {
  548. $("ul#fwmenu").remove();
  549. $('html').unbind('click.fwmenu');
  550. }
  551. var wt = $(window).scrollTop();
  552. $("#fwmenu")
  553. .menu({
  554. select: function(e,ui) { // changes the scrollTop();
  555. e.stopPropagation();
  556. fn($(e.currentTarget).find("[row]").attr("row"));
  557. delfwmenu();
  558. setTimeout(function(){ $(window).scrollTop(wt) }, 1); // Bug in select?
  559. }
  560. });
  561. var off = $(el).offset();
  562. if(embedEl) {
  563. var embOff = $(embedEl).offset();
  564. off.top += embOff.top;
  565. off.left += embOff.left;
  566. }
  567. var dH = $("#fwmenu").height(), dW = $("#fwmenu").width(),
  568. wH = $(window).height(), wW = $(window).width();
  569. var ey = off.top+dH+20, ex = off.left+dW;
  570. if(ex>wW && ey>wH) { off.top -= dH; off.left -= (dW+16);
  571. } else if(ey > wH) { off.top -= dH; off.left += 20;
  572. } else if(ex > wW) { off.left -= (dW+16);
  573. } else { off.top += 20;
  574. }
  575. $("#fwmenu").css(off);
  576. $('html').bind('click.fwmenu', function() { delfwmenu(); });
  577. }
  578. function
  579. FW_getLink(el)
  580. {
  581. var attr = $(el).attr("href");
  582. if(!attr) {
  583. attr = $(el).attr("onclick"); // Tablet/smallScreen version
  584. if(!attr)
  585. return "";
  586. attr = attr.replace(/^location.href='/,'');
  587. attr = attr.replace(/'$/,'');
  588. }
  589. return attr;
  590. }
  591. function
  592. FW_replaceLink(el)
  593. {
  594. var attr = FW_getLink(el);
  595. if(!attr)
  596. return;
  597. var ma = attr.match(/^(.*\?)(cmd[^=]*=.*)$/);
  598. if(ma == null || ma.length == 0 || !ma[2].match(/=(save|set)/)) {
  599. ma = attr.match(new RegExp("^"+FW_root)); // Avoid "Connection lost" @iOS
  600. if(ma) {
  601. $(el).click(function(e) {
  602. // Open link in window/tab, Forum #39154
  603. if(e.shiftKey || e.ctrlKey || e.metaKey || e.button == 1)
  604. return;
  605. e.preventDefault();
  606. FW_leaving = 1;
  607. if($(el).attr("target") == "_blank") {
  608. window.open(attr, '_blank').focus();
  609. } else {
  610. location.href = attr;
  611. }
  612. });
  613. }
  614. return;
  615. }
  616. $(el).removeAttr("href");
  617. $(el).removeAttr("onclick");
  618. $(el).click(function() {
  619. FW_cmd(attr+"&XHR=1", function(txt){
  620. if(!txt)
  621. return;
  622. if(ma[2].match(/=set/)) // Forum #38875
  623. FW_okDialog('<pre>'+txt+'<pre>', el);
  624. else
  625. FW_errmsg(txt, 5000);
  626. });
  627. });
  628. $(el).css("cursor", "pointer");
  629. }
  630. function
  631. FW_htmlQuote(text)
  632. {
  633. return text.replace(/&/g, '&amp;') // Same as in 01_FHEMWEB
  634. .replace(/</g, '&lt;')
  635. .replace(/>/g, '&gt;');
  636. }
  637. function
  638. FW_inlineModify() // Do not generate a new HTML page upon pressing modify
  639. {
  640. var cm;
  641. if( typeof AddCodeMirror == 'function' ) {
  642. // init codemirror for FW_style edit textarea
  643. AddCodeMirror($('textarea[name="data"]'));
  644. }
  645. $('#DEFa').click(function(){
  646. var old = $('#edit').css('display');
  647. $('#edit').css('display', old=='none' ? 'block' : 'none');
  648. $('#disp').css('display', old=='none' ? 'none' : 'block');
  649. if( typeof AddCodeMirror == 'function' ) {
  650. var s=document.getElementById("edit").getElementsByTagName("textarea");
  651. AddCodeMirror(s[0], function(pcm) {cm = pcm;});
  652. }
  653. });
  654. $("div input.psc[type=submit]:not(.get)").click(function(e){
  655. e.preventDefault();
  656. var newDef = typeof cm !== 'undefined' ?
  657. cm.getValue() : $(this).closest("form").find("textarea").val();
  658. var cmd = $(this).attr("name")+"="+$(this).attr("value")+" "+newDef;
  659. var isDef = true, reloadIfOk = false;
  660. if(newDef == undefined || $(this).attr("value").indexOf("modify") != 0) {
  661. isDef = false;
  662. var div = $(this).closest("div.makeSelect");
  663. var devName = $(div).attr("dev"),
  664. cmd = $(div).attr("cmd");
  665. var sel = $(this).closest("form").find("select");
  666. var arg = $(sel).val();
  667. var ifid = (devName+"-"+arg).replace(/([^_a-z0-9])/gi,
  668. function(m){ return "\\"+m });
  669. if($(".dval[informid="+ifid+"]").length == 0) {
  670. if(cmd == "attr") {
  671. reloadIfOk = true;
  672. } else {
  673. $(this).unbind('click').click();// No element found to replace, reload
  674. return;
  675. }
  676. }
  677. newDef = $(this).closest("form").find("input:text").val();
  678. if(newDef == undefined)
  679. newDef = $(this).closest("form").find("[name^=val]").val();
  680. cmd = $(this).attr("name")+"="+cmd+" "+devName+" "+arg+" "+newDef;
  681. }
  682. FW_cmd(FW_root+"?"+encodeURIComponent(cmd)+"&XHR=1", function(resp){
  683. if(!resp && reloadIfOk)
  684. location.reload();
  685. if(resp) {
  686. resp = FW_htmlQuote(resp);
  687. if(resp.indexOf("\n") >= 0)
  688. resp = '<pre>'+resp+'</pre>';
  689. return FW_okDialog(resp);
  690. }
  691. if(isDef) {
  692. if(newDef.indexOf("\n") >= 0)
  693. newDef = '<pre>'+newDef+'</pre>';
  694. else
  695. newDef = FW_htmlQuote(newDef);
  696. $("div#disp").html(newDef).css("display", "");
  697. $("div#edit").css("display", "none");
  698. }
  699. });
  700. });
  701. }
  702. function
  703. FW_rawDef()
  704. {
  705. $("div.rawDef a").each(function(){ // Help on detail window
  706. var dev = FW_getLink(this).split(" ").pop().split("&")[0];
  707. $(this).unbind("click");
  708. $(this).attr("href", "#"); // Desktop: show underlined Text
  709. $(this).removeAttr("onclick");
  710. $(this).click(function(evt){
  711. if($("#rawDef").length) {
  712. $("#rawDef").remove();
  713. return;
  714. }
  715. var textAreaStyle = typeof AddCodeMirror == 'function'?'opacity:0':'';
  716. $("#content").append('<div id="rawDef">'+
  717. '<textarea id="td_rawDef" rows="25" cols="60" style="width:99%; '+
  718. textAreaStyle+'"/>'+
  719. '<button>Execute commands</button>'+
  720. ' Dump "Probably associated with" too <input type="checkbox">'+
  721. '<br><br></div>');
  722. var cmVar;
  723. function
  724. fillData(opt)
  725. {
  726. var s = $('#rawDef textarea');
  727. FW_cmd(FW_root+"?cmd=list "+opt+" "+dev+"&XHR=1", function(data) {
  728. var re = new RegExp("^define", "gm");
  729. data = data.replace(re, "defmod");
  730. s.val(data);
  731. var off = $("#rawDef").position().top-20;
  732. $('body, html').animate({scrollTop:off}, 500);
  733. $("#rawDef button").hide();
  734. var propertychange = function() {
  735. var nData = $("#rawDef textarea").val();
  736. if(nData != data)
  737. $("#rawDef button").show();
  738. else
  739. $("#rawDef button").hide();
  740. };
  741. s.bind('input propertychange', propertychange);
  742. if(cmVar) {
  743. cmVar.setValue(data);
  744. } else if(typeof AddCodeMirror == 'function') {
  745. AddCodeMirror(s, function(cm) {
  746. cmVar = cm;
  747. cm.on("change", function() {
  748. s.val(cm.getValue());
  749. propertychange();
  750. })
  751. });
  752. }
  753. });
  754. }
  755. fillData("-r");
  756. $("#rawDef input").click(function(){fillData(this.checked ?"-R":"-r")});
  757. $("#rawDef button").click(function(){
  758. var data = $("#rawDef textarea").val();
  759. var arr = data.split("\n"), str="", i1=-1;
  760. function
  761. doNext()
  762. {
  763. if(++i1 >= arr.length) {
  764. return FW_okDialog("Executed everything, no errors found.");
  765. }
  766. str += arr[i1];
  767. if(arr[i1].charAt(arr[i1].length-1) === "\\") {
  768. str += "\n";
  769. return doNext();
  770. }
  771. if(str != "") {
  772. str = str.replace(/\\\n/g, "\n")
  773. .replace(/;;/g, ";");
  774. FW_cmd(FW_root+"?cmd."+dev+"="+encodeURIComponent(str)+"&XHR=1",
  775. function(r){
  776. if(r)
  777. return FW_okDialog('<pre>'+r+'</pre>');
  778. str = "";
  779. doNext();
  780. });
  781. } else {
  782. doNext();
  783. }
  784. }
  785. doNext();
  786. });
  787. });
  788. });
  789. }
  790. var FW_arrowDown, FW_arrowRight;
  791. function
  792. FW_treeMenu()
  793. {
  794. var a = $("a").get(0);
  795. var col = 'rgb(39, 135, 38)';
  796. if(window.getComputedStyle && a)
  797. col = getComputedStyle(a,null).getPropertyValue('color');
  798. FW_arrowRight = 'data:image/svg+xml;utf8,<svg viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path fill="gray" d="M1171 960q0 13-10 23l-466 466q-10 10-23 10t-23-10l-50-50q-10-10-10-23t10-23l393-393-393-393q-10-10-10-23t10-23l50-50q10-10 23-10t23 10l466 466q10 10 10 23z"/></svg>'
  799. .replace('gray', col);
  800. FW_arrowDown =FW_arrowRight.replace('/>',' transform="rotate(90,896,896)"/>');
  801. var fnd;
  802. $("div#menu table.room").each(function(){ // one loop per Block
  803. var t = this, ma = {};
  804. $(t).find("td > div > a > span").each(function(e){
  805. var span = this, spanTxt = $(span).text().replace(/,/g,'');
  806. var ta = spanTxt.split("->");
  807. if(ta.length <= 1)
  808. return;
  809. fnd = true;
  810. var nxt="", lst="", tr=$(span).closest("tr");
  811. for(var i1=0; i1<ta.length-1; i1++) {
  812. nxt += "->"+ta[i1];
  813. if(!ma[nxt]) {
  814. $(tr).before("<tr class='menuTree closed level"+i1+"' "+
  815. "data-mTree='"+lst+"' data-nxt='"+nxt+"'>"+
  816. "<td><div><a href='#'>"+ta[i1]+"</a><div></div></div></td></tr>");
  817. }
  818. ma[nxt] = true;
  819. lst = nxt;
  820. }
  821. $(span).html(ta[ta.length-1]);
  822. $(tr).attr("data-mTree", nxt)
  823. .addClass("menuTree level"+(ta.length-1));
  824. });
  825. });
  826. if(fnd) {
  827. $("head").append(
  828. "<style>"+
  829. "tr.menuTree { cursor:pointer; }"+
  830. "tr.menuTree.level1 > td > div { margin-left:10px; }"+
  831. "tr.menuTree.level2 > td > div { margin-left:20px; }"+
  832. "tr.menuTree.level3 > td > div { margin-left:30px; }"+
  833. "tr.menuTree.open { font-weight: bold; }"+
  834. "tr.menuTree > td > div > div { "+
  835. "display:inline-block; width:1em; height:1em; float:right;"+
  836. "background-size: contain; background-repeat: no-repeat;"+
  837. "}"+
  838. "</style>");
  839. var t = $("div#menu table.room");
  840. $(t).find("tr[data-mTree]").not(".level0").hide();
  841. $(t).find("tr.menuTree").click(function(){treeClick(this)});
  842. $(t).find("tr.menuTree > td > div > div")
  843. .css("background-image", "url('"+FW_arrowRight+"')");
  844. var selRoom = $("div#content").attr("room");
  845. if(selRoom) {
  846. var ta = selRoom.split("->"), nxt="";
  847. for(var i1=0; i1<ta.length-1; i1++) {
  848. nxt += FW_escapeSelector("->"+ta[i1]);
  849. treeClick($(t).find("tr.menuTree[data-nxt="+nxt+"]"));
  850. }
  851. }
  852. }
  853. function
  854. treeClick(el)
  855. {
  856. var tgt = FW_escapeSelector($(el).attr("data-nxt"));
  857. if($(el).hasClass("closed")) {
  858. $(el).closest("table").find("tr[data-mTree="+tgt+"]").show();
  859. $(el).find("div>div").css("background-image", "url('"+FW_arrowDown+"')");
  860. } else {
  861. $(el).closest("table").find("tr[data-mTree^="+tgt+"]")
  862. .hide().addClass("closed");
  863. $(el).find("div>div").css("background-image", "url('"+FW_arrowRight+"')");
  864. }
  865. $(el).toggleClass("closed");
  866. $(el).toggleClass("open");
  867. };
  868. }
  869. function
  870. FW_escapeSelector(s)
  871. {
  872. if(typeof s != 'string')
  873. return s;
  874. return s.replace(/[ .#\[\]>]/g, function(r) { return '\\'+r });
  875. }
  876. /*************** LONGPOLL START **************/
  877. var FW_pollConn;
  878. var FW_longpollOffset = 0;
  879. var FW_leaving;
  880. var FW_lastDataTime=0;
  881. function
  882. FW_doUpdate(evt)
  883. {
  884. var errstr = "Connection lost, trying a reconnect every 5 seconds.";
  885. var input="";
  886. var retryTime = 5000;
  887. var now = new Date()/1000;
  888. function
  889. setValue(d) // is Callable from eval below
  890. {
  891. $("[informId='"+d[0]+"']").each(function(){
  892. if(this.setValueFn) { // change the select/etc value
  893. this.setValueFn(d[1].replace(/\n/g, '\u2424'));
  894. } else {
  895. if(d[2].match(/\n/) && !d[2].match(/<.*>/)) // format multiline
  896. d[2] = '<html><pre>'+d[2]+'</pre></html>';
  897. var ma = /^<html>([\s\S]*)<\/html>$/.exec(d[2]);
  898. if(!d[0].match("-")) // not a reading
  899. $(this).html(d[2]);
  900. else if(ma)
  901. $(this).html(ma[1]);
  902. else
  903. $(this).text(d[2]);
  904. if(d[0].match(/-ts$/)) // timestamps
  905. $(this).addClass('changed');
  906. $(this).find("a").each(function() { FW_replaceLink(this) });
  907. }
  908. });
  909. }
  910. // iOS closes HTTP after 60s idle, websocket after 240s idle
  911. if(now-FW_lastDataTime > 59) {
  912. errstr="";
  913. retryTime = 100;
  914. }
  915. FW_lastDataTime = now;
  916. // Websocket starts with Android 4.4, and IE10
  917. if(typeof WebSocket == "function" && evt && evt.target instanceof WebSocket) {
  918. if(evt.type == 'close' && !FW_leaving) {
  919. FW_errmsg(errstr, retryTime-100);
  920. FW_pollConn.close();
  921. FW_pollConn = undefined;
  922. setTimeout(FW_longpoll, retryTime);
  923. return;
  924. }
  925. input = evt.data;
  926. FW_longpollOffset = 0;
  927. } else if(FW_pollConn != undefined) {
  928. if(FW_pollConn.readyState == 4 && !FW_leaving) {
  929. if(FW_pollConn.status == "400") {
  930. location.reload();
  931. return;
  932. }
  933. FW_errmsg(errstr, retryTime-100);
  934. setTimeout(FW_longpoll, retryTime);
  935. return;
  936. }
  937. if(FW_pollConn.readyState != 3)
  938. return;
  939. input = FW_pollConn.responseText;
  940. }
  941. var devs = new Array();
  942. if(!input || input.length <= FW_longpollOffset)
  943. return;
  944. FW_serverLastMsg = (new Date()).getTime()/1000;
  945. for(;;) {
  946. var nOff = input.indexOf("\n", FW_longpollOffset);
  947. if(nOff < 0)
  948. break;
  949. var l = input.substr(FW_longpollOffset, nOff-FW_longpollOffset);
  950. FW_longpollOffset = nOff+1;
  951. log("Rcvd: "+(l.length>132 ? l.substring(0,132)+"...("+l.length+")":l));
  952. if(!l.length)
  953. continue;
  954. if(l.indexOf("<")== 0) { // HTML returned by proxy, if FHEM behind is dead
  955. FW_closeConn();
  956. FW_errmsg(errstr, retryTime-100);
  957. setTimeout(FW_longpoll, retryTime);
  958. return;
  959. }
  960. var d = JSON.parse(l);
  961. if(d.length != 3)
  962. continue;
  963. if( d[0].match(/^#FHEMWEB:/) ) {
  964. eval(d[1]);
  965. } else {
  966. setValue(d);
  967. }
  968. // updateLine is deprecated, use setValueFn
  969. for(var w in FW_widgets)
  970. if(FW_widgets[w].updateLine && !FW_widgets[w].second)
  971. FW_widgets[w].updateLine(d);
  972. devs.push(d);
  973. }
  974. // used for SVG to avoid double-reloads
  975. for(var w in FW_widgets)
  976. if(FW_widgets[w].updateDevs && !FW_widgets[w].second)
  977. FW_widgets[w].updateDevs(devs);
  978. // reset the connection to avoid memory problems
  979. if(FW_longpollOffset > 1024*1024 && FW_longpollOffset==input.length)
  980. FW_longpoll();
  981. }
  982. function
  983. FW_closeConn()
  984. {
  985. FW_leaving = 1;
  986. if(!FW_pollConn)
  987. return;
  988. if(typeof FW_pollConn.close == "function")
  989. FW_pollConn.close();
  990. else if(typeof FW_pollConn.abort == "function")
  991. FW_pollConn.abort();
  992. FW_pollConn = undefined;
  993. }
  994. function
  995. FW_longpoll()
  996. {
  997. FW_closeConn();
  998. FW_leaving = 0;
  999. FW_longpollOffset = 0;
  1000. // Build the notify filter for the backend
  1001. var filter = $("body").attr("longpollfilter");
  1002. if(filter == null)
  1003. filter = "";
  1004. var retry;
  1005. if(filter == "") {
  1006. $("embed").each(function() { // wait for all embeds to be there
  1007. if(retry)
  1008. return;
  1009. var ed = FW_getSVG(this);
  1010. if(!retry && ed == undefined && filter != ".*" && --embedLoadRetry > 0) {
  1011. retry = 1;
  1012. setTimeout(FW_longpoll, 100);
  1013. return;
  1014. }
  1015. if(ed && $(ed).find("svg[flog]").attr("flog"))
  1016. filter=".*";
  1017. });
  1018. if(retry)
  1019. return;
  1020. }
  1021. if(filter == "") {
  1022. if(FW_urlParams.room) filter="room="+FW_urlParams.room;
  1023. if(FW_urlParams.detail) filter=FW_urlParams.detail;
  1024. }
  1025. if($("#floorplan").length>0) //floorplan special
  1026. filter += ";iconPath="+$("body").attr("name");
  1027. if(filter == "") {
  1028. var content = document.getElementById("content");
  1029. if(content) {
  1030. var room = content.getAttribute("room");
  1031. if(room)
  1032. filter="room="+room;
  1033. }
  1034. }
  1035. var iP = $("body").attr("iconPath");
  1036. if(iP != null)
  1037. filter = filter +";iconPath="+iP;
  1038. var since = "null";
  1039. if(FW_serverGenerated)
  1040. since = FW_serverLastMsg + (FW_serverGenerated-FW_serverFirstMsg);
  1041. var query = "?XHR=1"+
  1042. "&inform=type=status;filter="+filter+";since="+since+";fmt=JSON"+
  1043. '&fw_id='+$("body").attr('fw_id')+
  1044. "&timestamp="+new Date().getTime();
  1045. var loc = (""+location).replace(/\?.*/,"");
  1046. if(typeof WebSocket == "function" && FW_longpollType == "websocket") {
  1047. FW_pollConn = new WebSocket(loc.replace(/[#&?].*/,'')
  1048. .replace(/^http/i, "ws")+query);
  1049. FW_pollConn.onclose =
  1050. FW_pollConn.onerror =
  1051. FW_pollConn.onmessage = FW_doUpdate;
  1052. FW_pollConn.onopen = function(){FW_wsPing(FW_pollConn);};
  1053. } else {
  1054. FW_pollConn = new XMLHttpRequest();
  1055. FW_pollConn.open("GET", location.pathname+query, true);
  1056. if(FW_pollConn.overrideMimeType) // Win 8.1, #66004
  1057. FW_pollConn.overrideMimeType("application/json");
  1058. FW_pollConn.onreadystatechange = FW_doUpdate;
  1059. FW_pollConn.send(null);
  1060. }
  1061. log("Inform-channel opened ("+(FW_longpollType==1 ? "HTTP":FW_longpollType)+
  1062. ") with filter "+filter);
  1063. }
  1064. function
  1065. FW_wsPing(conn) // idle websockets are closed by the browser after 55sec
  1066. {
  1067. if(!conn || conn.readyState != conn.OPEN)
  1068. return;
  1069. conn.send("\n");
  1070. // setTimeout(function(){FW_wsPing(conn);}, 30000);
  1071. }
  1072. /*************** LONGPOLL END **************/
  1073. /*************** WIDGETS START **************/
  1074. /*************** "Double" select in detail window ****/
  1075. function
  1076. FW_detailSelect(selEl, mayMissing)
  1077. {
  1078. if(selEl.target)
  1079. selEl = selEl.target;
  1080. var selVal = $(selEl).val();
  1081. var div = $(selEl).closest("div.makeSelect");
  1082. if(!div.attr("list")) // hiddenRoom=input
  1083. return;
  1084. var arg,
  1085. listArr = $(div).attr("list").split(" "),
  1086. devName = $(div).attr("dev"),
  1087. cmd = $(div).attr("cmd");
  1088. var i1;
  1089. for(i1=0; i1<listArr.length; i1++) {
  1090. arg = listArr[i1];
  1091. if(arg.indexOf(selVal) == 0 &&
  1092. (arg.length == selVal.length || arg[selVal.length] == ':'))
  1093. break;
  1094. }
  1095. var vArr = [];
  1096. if(i1==listArr.length && !mayMissing)
  1097. return;
  1098. if(i1<listArr.length) {
  1099. if(arg.length > selVal.length)
  1100. vArr = arg.substr(selVal.length+1).split(",");
  1101. }
  1102. FW_replaceWidget($(selEl).next(), devName, vArr,undefined,selVal,
  1103. undefined, undefined, undefined,
  1104. function(newEl) {
  1105. if(cmd == "attr")
  1106. FW_queryValue('{AttrVal("'+devName+'","'+selVal+'","")}', newEl);
  1107. if(cmd == "set")
  1108. FW_queryValue('{ReadingsVal("'+devName+'","'+selVal+'","")}', newEl);
  1109. });
  1110. }
  1111. function
  1112. FW_callCreateFn(elName, devName, vArr, currVal, set, params, cmd, finishFn)
  1113. {
  1114. for(var wn in FW_widgets) {
  1115. if(FW_widgets[wn].createFn && !FW_widgets[wn].second) {
  1116. var newEl = FW_widgets[wn].createFn(elName, devName, vArr,
  1117. currVal, set, params, cmd);
  1118. if(newEl)
  1119. return finishFn(wn, newEl);
  1120. }
  1121. }
  1122. var v0 = vArr[0].split("-")[0];
  1123. if(v0.indexOf("uzsu") == 0)
  1124. v0 = "uzsu";
  1125. if(FW_availableJs[v0]) {
  1126. loadScript("pgm2/fhemweb_"+v0+".js", function() {
  1127. if(FW_widgets[vArr[0]].createFn)
  1128. var newEl = FW_widgets[vArr[0]].createFn(elName, devName, vArr,
  1129. currVal, set, params, cmd);
  1130. finishFn(vArr[0], newEl);
  1131. });
  1132. } else {
  1133. finishFn();
  1134. }
  1135. }
  1136. function
  1137. FW_replaceWidget(oldEl,devName,vArr,currVal,reading,set,params,cmd,readyFn)
  1138. {
  1139. var elName = $(oldEl).attr("name");
  1140. if(!elName)
  1141. elName = $(oldEl).find("[name]").attr("name");
  1142. if(vArr.length == 0) { // No parameters, input field
  1143. var newEl = FW_createTextField(elName, devName, ["textField"], currVal,
  1144. set, params, cmd);
  1145. finishFn("textField", newEl);
  1146. } else {
  1147. return FW_callCreateFn(elName, devName, vArr, currVal, set,
  1148. params, cmd, finishFn);
  1149. }
  1150. function
  1151. finishFn(wn, newEl)
  1152. {
  1153. if(!newEl) {
  1154. vArr.unshift("select");
  1155. newEl = FW_createSelect(elName,devName,vArr,currVal,set,params,cmd);
  1156. wn = "select";
  1157. }
  1158. if(!newEl) { // Simple link
  1159. newEl = $('<div class="col3"><a style="cursor: pointer;">'+
  1160. set+' '+params.join(' ')+ '</a></div>');
  1161. $(newEl).click(function(arg) { cmd(params[0]) });
  1162. $(oldEl).replaceWith(newEl);
  1163. if(readyFn)
  1164. return readyFn(newEl);
  1165. return;
  1166. }
  1167. $(newEl).addClass(wn+"_widget");
  1168. if( $(newEl).find("[informId]").length==0 && !$(newEl).attr("informId") ) {
  1169. if(reading) {
  1170. var a = $(oldEl).closest("form").find("input[type=submit][value=attr]");
  1171. $(newEl).attr("informId", devName+(a.length?"-a-":"-")+reading);
  1172. }
  1173. var addTitle = $("body").attr("data-addHtmlTitle");
  1174. if(reading != "state" && addTitle==1)
  1175. $(newEl).attr("title", reading);
  1176. }
  1177. $(oldEl).replaceWith(newEl);
  1178. if(newEl.activateFn) // CSS is not applied if newEl is not in the document
  1179. newEl.activateFn();
  1180. if(readyFn)
  1181. readyFn(newEl);
  1182. }
  1183. }
  1184. function
  1185. FW_queryValue(cmd, el)
  1186. {
  1187. log("FW_queryValue:"+cmd);
  1188. var query = location.pathname+"?cmd="+encodeURIComponent(cmd)+"&XHR=1";
  1189. query = addcsrf(query);
  1190. var qConn = new XMLHttpRequest();
  1191. qConn.onreadystatechange = function() {
  1192. if(qConn.readyState != 4)
  1193. return;
  1194. var qResp = qConn.responseText.replace(/\n$/, '');
  1195. qResp = qResp.replace(/\n/g, '\u2424');
  1196. if(el.setValueFn)
  1197. el.setValueFn(qResp);
  1198. qConn.abort();
  1199. }
  1200. qConn.open("GET", query, true);
  1201. qConn.send(null);
  1202. }
  1203. /*************** TEXTFIELD **************/
  1204. function
  1205. FW_createTextField(elName, devName, vArr, currVal, set, params, cmd)
  1206. {
  1207. if(vArr.length != 1 ||
  1208. (vArr[0] != "textField" &&
  1209. vArr[0] != "textFieldNL" &&
  1210. vArr[0] != "textField-long" &&
  1211. vArr[0] != "textFieldNL-long") ||
  1212. (params && params.length))
  1213. return undefined;
  1214. var is_long = (vArr[0].indexOf("long") > 0);
  1215. var newEl = $("<div style='display:inline-block'>").get(0);
  1216. if(set && set != "state" && vArr[0].indexOf("NL") < 0)
  1217. $(newEl).append(set+":");
  1218. $(newEl).append('<input type="text" size="30">');
  1219. var inp = $(newEl).find("input").get(0);
  1220. if(elName)
  1221. $(inp).attr('name', elName);
  1222. if(currVal != undefined)
  1223. $(inp).val(currVal);
  1224. function addBlur() { if(cmd) $(inp).blur(function() { cmd($(inp).val()) }); };
  1225. newEl.setValueFn = function(arg){ $(inp).val(arg) };
  1226. addBlur();
  1227. var myFunc = function(){
  1228. $(inp).unbind("blur");
  1229. $('body').append(
  1230. '<div id="editdlg" style="display:none">'+
  1231. '<textarea id="td_longText" rows="25" cols="60" style="width:99%"/>'+
  1232. '</div>');
  1233. var txt = $(inp).val();
  1234. txt = txt.replace(/\u2424/g, '\n');
  1235. $("#td_longText").val(txt);
  1236. var cm;
  1237. if(typeof AddCodeMirror == 'function') {
  1238. AddCodeMirror($("#td_longText"), function(pcm) {cm = pcm;});
  1239. }
  1240. $('#editdlg').dialog(
  1241. { modal:true, closeOnEscape:true, width:$(window).width()*3/4,
  1242. height:$(window).height()*3/4,
  1243. close:function(){ $('#editdlg').remove(); },
  1244. buttons:[
  1245. { text:"Cancel", click:function(){
  1246. $(this).dialog('close');
  1247. addBlur();
  1248. }},
  1249. { text:"OK", click:function(){
  1250. if(cm)
  1251. $("#td_longText").val(cm.getValue());
  1252. var res=$("#td_longText").val();
  1253. res = res.replace(/\n/g, '\u2424' );
  1254. $(this).dialog('close');
  1255. $(inp).val(res);
  1256. addBlur();
  1257. }}]
  1258. });
  1259. };
  1260. if(is_long)
  1261. $(newEl).click(myFunc);
  1262. return newEl;
  1263. }
  1264. /*************** select **************/
  1265. function
  1266. FW_createSelect(elName, devName, vArr, currVal, set, params, cmd)
  1267. {
  1268. if(vArr.length < 2 || vArr[0] != "select" || (params && params.length))
  1269. return undefined;
  1270. var newEl = document.createElement('select');
  1271. var vHash = {};
  1272. for(var j=1; j < vArr.length; j++) {
  1273. var o = document.createElement('option');
  1274. o.text = o.value = vArr[j].replace(/#/g," ");
  1275. vHash[vArr[j]] = 1;
  1276. newEl.options[j-1] = o;
  1277. }
  1278. if(currVal)
  1279. $(newEl).val(currVal);
  1280. if(elName)
  1281. $(newEl).attr('name', elName);
  1282. if(cmd)
  1283. $(newEl).change(function(arg) { cmd($(newEl).val()) });
  1284. newEl.setValueFn = function(arg) { if(vHash[arg]) $(newEl).val(arg); };
  1285. return newEl;
  1286. }
  1287. /*************** selectNumbers **************/
  1288. // Syntax: selectnumbers,<min value>,<step|step of exponent>,<max value>,<number of digits after decimal point>,lin|log10
  1289. function
  1290. FW_createSelectNumbers(elName, devName, vArr, currVal, set, params, cmd)
  1291. {
  1292. if(vArr.length < 6 || vArr[0] != "selectnumbers" || (params && params.length))
  1293. return undefined;
  1294. var min = parseFloat(vArr[1]);
  1295. var stp = parseFloat(vArr[2]);
  1296. var max = parseFloat(vArr[3]);
  1297. var dp = parseFloat(vArr[4]); // decimal points
  1298. var fun = vArr[5]; // function
  1299. if(currVal != undefined)
  1300. currVal = currVal.replace(/[^\d.\-]/g, "");
  1301. currVal = (currVal==undefined || currVal=="") ? min : parseFloat(currVal);
  1302. if(max==min)
  1303. return undefined;
  1304. if(!(fun == "lin" || fun == "log10"))
  1305. return undefined;
  1306. if(currVal < min)
  1307. currVal = min;
  1308. if(currVal > max)
  1309. currVal = max;
  1310. var newEl = document.createElement('select');
  1311. var vHash = {};
  1312. var k = 0;
  1313. var v = 0;
  1314. if (fun == "lin") {
  1315. for(var j=min; j <= max; j+=stp) {
  1316. var o = document.createElement('option');
  1317. o.text = o.value = j.toFixed(dp);
  1318. vHash[j.toString()] = 1;
  1319. newEl.options[k] = o;
  1320. k++;
  1321. }
  1322. } else if (fun == "log10") {
  1323. if(min <= 0 || max <= 0)
  1324. return undefined;
  1325. for(var j=Math.log10(min); j <= Math.log10(max)+stp; j+=stp) {
  1326. var o = document.createElement('option');
  1327. var w = Math.pow(10, j)
  1328. if (w > max)
  1329. w = max;
  1330. if (v == w.toFixed(dp))
  1331. continue;
  1332. v = w.toFixed(dp);
  1333. o.text = o.value = v;
  1334. vHash[v] = 1;
  1335. newEl.options[k] = o;
  1336. k++;
  1337. }
  1338. }
  1339. if(currVal)
  1340. $(newEl).val(currVal.toFixed(dp));
  1341. if(elName)
  1342. $(newEl).attr('name', elName);
  1343. if(cmd)
  1344. $(newEl).change(function(arg) { cmd($(newEl).val()) });
  1345. newEl.setValueFn = function(arg) { if(vHash[arg]) $(newEl).val(arg); };
  1346. return newEl;
  1347. }
  1348. /*************** noArg **************/
  1349. function
  1350. FW_createNoArg(elName, devName, vArr, currVal, set, params, cmd)
  1351. {
  1352. if(vArr.length != 1 || vArr[0] != "noArg" || (params && params.length))
  1353. return undefined;
  1354. var newEl = $('<div style="display:none">').get(0);
  1355. if(elName)
  1356. $(newEl).append('<input type="hidden" name="'+elName+ '" value="">');
  1357. return(newEl);
  1358. }
  1359. /*************** slider **************/
  1360. function
  1361. FW_createSlider(elName, devName, vArr, currVal, set, params, cmd)
  1362. {
  1363. // min, step, max, float
  1364. if(vArr.length < 4 || vArr.length > 5 || vArr[0] != "slider" ||
  1365. (params && params.length))
  1366. return undefined;
  1367. var min = parseFloat(vArr[1]);
  1368. var stp = parseFloat(vArr[2]);
  1369. var max = parseFloat(vArr[3]);
  1370. var flt = (vArr.length == 5 && vArr[4] == "1");
  1371. var dp = 0; // decimal points for float
  1372. if(flt) {
  1373. var s = ""+stp;
  1374. if(s.indexOf(".") >= 0)
  1375. dp = s.substr(s.indexOf(".")+1).length;
  1376. }
  1377. if(currVal != undefined)
  1378. currVal = currVal.replace(/[^\d.\-]/g, "");
  1379. currVal = (currVal==undefined || currVal=="") ? min : parseFloat(currVal);
  1380. if(max==min)
  1381. return undefined;
  1382. if(currVal < min || currVal > max)
  1383. currVal = min;
  1384. var newEl = $('<div style="display:inline-block" tabindex="0">').get(0);
  1385. var slider = $('<div class="slider" id="slider.'+devName+'">').get(0);
  1386. $(newEl).append(slider);
  1387. var sh = $('<div class="handle">'+currVal+'</div>').get(0);
  1388. $(slider).append(sh);
  1389. if(elName)
  1390. $(newEl).append('<input type="hidden" name="'+elName+
  1391. '" value="'+currVal+'">');
  1392. var lastX=-1, offX=0, maxX=0, val=currVal;
  1393. newEl.activateFn = function() {
  1394. if(currVal < min || currVal > max)
  1395. return;
  1396. if(!slider.offsetWidth)
  1397. return setTimeout(newEl.activateFn, 1);
  1398. maxX = slider.offsetWidth-sh.offsetWidth;
  1399. offX = (currVal-min)*maxX/(max-min);
  1400. var strVal = (flt ? currVal.toFixed(dp) : ""+parseInt(currVal));
  1401. sh.innerHTML = strVal;
  1402. sh.setAttribute('style', 'left:'+offX+'px;');
  1403. if(elName)
  1404. slider.nextSibling.setAttribute('value', strVal);
  1405. }
  1406. $(newEl).keydown(function(e){
  1407. if(e.keyCode == 37) currVal -= stp;
  1408. else if(e.keyCode == 39) currVal += stp;
  1409. else return;
  1410. if(currVal < min) currVal = min;
  1411. if(currVal > max) currVal = max;
  1412. offX = (currVal-min)*maxX/(max-min);
  1413. var strVal = (flt ? currVal.toFixed(dp) : ""+parseInt(currVal));
  1414. sh.innerHTML = strVal;
  1415. sh.setAttribute('style', 'left:'+offX+'px;');
  1416. if(cmd)
  1417. cmd(strVal);
  1418. if(elName)
  1419. slider.nextSibling.setAttribute('value', strVal);
  1420. });
  1421. function
  1422. touchFn(e, fn)
  1423. {
  1424. e.preventDefault(); // Prevents Safari from scrolling!
  1425. if(e.touches == null || e.touches.length == 0)
  1426. return;
  1427. e.clientX = e.touches[0].clientX;
  1428. fn(e);
  1429. }
  1430. function
  1431. mouseDown(e)
  1432. {
  1433. var oldFn1 = document.onmousemove, oldFn2 = document.onmouseup,
  1434. oldFn3 = document.ontouchmove, oldFn4 = document.ontouchend;
  1435. e.stopPropagation(); // Dashboard fix
  1436. lastX = e.clientX; // Does not work on IE8
  1437. function
  1438. mouseMove(e)
  1439. {
  1440. e.stopPropagation(); // Dashboard fix
  1441. if(maxX == 0) // Forum #35846
  1442. maxX = slider.offsetWidth-sh.offsetWidth;
  1443. var diff = e.clientX-lastX; lastX = e.clientX;
  1444. offX += diff;
  1445. if(offX < 0) offX = 0;
  1446. if(offX > maxX) offX = maxX;
  1447. val = offX/maxX * (max-min);
  1448. val = flt ? (Math.floor(val/stp)*stp+min).toFixed(dp) :
  1449. (Math.floor(Math.floor(val/stp)*stp)+min);
  1450. sh.innerHTML = val;
  1451. sh.setAttribute('style', 'left:'+offX+'px;');
  1452. }
  1453. document.onmousemove = mouseMove;
  1454. document.ontouchmove = function(e) { touchFn(e, mouseMove); }
  1455. document.onmouseup = document.ontouchend = function(e)
  1456. {
  1457. e.stopPropagation(); // Dashboard fix
  1458. document.onmousemove = oldFn1; document.onmouseup = oldFn2;
  1459. document.ontouchmove = oldFn3; document.ontouchend = oldFn4;
  1460. if(cmd)
  1461. cmd(val);
  1462. if(elName)
  1463. slider.nextSibling.setAttribute('value', val);
  1464. };
  1465. };
  1466. sh.onselectstart = function() { return false; }
  1467. sh.onmousedown = mouseDown;
  1468. sh.ontouchstart = function(e) { touchFn(e, mouseDown); }
  1469. newEl.setValueFn = function(arg) {
  1470. var res = arg.match(/[\d.\-]+/); // extract first number
  1471. currVal = (res ? parseFloat(res[0]) : min);
  1472. if(currVal < min || currVal > max)
  1473. currVal = min;
  1474. newEl.activateFn();
  1475. };
  1476. return newEl;
  1477. }
  1478. /*************** TIME **************/
  1479. function
  1480. FW_createTime(elName, devName, vArr, currVal, set, params, cmd)
  1481. {
  1482. if(vArr.length != 1 || vArr[0] != "time" || (params && params.length))
  1483. return undefined;
  1484. var open="-", closed="+";
  1485. var newEl = document.createElement('div');
  1486. $(newEl).append('<input type="text" size="5">');
  1487. $(newEl).append('<input type="button" value="'+closed+'">');
  1488. var inp = $(newEl).find("[type=text]");
  1489. var btn = $(newEl).find("[type=button]");
  1490. currVal = (currVal ? currVal : "12:00")
  1491. .replace(/[^\d]*(\d\d):(\d\d).*/g,"$1:$2");
  1492. $(inp).val(currVal)
  1493. if(elName)
  1494. $(inp).attr("name", elName);
  1495. var hh, mm; // the slider elements
  1496. newEl.setValueFn = function(arg) {
  1497. arg = arg.replace(/[^\d]*(\d\d):(\d\d).*/g,"$1:$2");
  1498. $(inp).val(arg);
  1499. var hhmm = arg.split(":");
  1500. if(hhmm.length == 2 && hh && mm) {
  1501. hh.setValueFn(hhmm[0]);
  1502. mm.setValueFn(hhmm[1]);
  1503. }
  1504. };
  1505. $(btn).click(function(){ // Open/Close the slider view
  1506. var v = $(inp).val();
  1507. if($(btn).val() == open) {
  1508. $(btn).val(closed);
  1509. $(newEl).find(".timeSlider").remove();
  1510. hh = mm = undefined;
  1511. if(cmd)
  1512. cmd(v);
  1513. return;
  1514. }
  1515. $(btn).val(open);
  1516. if(v.indexOf(":") < 0) {
  1517. v = "12:00";
  1518. $(inp).val(v);
  1519. }
  1520. var hhmm = v.split(":");
  1521. function
  1522. tSet(idx, arg)
  1523. {
  1524. if((""+arg).length < 2)
  1525. arg = '0'+arg;
  1526. hhmm[idx] = arg;
  1527. $(inp).val(hhmm.join(":"));
  1528. }
  1529. $(newEl).append('<div class="timeSlider">');
  1530. var ts = $(newEl).find(".timeSlider");
  1531. hh = FW_createSlider(undefined, devName+"HH", ["slider", 0, 1, 23],
  1532. hhmm[0], undefined, params, function(arg) { tSet(0, arg) });
  1533. mm = FW_createSlider(undefined, devName+"MM", ["slider", 0, 5, 55],
  1534. hhmm[1], undefined, params, function(arg) { tSet(1, arg) });
  1535. $(ts).append("<br>"); $(ts).append(hh); hh.activateFn();
  1536. $(ts).append("<br>"); $(ts).append(mm); mm.activateFn();
  1537. });
  1538. return newEl;
  1539. }
  1540. /*************** MULTIPLE **************/
  1541. function
  1542. FW_createMultiple(elName, devName, vArr, currVal, set, params, cmd)
  1543. {
  1544. if(vArr.length < 2 || (vArr[0]!="multiple" && vArr[0]!="multiple-strict") ||
  1545. (params && params.length))
  1546. return undefined;
  1547. var newEl = $('<input type="text" size="30" readonly>').get(0);
  1548. if(currVal)
  1549. $(newEl).val(currVal);
  1550. if(elName)
  1551. $(newEl).attr("name", elName);
  1552. newEl.setValueFn = function(arg){ $(newEl).val(arg) };
  1553. for(var i1=1; i1<vArr.length; i1++)
  1554. vArr[i1] = vArr[i1].replace(/#/g, " ");
  1555. $(newEl).focus(function(){
  1556. var sel = $(newEl).val().split(","), selObj={};
  1557. for(var i1=0; i1<sel.length; i1++)
  1558. selObj[sel[i1]] = 1;
  1559. var table = "";
  1560. for(var i1=1; i1<vArr.length; i1++) {
  1561. var v = vArr[i1];
  1562. table += '<tr>'+ // funny stuff for ios6 style, forum #23561
  1563. '<td><div class="checkbox">'+
  1564. '<input name="'+v+'" id="multiple_'+v+'" type="checkbox"'+
  1565. (selObj[v] ? " checked" : "")+'/>'+'</div></td>'+
  1566. '<td><label for="multiple_'+v+'">'+v+'</label></td></tr>';
  1567. delete(selObj[v]);
  1568. }
  1569. var selArr=[];
  1570. for(var i1 in selObj)
  1571. selArr.push(i1);
  1572. var strict = (vArr[0] == "multiple-strict");
  1573. $('body').append(
  1574. '<div id="multidlg" style="display:none">'+
  1575. '<table>'+table+'</table>'+(!strict ? '<input id="md_freeText" '+
  1576. 'value="'+selArr.join(',')+'"/>' : '')+
  1577. '</div>');
  1578. $('#multidlg').dialog(
  1579. { modal:true, closeOnEscape:false, maxHeight:$(window).height()*3/4,
  1580. buttons:[
  1581. { text:"Cancel", click:function(){ $('#multidlg').remove(); }},
  1582. { text:"OK", click:function(){
  1583. var res=[];
  1584. if($("#md_freeText").val())
  1585. res.push($("#md_freeText").val());
  1586. $("#multidlg table input").each(function(){
  1587. if($(this).prop("checked"))
  1588. res.push($(this).attr("name"));
  1589. });
  1590. $('#multidlg').remove();
  1591. $(newEl).val(res.join(","));
  1592. if(cmd)
  1593. cmd(res.join(","));
  1594. }}]});
  1595. });
  1596. return newEl;
  1597. }
  1598. /*************** WIDGETS END **************/
  1599. /*************** SCRIPT LOAD FUNCTIONS START **************/
  1600. function
  1601. loadScript(sname, callback, force)
  1602. {
  1603. var h = document.head || document.getElementsByTagName('head')[0];
  1604. sname = FW_root+"/"+sname;
  1605. if(FW_scripts[sname]) {
  1606. if(FW_scripts[sname].loaded) {
  1607. if(callback)
  1608. callback();
  1609. } else {
  1610. FW_scripts[sname].callbacks.push(callback);
  1611. }
  1612. return;
  1613. }
  1614. if(!FW_docReady && !force) {
  1615. FW_scripts[sname] = { callbacks:[ callback] };
  1616. return;
  1617. }
  1618. var script = document.createElement("script");
  1619. script.src = sname;
  1620. script.async = script.defer = false;
  1621. script.type = "text/javascript";
  1622. FW_scripts[sname] = { callbacks:[ callback] };
  1623. function
  1624. scriptLoaded()
  1625. {
  1626. var p = FW_scripts[sname];
  1627. p.loaded = true;
  1628. if(!p.called) {
  1629. p.called = true;
  1630. for(var i1=0; i1< p.callbacks.length; i1++)
  1631. if(p.callbacks[i1]) // pushing undefined callbacks on the stack is ok
  1632. p.callbacks[i1]();
  1633. }
  1634. delete(p.callbacks);
  1635. }
  1636. log("Loading script "+sname);
  1637. if(FW_isIE) {
  1638. script.onreadystatechange = function() {
  1639. if(script.readyState == 'loaded' || script.readyState == 'complete') {
  1640. script.onreadystatechange = null;
  1641. scriptLoaded();
  1642. }
  1643. }
  1644. } else {
  1645. if(FW_isiOS)
  1646. FW_closeConn();
  1647. script.onload = function(){
  1648. scriptLoaded();
  1649. }
  1650. }
  1651. h.appendChild(script);
  1652. }
  1653. function
  1654. loadLink(lname)
  1655. {
  1656. var h = document.head || document.getElementsByTagName('head')[0];
  1657. lname = FW_root+"/"+lname;
  1658. var arr = h.getElementsByTagName("link");
  1659. for(var i1=0; i1<arr.length; i1++)
  1660. if(lname == arr[i1].getAttribute("href"))
  1661. return;
  1662. var link = document.createElement("link");
  1663. link.href = lname;
  1664. link.rel = "stylesheet";
  1665. log("Loading link "+lname);
  1666. h.appendChild(link);
  1667. }
  1668. function
  1669. scriptAttribute(sname)
  1670. {
  1671. var attr="";
  1672. $("head script").each(function(){
  1673. var src = $(this).attr("src");
  1674. if(src && src.indexOf(sname) >= 0)
  1675. attr = $(this).attr("attr");
  1676. });
  1677. var ua={};
  1678. if(attr && attr != "") {
  1679. try {
  1680. ua=JSON.parse(attr);
  1681. } catch(e){
  1682. FW_errmsg(sname+" Parameter "+e,5000);
  1683. }
  1684. }
  1685. return ua;
  1686. }
  1687. /*************** SCRIPT LOAD FUNCTIONS END **************/
  1688. function
  1689. print_call_stack() {
  1690. var stack = new Error().stack;
  1691. console.log("PRINTING CALL STACK");
  1692. console.log( stack );
  1693. }
  1694. function
  1695. FW_getSVG(emb)
  1696. {
  1697. if(emb.contentDocument)
  1698. return emb.contentDocument;
  1699. if(typeof emb.getSVGDocument == "function") {
  1700. try {
  1701. return emb.getSVGDocument();
  1702. } catch(err) {
  1703. // dom not loaded -> fall through -> retry;
  1704. }
  1705. }
  1706. return undefined;
  1707. }
  1708. /*
  1709. =pod
  1710. =begin html
  1711. <li>noArg - show no input field.</li>
  1712. <li>time - show a JavaScript driven timepicker.<br>
  1713. Example: attr FS20dev widgetOverride on-till:time</li>
  1714. <li>textField - show an input field.<br>
  1715. Example: attr WEB widgetOverride room:textField</li>
  1716. <li>textFieldNL - show the input field and hide the label.</li>
  1717. <li>textField-long - show an input-field, but upon
  1718. clicking on the input field open a textArea (60x25).</li>
  1719. <li>textFieldNL-long - the behaviour is the same
  1720. as :textField-long, but no label is displayed.</li>
  1721. <li>slider,&lt;min&gt;,&lt;step&gt;,&lt;max&gt;[,1] - show
  1722. a JavaScript driven slider. The optional ,1 at the end
  1723. avoids the rounding of floating-point numbers.</li>
  1724. <li>multiple,&lt;val1&gt;,&lt;val2&gt;,..." - present a
  1725. multiple-value-selector with an additional textfield. The result is
  1726. comman separated.</li>
  1727. <li>multiple-strict,&lt;val1&gt;,&lt;val2&gt;,... - like :multiple, but
  1728. without the textfield.</li>
  1729. <li>selectnumbers,&lt;min&gt;,&lt;step&gt;,&lt;max&gt;,&lt;number of
  1730. digits after decimal point&gt;,lin|log10" - display a select widget
  1731. generated with values from min to max with step.<br>
  1732. lin generates a constantly increasing series. log10 generates an
  1733. exponentially increasing series to base 10, step is related to the
  1734. exponent, e.g. 0.0625.</li>
  1735. <li>select,&lt;val1&gt;,&lt;val2&gt;,... - show a dropdown with all values.
  1736. <b>NOTE</b>: this is also the fallback, if no modifier is found.</li>
  1737. =end html
  1738. =begin html_DE
  1739. <li>noArg - es wird kein weiteres Eingabefeld angezeigt.</li>
  1740. <li>time - zeigt ein Zeitauswahlmen&uuml;.
  1741. Beispiel: attr FS20dev widgetOverride on-till:time</li>
  1742. <li>textField - zeigt ein Eingabefeld.<br>
  1743. Beispiel: attr WEB widgetOverride room:textField</li>
  1744. <li>textField-long - ist wie textField, aber beim Click im Eingabefeld wird
  1745. ein Dialog mit einer HTML textarea (60x25) wird ge&ouml;ffnet.</li>
  1746. <li>slider,&lt;min&gt;,&lt;step&gt;,&lt;max&gt;[,1] - zeigt einen
  1747. Schieberegler. Das optionale 1 (isFloat) vermeidet eine Rundung der
  1748. Fliesskommazahlen.</li>
  1749. <li>multiple,&lt;val1&gt;,&lt;val2&gt;,... - zeigt eine Mehrfachauswahl mit
  1750. einem zus&auml;tzlichen Eingabefeld. Das Ergebnis ist Komma
  1751. separiert.</li>
  1752. <li>multiple-strict,&lt;val1&gt;,&lt;val2&gt;,... - ist wie :multiple,
  1753. blo&szlig; ohne Eingabefeld.</li>
  1754. <li>selectnumbers,&lt;min&gt;,&lt;step&gt;,&lt;max&gt;,&lt;number of
  1755. digits after decimal point&gt;,lin|log10" zeigt ein HTML-select mit einer
  1756. Zahlenreihe vom Wert min bis Wert max mit Schritten von step
  1757. angezeigt.<br>
  1758. Die Angabe lin erzeugt eine konstant ansteigende Reihe. Die Angabe
  1759. log10 erzeugt eine exponentiell ansteigende Reihe zur Basis 10,
  1760. step bezieht sich auf den Exponenten, z.B. 0.0625.</li>
  1761. <li>select,&lt;val1&gt;,&lt;val2&gt;,... - zeigt ein HTML select mit allen
  1762. Werten. <b>Achtung</b>: so ein Widget wird auch dann angezeigt, falls
  1763. kein passender Modifier gefunden wurde.</li>
  1764. =end html_DE
  1765. =cut
  1766. */