fhemweb.js 55 KB

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