"use strict"; var FW_version={}; FW_version["fhemweb.js"] = "$Id$"; var FW_serverGenerated; var FW_serverFirstMsg = (new Date()).getTime()/1000; var FW_serverLastMsg = FW_serverFirstMsg; var FW_isIE = (navigator.appVersion.indexOf("MSIE") > 0); var FW_isiOS = navigator.userAgent.match(/(iPad|iPhone|iPod)/); var FW_scripts = {}, FW_links = {}; var FW_docReady = false, FW_longpollType, FW_csrfToken, FW_csrfOk=true; var FW_root = "/fhem"; // root var FW_availableJs={}; var FW_urlParams={}; var embedLoadRetry = 100; var FW_os = "unknown"; if(FW_isiOS) { FW_os = "iOS"; } else if(navigator.userAgent.indexOf("Android") >= 0) { FW_os = "android"; } else if(navigator.userAgent.indexOf("OS X") >= 0) { FW_os = "osx"; } else if(navigator.userAgent.indexOf("Windows") >= 0) { FW_os = "windows"; } else if(navigator.userAgent.indexOf("Linux") >= 0) { FW_os = "linux"; } // createFn returns an HTML Element, which may contain // - setValueFn, which is called when data via longpoll arrives // - activateFn, which is called after the HTML element is part of the DOM. var FW_widgets = { select: { createFn:FW_createSelect }, selectnumbers: { createFn:FW_createSelectNumbers, }, slider: { createFn:FW_createSlider }, time: { createFn:FW_createTime }, noArg: { createFn:FW_createNoArg }, multiple: { createFn:FW_createMultiple }, "multiple-strict": { createFn:FW_createMultiple, second:true }, textField: { createFn:FW_createTextField }, textFieldNL: { createFn:FW_createTextField, second:true }, "textField-long": { createFn:FW_createTextField, second:true }, "textFieldNL-long":{ createFn:FW_createTextField, second:true } }; window.onbeforeunload = function(e) { FW_leaving = 1; return undefined; } window.onerror = function(errMsg, url, lineno) { url = url.replace(/.*\//,''); if($("body").attr("data-confirmJSError") != 0) FW_okDialog(url+" line "+lineno+":<br>"+errMsg); } function FW_replaceWidgets(parent) { parent.find("div.fhemWidget").each(function() { var dev=$(this).attr("dev"); var cmd=$(this).attr("cmd"); var rd=$(this).attr("reading"); var params = cmd.split(" "); var type=$(this).attr("type"); if(type == undefined) type = "set"; FW_replaceWidget(this, dev, $(this).attr("arg").split(","), $(this).attr("current"), rd, params[0], params.slice(1), function(arg) { FW_cmd(FW_root+"?cmd="+type+" "+dev+ (params[0]=="state" ? "":" "+params[0])+" "+arg+"&XHR=1"); }); }); } function FW_jqueryReadyFn() { if(FW_docReady) // loading fhemweb.js twice is hard to debug return; FW_docReady = true; FW_serverGenerated = $("body").attr("generated"); FW_longpollType = $("body").attr("longpoll"); var ajs = $("body").attr("data-availableJs"); if(ajs) { ajs = ajs.split(","); for(var i1=0; i1<ajs.length; i1++) FW_availableJs[ajs[i1]] = 1; } if(FW_longpollType != "0") setTimeout("FW_longpoll()", 100); FW_csrfToken = $("body").attr('fwcsrf'); $("a").each(function() { FW_replaceLink(this); }) $("head script").each(function() { var sname = $(this).attr("src"), p = FW_scripts[sname]; if(!p) { FW_scripts[sname] = { loaded:true }; return; } FW_scripts[sname].loaded = true; if(p.callbacks && !p.called) { p.called = true; // Avoid endless loop for(var i1=0; i1< p.callbacks.length; i1++) if(p.callbacks[i1]) // pushing undefined callbacks on the stack is ok p.callbacks[i1](); delete(p.callbacks); } }); $("head link").each(function() { FW_links[$(this).attr("href")] = 1 }); $("div.makeSelect select").each(function() { FW_detailSelect(this); $(this).change(FW_detailSelect); }); // Activate the widgets var r = $("head").attr("root"); if(r) FW_root = r; FW_replaceWidgets($("html")); FW_confirmDelete(); // Fix the td count by setting colspan on the last column $("table.block.wide").each(function(){ // table var id = $(this).attr("id"); if(!id || id.indexOf("TYPE") != 0) return; var maxTd=0, tdCount=[], tbl = $(this); $(tbl).find("> tbody > tr").each(function(){ // count the td's var cnt = 0, row=this; $(row).find("> td").each(function(){ var cs = $(this).attr("colspan"); cnt += parseInt(cs ? cs : 1); }); if(maxTd < cnt) maxTd = cnt; tdCount.push(cnt); }); $(tbl).find("> tbody> tr").each(function(){ // set the colspan var tdc = tdCount.shift(); $(this).find("> td:last").each(function(){ var cs = $(this).attr("colspan"); $(this).attr("colspan", maxTd-tdc+(cs ? parseInt(cs) : 1)); }); }); }); $("form input.get[type=submit]").click(function(e) { //"get" via XHR to dialog e.preventDefault(); var cmd = "", el=this; $(el).parent().find("input,[name]").each(function() { cmd += (cmd?"&":"")+encodeURIComponent($(this).attr("name"))+ "="+encodeURIComponent($(this).val()); }); FW_cmd(FW_root+"?"+cmd+"&XHR=1&addLinks=1", function(data) { if(!data.match(/^[\r\n]*$/)) {// ignore empty answers var ma = /^<html>([\s\S]*)<\/html>/.exec(data); if(ma) { FW_okDialog(ma[1], el); } else { FW_okDialog('<pre>'+data+'</pre>', el); } } }); }); $("#saveCheck") .css("cursor", "pointer") .click(function(){ var parent = this; FW_cmd(FW_root+"?cmd=save ?&XHR=1", function(data) { FW_okDialog('<pre>'+data+'</pre>',parent); }); }); $("form").each(function(){ // main input special cases var input = $(this).find("input.maininput"); if(!input.length) return; $(this).on("submit", function(e) { var val = $(input).val(); if(val.match(/^\s*ver.*/)) { // version e.preventDefault(); $(input).val(""); return FW_showVersion(val); } else if(val.match(/^\s*shutdown/)) { // shutdown FW_cmd(FW_root+"?XHR=1&cmd="+val); $(input).val(""); return false; } else if(val.match(/^\s*l\s/)) { // l dev var m = val.match(/^\s*l\s+(.*)/); location.href = FW_root+"?detail="+m[1]; e.preventDefault(); return false; } else if(val.match(/^\s*get\s+/)) { // get // make get use xhr instead of reload //return true; FW_cmd(FW_root+"?cmd="+encodeURIComponent(val)+"&XHR=1", function(data){ if( !data.match( /^<html>[\s\S]*<\/html>/ ) ) { data = data.replace( '<', '<' ); data = '<pre>'+data+'</pre>'; } if( location.href.indexOf('?') === -1 ) $('#content').html(data); else FW_okDialog(data); }); e.preventDefault(); $(input).val(""); return false; } return true; }); }); $("div.devSpecHelp a").each(function(){ // Help on detail window var dev = FW_getLink(this).split("#").pop(); $(this).unbind("click"); $(this).attr("href", "#"); // Desktop: show underlined Text $(this).removeAttr("onclick"); $(this).click(function(evt){ if($("#devSpecHelp").length) { $("#devSpecHelp").remove(); return; } FW_getHelp(dev, function(data){ $("#content").append('<div id="devSpecHelp"></div>'); $("#devSpecHelp").html(data); var off = $("#devSpecHelp").position().top-20; $('body, html').animate({scrollTop:off}, 500); }); }); }); $("table.attributes tr div.dname") // Click on attribute fills input value .each(function(){ $(this) .html('<a>'+$(this).html()+'</a>') .css({cursor:"pointer"}) .click(function(){ var attrName = $(this).text(); var sel = "#sel_attr"+$(this).attr("data-name").replace(/\./g,'_'); if($(sel+" option[value='"+attrName+"']").length == 0) $(sel).append('<option value="'+attrName+'">'+attrName+'</option>'); $(sel).val(attrName); FW_detailSelect(sel, true); }); }); $("[name=icon-filter]").on("change keyup paste", function() { clearTimeout($.data(this, 'delayTimer')); var wait = setTimeout(FW_filterIcons, 300); $(this).data('delayTimer', wait); }); $("pre.motd").each(function(){ // add links for passwort setting var txt = $(this).text(); txt = txt.replace(/(configuring|define|attr) .*/g, function(x) { return "<a href='#'>"+x+"</a>"; }); $(this).html(txt); $(this).find("a").click(function(){ var txt = $(this).text(); var ma = txt.match(/configuring.*device (.*)/); // ?? if(ma) location.href = FW_root+"?detail="+ma[1]; FW_cmd(FW_root+"?cmd="+encodeURIComponent(txt)+"&XHR=1", function(data){ if(txt.indexOf("attr") == 0) $("pre.motd").html(""); if(txt.indexOf("define") == 0) location.href = FW_root+"?detail=allowed"; }); }); }); var sa = location.search.substring(1).split("&"); for(var i = 0; i < sa.length; i++) { var kv = sa[i].split("="); FW_urlParams[kv[0]] = kv[1]; } $("select[id^=sel_attr],select[id^=sel_set],select[id^=sel_get]") .change(function(){ // online help var val=$(this).val().replace(/[.\/(){}\[\]*]/g,function(a){return "\\"+a}); var m = $(this).attr("name").match(/arg.(set|get|attr)(.*)/); if(!m) return; $("#devSpecHelp").remove(); var sel = this; FW_getHelp(m[2], function(data) { // show either the next or the outer li $("#content") .append("<div id='workbench' style='display:none'></div>"); $("#content > #workbench").html(data); var aTag = $("#content > #workbench").find("a[name="+val+"]"); if($(aTag).length) { var liTag = $(aTag).next("li"); if(!$(liTag).length) liTag = $(aTag).parent("li"); if($(liTag).length) { $(sel).closest("div[cmd='"+m[1]+"']") .after('<div class="makeTable" id="devSpecHelp"></div>') $("#devSpecHelp").html($(liTag).html()); } } $("#content > #workbench").remove(); }); }); FW_smallScreenCommands(); FW_inlineModify(); FW_rawDef(); FW_treeMenu(); $("body").attr("data-os", FW_os); // automatic reload for style change if(location.search.indexOf("cmd=style%20select") > 0) { $('a[href*="style set"],a[onclick*="style set"]').each(function(){ var href = $(this).attr("href"); if(!href && (href = $(this).attr("onclick"))) href = href.substr(15,href.length-16); $(this).click(function(e){ e.preventDefault(); FW_cmd(href+"&XHR=1", function(data) { location.reload(true); }); }); }); } } var FW_helpData; function FW_getHelp(dev, fn) { if(FW_helpData) return fn(FW_helpData); FW_cmd(FW_root+"?cmd=help "+dev+"&XHR=1", function(data) { FW_helpData = data; return fn(FW_helpData); }); } function FW_showVersion(val) { FW_cmd(FW_root+"?cmd="+encodeURIComponent(val)+"&XHR=1", function(data){ var list = Object.keys(FW_version); list.sort(); for(var i1=0; i1<list.length; i1++) { var ma = /\$Id: ([^ ]*) (.*) \$/.exec(FW_version[list[i1]]); if(ma) { if(ma[1].length < 26) ma[1] = (ma[1]+" ").substr(0,26); data += "\n"+ma[1]+" "+ma[2]; } } FW_okDialog('<pre>'+data+'</pre>'); }); return false; } function FW_filterIcons() { var icons = $('.dist[title]'); icons.show(); var filterText = $('[name=icon-filter]').val(); if (filterText != '') { var re = RegExp(filterText,"i"); icons.filter(function() { return !re.test(this.title); }).hide(); } } function FW_confirmDelete() { var b = $("body"); var cd = $(b).attr("data-confirmDelete"); if(!cd || cd == 0) return; var wn = $(b).attr("data-webName"); $("div#content").find("a").each(function(){ var href = $(this).attr("href"); if(!href) return; var ma = $(this).attr("href").match(/.*cmd[^=]*=(delete[^&]*).*$/); if(!ma || ma.length != 2) return; $(this).attr("href", "#"); $(this).unbind("click"); $(this).click(function(e){ e.preventDefault(); var div = $("<div id='FW_okDialog'>"); $(div).html("Do you really want to "+ma[1]+"?<br><br>"+ "<input type='checkbox' name='noconf'> Skip this dialog in the future"); $("body").append(div); function doClose() { if($(div).find("input:checked").length) FW_cmd(FW_root+"?cmd=attr "+wn+" confirmDelete 0&XHR=1"); $(this).dialog("close"); $(div).remove(); } $(div).dialog({ dialogClass:"no-close", modal:true, width:"auto", closeOnEscape:true, maxWidth:$(window).width()*0.9, maxHeight:$(window).height()*0.9, buttons: [ {text:"Yes", click:function(){ location.href = ma[0]; doClose(); }}, {text:"No", click:function(){ doClose(); }}] }); }); }); } // Show the webCmd list in a dialog if: smallScreen & hiddenroom=detail & room function FW_smallScreenCommands() { if($("div#menu select").length == 0 || // not SmallScreen $("div#content").attr("room") == undefined || // not room Overview $("div#content div.col1 a").length > 0) // no hiddenroom=detail return; $("div#content div.col1").each(function(){ var tr = $(this).closest("tr"); if($(tr).find("> td").length <= 2) return; $(this).html("<a href='#'>"+$(this).html()+"</a>"); $(this).find("a").click(function(){ var t = $("<table></table>"), row=0; $(tr).find("> td").each(function(){ $(t).append("<tr></tr>"); if(row++ == 0) { $(t).find("tr:last").append($(this).find("a").html()); } else { $(this).attr("data-orig", 1); this.orig=$(this).parent(); $(t).find("tr:last").append($(this).detach()); } }); FW_okDialog(t, this, function(){ $("#FW_okDialog [data-orig]").each(function(){ $(this).detach().appendTo(this.orig); }); }); }); }); } if(window.jQuery) { $(document).ready(FW_jqueryReadyFn); } else { // FLOORPLAN compatibility loadScript("pgm2/jquery.min.js", function() { loadScript("pgm2/jquery-ui.min.js", function() { FW_jqueryReadyFn(); }, true); }, true); } // FLOORPLAN compatibility function FW_delayedStart() { setTimeout("FW_longpoll()", 100); } function log(txt) { var d = new Date(); var ms = ("000"+(d.getMilliseconds()%1000)); ms = ms.substr(ms.length-3,3); txt = d.toTimeString().substring(0,8)+"."+ms+" "+txt; if(typeof window.console != "undefined") console.log(txt); } function addcsrf(arg) { if(typeof FW_csrfToken != "undefined") { arg = arg.replace(/&fwcsrf=[^&]*/,''); arg += '&fwcsrf='+encodeURIComponent(FW_csrfToken); } return arg; } function FW_csrfRefresh(callback) { log("FW_csrfRefresh, last was "+(FW_csrfOk ? "ok":"bad")); if(!FW_csrfOk) // avoid endless loop return; $.ajax({ url:location.pathname+"?XHR=1", success: function(data, textStatus, request){ FW_csrfToken = request.getResponseHeader('x-fhem-csrftoken'); FW_csrfOk = false; if(callback) callback(); } }); } function FW_cmd(arg, callback) { if(arg.length < 120) log("FW_cmd:"+arg); else log("FW_cmd:"+arg.substr(0,120)+"..."); $.ajax({ url:addcsrf(arg)+'&fw_id='+$("body").attr('fw_id'), method:'POST', success: function(data, textStatus, req){ FW_csrfOk = true; if(callback) callback(req.responseText); else if(req.responseText) FW_errmsg(req.responseText, 5000); }, error:function(xhr, status, err) { if(xhr.status == 400 && typeof FW_csrfToken != "undefined") { FW_csrfToken = ""; FW_csrfRefresh(function(){FW_cmd(arg, callback)}); } } }); } function FW_errmsg(txt, timeout) { log("ERRMSG:"+txt+"<"); var errmsg = document.getElementById("errmsg"); if(!errmsg) { if(txt == "") return; errmsg = document.createElement('div'); errmsg.setAttribute("id","errmsg"); document.body.appendChild(errmsg); } if(txt == "") { document.body.removeChild(errmsg); return; } errmsg.innerHTML = txt; if(timeout) setTimeout("FW_errmsg('')", timeout); } function FW_okDialog(txt, parent, removeFn) { var div = $("<div id='FW_okDialog'>"); $(div).html(txt); $("body").append(div); var oldPos = $("body").scrollTop(); $(div).dialog({ dialogClass:"no-close", modal:true, width:"auto", closeOnEscape:true, maxWidth:$(window).width()*0.9, maxHeight:$(window).height()*0.9, buttons: [{text:"OK", click:function(){ $(this).dialog("close"); if(removeFn) removeFn(); $(div).remove(); }}] }); FW_replaceWidgets(div); $(div).find("a").each(function(){FW_replaceLink(this);}); //Forum #33766 if(parent) $(div).dialog( "option", "position", { my: "left top", at: "right bottom", of: parent, collision: "flipfit" }); setTimeout(function(){$("body").scrollTop(oldPos);}, 1); // Not ideal. } function FW_menu(evt, el, arr, dis, fn, embedEl) { if(!embedEl) evt.stopPropagation(); if($("#fwmenu").length) { delfwmenu(); return; } var html = '<ul id="fwmenu">'; for(var i=0; i<arr.length; i++) { html+='<li class="'+ ((dis && dis[i]) ? 'ui-state-disabled' : '')+'">'+ '<a row="'+i+'" href="#">'+arr[i]+'</a></li>'; } html += '</ul>'; $("body").append(html); function delfwmenu() { $("ul#fwmenu").remove(); $('html').unbind('click.fwmenu'); } var wt = $(window).scrollTop(); $("#fwmenu") .menu({ select: function(e,ui) { // changes the scrollTop(); e.stopPropagation(); fn($(e.currentTarget).find("[row]").attr("row")); delfwmenu(); setTimeout(function(){ $(window).scrollTop(wt) }, 1); // Bug in select? } }); var off = $(el).offset(); if(embedEl) { var embOff = $(embedEl).offset(); off.top += embOff.top; off.left += embOff.left; } var dH = $("#fwmenu").height(), dW = $("#fwmenu").width(), wH = $(window).height(), wW = $(window).width(); var ey = off.top+dH+20, ex = off.left+dW; if(ex>wW && ey>wH) { off.top -= dH; off.left -= (dW+16); } else if(ey > wH) { off.top -= dH; off.left += 20; } else if(ex > wW) { off.left -= (dW+16); } else { off.top += 20; } $("#fwmenu").css(off); $('html').bind('click.fwmenu', function() { delfwmenu(); }); } function FW_getLink(el) { var attr = $(el).attr("href"); if(!attr) { attr = $(el).attr("onclick"); // Tablet/smallScreen version if(!attr) return ""; attr = attr.replace(/^location.href='/,''); attr = attr.replace(/'$/,''); } return attr; } function FW_replaceLink(el) { var attr = FW_getLink(el); if(!attr) return; var ma = attr.match(/^(.*\?)(cmd[^=]*=.*)$/); if(ma == null || ma.length == 0 || !ma[2].match(/=(save|set)/)) { ma = attr.match(new RegExp("^"+FW_root)); // Avoid "Connection lost" @iOS if(ma) { $(el).click(function(e) { // Open link in window/tab, Forum #39154 if(e.shiftKey || e.ctrlKey || e.metaKey || e.button == 1) return; e.preventDefault(); FW_leaving = 1; if($(el).attr("target") == "_blank") { window.open(attr, '_blank').focus(); } else { location.href = attr; } }); } return; } $(el).removeAttr("href"); $(el).removeAttr("onclick"); $(el).click(function() { attr = attr.replace(/&.*$/,''); // remove unnecessary params, forum: #97351 FW_cmd(attr+"&XHR=1", function(txt){ if(!txt) return; if(ma[2].match(/=set/)) // Forum #38875 FW_okDialog('<pre>'+txt+'<pre>', el); else FW_errmsg(txt, 5000); }); }); $(el).css("cursor", "pointer"); } function FW_htmlQuote(text) { return text.replace(/&/g, '&') // Same as in 01_FHEMWEB .replace(/</g, '<') .replace(/>/g, '>'); } function FW_inlineModify() // Do not generate a new HTML page upon pressing modify { var cm; if( typeof AddCodeMirror == 'function' ) { // init codemirror for FW_style edit textarea AddCodeMirror($('textarea[name="data"]')); } $('#DEFa').click(function(){ var old = $('#edit').css('display'); $('#edit').css('display', old=='none' ? 'block' : 'none'); $('#disp').css('display', old=='none' ? 'none' : 'block'); if( typeof AddCodeMirror == 'function' ) { var s=document.getElementById("edit").getElementsByTagName("textarea"); AddCodeMirror(s[0], function(pcm) {cm = pcm;}); } }); $("div input.psc[type=submit]:not(.get)").click(function(e){ e.preventDefault(); var newDef = typeof cm !== 'undefined' ? cm.getValue() : $(this).closest("form").find("textarea").val(); var cmd = $(this).attr("name")+"="+$(this).attr("value")+" "+newDef; var isDef = true, reloadIfOk = false; if(newDef == undefined || $(this).attr("value").indexOf("modify") != 0) { isDef = false; var div = $(this).closest("div.makeSelect"); var devName = $(div).attr("dev"), cmd = $(div).attr("cmd"); var sel = $(this).closest("form").find("select"); var arg = $(sel).val(); var ifid = (devName+"-"+arg).replace(/([^_a-z0-9])/gi, function(m){ return "\\"+m }); if($(".dval[informid="+ifid+"]").length == 0) { if(cmd == "attr" || (cmd == "set" && arg == "attrTemplate")) { reloadIfOk = true; } else { $(this).unbind('click').click();// No element found to replace, reload return; } } newDef = $(this).closest("form").find("input:text").val(); if(newDef == undefined) newDef = $(this).closest("form").find("[name^=val]").val(); cmd = $(this).attr("name")+"="+cmd+" "+devName+" "+arg+" "+newDef; } FW_cmd(FW_root+"?"+encodeURIComponent(cmd)+"&XHR=1", function(resp){ if(!resp && reloadIfOk) { var hr = location.href+""; location.href = hr+ // retain fw_id (hr.match(/fw_id=\d+/) ? "" : '&fw_id='+$("body").attr('fw_id')); } if(resp) { if(!resp.match(/^<html>[\s\S]*<\/html>/ ) ) { resp = FW_htmlQuote(resp); if(resp.indexOf("\n") >= 0) resp = '<pre>'+resp+'</pre>'; } return FW_okDialog(resp); } if(isDef) { if(newDef.indexOf("\n") >= 0) newDef = '<pre>'+newDef+'</pre>'; else newDef = FW_htmlQuote(newDef); $("div#disp").html(newDef).css("display", ""); $("div#edit").css("display", "none"); } }); }); } function FW_rawDef() { $("div.rawDef a").each(function(){ // Help on detail window var dev = FW_getLink(this).split(" ").pop().split("&")[0]; $(this).unbind("click"); $(this).attr("href", "#"); // Desktop: show underlined Text $(this).removeAttr("onclick"); $(this).click(function(evt){ if($("#rawDef").length) { $("#rawDef").remove(); return; } var textAreaStyle = typeof AddCodeMirror == 'function'?'opacity:0':''; $("#content").append('<div id="rawDef">'+ '<textarea id="td_rawDef" rows="25" cols="60" style="width:99%; '+ textAreaStyle+'"/>'+ '<button>Execute commands</button>'+ ' Dump "Probably associated with" too <input type="checkbox">'+ '<br><br></div>'); var cmVar; function fillData(opt) { var s = $('#rawDef textarea'); FW_cmd(FW_root+"?cmd=list "+opt+" "+dev+"&XHR=1", function(data) { var re = new RegExp("^define", "gm"); data = data.replace(re, "defmod"); s.val(data); var off = $("#rawDef").position().top-20; $('body, html').animate({scrollTop:off}, 500); $("#rawDef button").hide(); var propertychange = function() { var nData = $("#rawDef textarea").val(); if(nData != data) $("#rawDef button").show(); else $("#rawDef button").hide(); }; s.bind('input propertychange', propertychange); if(cmVar) { cmVar.setValue(data); } else if(typeof AddCodeMirror == 'function') { AddCodeMirror(s, function(cm) { cmVar = cm; cm.on("change", function() { s.val(cm.getValue()); propertychange(); }) }); } }); } fillData("-r"); $("#rawDef input").click(function(){fillData(this.checked ?"-R":"-r")}); $("#rawDef button").click(function(){ FW_execRawDef($("#rawDef textarea").val()); }); }); }); } function FW_execRawDef(data) { var arr = data.split("\n"), str="", i1=-1; function doNext() { if(++i1 >= arr.length) { return FW_okDialog("Executed everything, no errors found."); } str += arr[i1]; if(arr[i1].charAt(arr[i1].length-1) === "\\") { str += "\n"; return doNext(); } if(str != "") { str = str.replace(/\\\n/g, "\n") .replace(/;;/g, ";"); FW_cmd(FW_root+"?cmd.x="+encodeURIComponent(str)+"&XHR=1", function(r){ if(r) return FW_okDialog('<pre>'+r+'</pre>'); str = ""; doNext(); }); } else { doNext(); } } doNext(); } var FW_arrowDown="", FW_arrowRight=""; function FW_treeMenu() { var a = $("a").get(0); var col = 'rgb(39, 135, 38)'; if(window.getComputedStyle && a) col = getComputedStyle(a,null).getPropertyValue('color'); 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>' .replace('gray', col); FW_arrowDown =FW_arrowRight.replace('/>',' transform="rotate(90,896,896)"/>'); var fnd; $("div#menu table.room").each(function(){ // one loop per Block var t = this, ma = {}; $(t).find("td > div > a > span").each(function(e){ var span = this, spanTxt = $(span).text().replace(/,/g,''); var ta = spanTxt.split("->"); if(ta.length <= 1) return; fnd = true; var nxt="", lst="", tr=$(span).closest("tr"); for(var i1=0; i1<ta.length-1; i1++) { nxt += "->"+ta[i1]; if(!ma[nxt]) { $(tr).before("<tr class='menuTree closed level"+i1+"' "+ "data-mTree='"+lst+"' data-nxt='"+nxt+"'>"+ "<td><div><a href='#'>"+ta[i1]+"</a><div></div></div></td></tr>"); } ma[nxt] = true; lst = nxt; } $(span).html(ta[ta.length-1]); $(tr).attr("data-mTree", nxt) .addClass("menuTree level"+(ta.length-1)); }); }); if(fnd) { $("head").append( "<style>"+ "tr.menuTree { cursor:pointer; }"+ "tr.menuTree.level1 > td > div { margin-left:10px; }"+ "tr.menuTree.level2 > td > div { margin-left:20px; }"+ "tr.menuTree.level3 > td > div { margin-left:30px; }"+ "tr.menuTree.open { font-weight: bold; }"+ "tr.menuTree > td > div > div { "+ "display:inline-block; width:1em; height:1em; float:right;"+ "background-size: contain; background-repeat: no-repeat;"+ "}"+ "</style>"); var t = $("div#menu table.room"); $(t).find("tr[data-mTree]").not(".level0").hide(); $(t).find("tr.menuTree").click(function(){treeClick(this)}); $(t).find("tr.menuTree > td > div > div") .css("background-image", "url('"+FW_arrowRight+"')"); var selRoom = $("div#content").attr("room"); if(selRoom) { var ta = selRoom.split("->"), nxt=""; for(var i1=0; i1<ta.length-1; i1++) { nxt += FW_escapeSelector("->"+ta[i1]); treeClick($(t).find("tr.menuTree[data-nxt="+nxt+"]")); } } } function treeClick(el) { var tgt = FW_escapeSelector($(el).attr("data-nxt")); if($(el).hasClass("closed")) { $(el).closest("table").find("tr[data-mTree="+tgt+"]").show(); $(el).find("div>div").css("background-image", "url('"+FW_arrowDown+"')"); } else { $(el).closest("table").find("tr[data-mTree^="+tgt+"]") .hide().addClass("closed"); $(el).find("div>div").css("background-image", "url('"+FW_arrowRight+"')"); } $(el).toggleClass("closed"); $(el).toggleClass("open"); }; } function FW_escapeSelector(s) { if(typeof s != 'string') return s; return s.replace(/[ .#\[\]>]/g, function(r) { return '\\'+r }); } /*************** LONGPOLL START **************/ var FW_pollConn; var FW_longpollOffset = 0; var FW_leaving; var FW_lastDataTime=0; function FW_doUpdate(evt) { var errstr = "Connection lost, trying a reconnect every 5 seconds."; var input=""; var retryTime = 5000; var now = new Date()/1000; function setValue(d) // is Callable from eval below { $("[informId='"+d[0]+"']").each(function(){ if(this.setValueFn) { // change the select/etc value this.setValueFn(d[1].replace(/\n/g, '\u2424')); } else { if(d[2].match(/\n/) && !d[2].match(/<.*>/)) // format multiline d[2] = '<html><pre>'+d[2]+'</pre></html>'; var ma = /^<html>([\s\S]*)<\/html>/.exec(d[2]); if(!d[0].match("-")) // not a reading $(this).html(d[2]); else if(ma) $(this).html(ma[1]); else $(this).text(d[2]); if(d[0].match(/-ts$/)) // timestamps $(this).addClass('changed'); $(this).find("a").each(function() { FW_replaceLink(this) }); } }); } // iOS closes HTTP after 60s idle, websocket after 240s idle if(now-FW_lastDataTime > 59) { errstr=""; retryTime = 100; } FW_lastDataTime = now; // Websocket starts with Android 4.4, and IE10 if(typeof WebSocket == "function" && evt && evt.target instanceof WebSocket) { if(evt.type == 'close' && !FW_leaving) { FW_errmsg(errstr, retryTime-100); FW_pollConn.close(); FW_pollConn = undefined; setTimeout(FW_longpoll, retryTime); return; } input = evt.data; FW_longpollOffset = 0; } else if(FW_pollConn != undefined) { if(FW_pollConn.readyState == 4 && !FW_leaving) { if(FW_pollConn.status == "400") { location.reload(); return; } FW_errmsg(errstr, retryTime-100); setTimeout(FW_longpoll, retryTime); return; } if(FW_pollConn.readyState != 3) return; input = FW_pollConn.responseText; } var devs = new Array(); if(!input || input.length <= FW_longpollOffset) return; FW_serverLastMsg = (new Date()).getTime()/1000; for(;;) { var nOff = input.indexOf("\n", FW_longpollOffset); if(nOff < 0) break; var l = input.substr(FW_longpollOffset, nOff-FW_longpollOffset); FW_longpollOffset = nOff+1; log("Rcvd: "+(l.length>132 ? l.substring(0,132)+"...("+l.length+")":l)); if(!l.length) continue; if(l.indexOf("<")== 0) { // HTML returned by proxy, if FHEM behind is dead FW_closeConn(); FW_errmsg(errstr, retryTime-100); setTimeout(FW_longpoll, retryTime); return; } var d = JSON.parse(l); if(d.length != 3) continue; if( d[0].match(/^#FHEMWEB:/) ) { eval(d[1]); } else { setValue(d); } // updateLine is deprecated, use setValueFn for(var w in FW_widgets) if(FW_widgets[w].updateLine && !FW_widgets[w].second) FW_widgets[w].updateLine(d); devs.push(d); } // used for SVG to avoid double-reloads for(var w in FW_widgets) if(FW_widgets[w].updateDevs && !FW_widgets[w].second) FW_widgets[w].updateDevs(devs); // reset the connection to avoid memory problems if(FW_longpollOffset > 1024*1024 && FW_longpollOffset==input.length) FW_longpoll(); } function FW_closeConn() { FW_leaving = 1; if(!FW_pollConn) return; if(typeof FW_pollConn.close == "function") FW_pollConn.close(); else if(typeof FW_pollConn.abort == "function") FW_pollConn.abort(); FW_pollConn = undefined; } function FW_longpoll() { FW_closeConn(); FW_leaving = 0; FW_longpollOffset = 0; // Build the notify filter for the backend var filter = $("body").attr("longpollfilter"); if(filter == null) filter = ""; var retry; if(filter == "") { $("embed").each(function() { // wait for all embeds to be there if(retry) return; var ed = FW_getSVG(this); if(!retry && ed == undefined && filter != ".*" && --embedLoadRetry > 0) { retry = 1; setTimeout(FW_longpoll, 100); return; } if(ed && $(ed).find("svg[flog]").attr("flog")) filter=".*"; }); if(retry) return; } if(filter == "") { if(FW_urlParams.room) filter="room="+FW_urlParams.room; if(FW_urlParams.detail) filter=FW_urlParams.detail; } if($("#floorplan").length>0) //floorplan special filter += ";iconPath="+$("body").attr("name"); if(filter == "") { var content = document.getElementById("content"); if(content) { var room = content.getAttribute("room"); if(room) filter="room="+room; } } // use devspec directly if room is dynamic (#devspec=<devspec>) filter = filter.replace( 'room=#devspec=', '' ); filter = filter.replace( 'room=%23devspec%3d', '' ); var iP = $("body").attr("iconPath"); if(iP != null) filter = filter +";iconPath="+iP; var since = "null"; if(FW_serverGenerated) since = FW_serverLastMsg + (FW_serverGenerated-FW_serverFirstMsg); var query = "?XHR=1"+ "&inform=type=status;filter="+filter+";since="+since+";fmt=JSON"+ '&fw_id='+$("body").attr('fw_id')+ "×tamp="+new Date().getTime(); var loc = (""+location).replace(/\?.*/,""); if(typeof WebSocket == "function" && FW_longpollType == "websocket") { FW_pollConn = new WebSocket(loc.replace(/[#&?].*/,'') .replace(/^http/i, "ws")+query); FW_pollConn.onclose = FW_pollConn.onerror = FW_pollConn.onmessage = FW_doUpdate; FW_pollConn.onopen = function(){FW_wsPing(FW_pollConn);}; } else { FW_pollConn = new XMLHttpRequest(); FW_pollConn.open("GET", location.pathname+query, true); if(FW_pollConn.overrideMimeType) // Win 8.1, #66004 FW_pollConn.overrideMimeType("application/json"); FW_pollConn.onreadystatechange = FW_doUpdate; FW_pollConn.send(null); } log("Inform-channel opened ("+(FW_longpollType==1 ? "HTTP":FW_longpollType)+ ") with filter "+filter); } function FW_wsPing(conn) // idle websockets are closed by the browser after 55sec { if(!conn || conn.readyState != conn.OPEN) return; conn.send("\n"); // setTimeout(function(){FW_wsPing(conn);}, 30000); } /*************** LONGPOLL END **************/ /*************** WIDGETS START **************/ /*************** "Double" select in detail window ****/ function FW_detailSelect(selEl, mayMissing) { if(selEl.target) selEl = selEl.target; var selVal = $(selEl).val(); var div = $(selEl).closest("div.makeSelect"); if(!div.attr("list")) // hiddenRoom=input return; var arg, listArr = $(div).attr("list").split(" "), devName = $(div).attr("dev"), cmd = $(div).attr("cmd"); var i1; for(i1=0; i1<listArr.length; i1++) { arg = listArr[i1]; if(arg.indexOf(selVal) == 0 && (arg.length == selVal.length || arg[selVal.length] == ':')) break; } var vArr = []; if(i1==listArr.length && !mayMissing) return; if(i1<listArr.length) { if(arg.length > selVal.length) vArr = arg.substr(selVal.length+1).split(","); } FW_replaceWidget($(selEl).next(), devName, vArr,undefined,selVal, undefined, undefined, undefined, function(newEl) { if(cmd == "attr") FW_queryValue('{AttrVal("'+devName+'","'+selVal+'","")}', newEl); if(cmd == "set") FW_queryValue('{ReadingsVal("'+devName+'","'+selVal+'","")}', newEl); }); } function FW_callCreateFn(elName, devName, vArr, currVal, set, params, cmd, finishFn) { for(var wn in FW_widgets) { if(FW_widgets[wn].createFn && !FW_widgets[wn].second) { var newEl = FW_widgets[wn].createFn(elName, devName, vArr, currVal, set, params, cmd); if(newEl) return finishFn(wn, newEl); } } var v0 = vArr[0].split("-")[0]; if(v0.indexOf("uzsu") == 0) v0 = "uzsu"; if(FW_availableJs[v0]) { loadScript("pgm2/fhemweb_"+v0+".js", function() { if(!FW_widgets[vArr[0]]) { log("ERROR: fhemweb_"+vArr[v0]+".js does not fill FW_widgets"); return; } if(FW_widgets[vArr[0]].createFn) var newEl = FW_widgets[vArr[0]].createFn(elName, devName, vArr, currVal, set, params, cmd); finishFn(vArr[0], newEl); }); } else { finishFn(); } } function FW_replaceWidget(oldEl,devName,vArr,currVal,reading,set,params,cmd,readyFn) { var elName = $(oldEl).attr("name"); if(!elName) elName = $(oldEl).find("[name]").attr("name"); if(vArr.length == 0) { // No parameters, input field var newEl = FW_createTextField(elName, devName, ["textField"], currVal, set, params, cmd); finishFn("textField", newEl); } else { return FW_callCreateFn(elName, devName, vArr, currVal, set, params, cmd, finishFn); } function finishFn(wn, newEl) { if(!newEl) { vArr.unshift("select"); newEl = FW_createSelect(elName,devName,vArr,currVal,set,params,cmd); wn = "select"; } if(!newEl) { // Simple link newEl = $('<div class="col3"><a style="cursor: pointer;">'+ set+' '+params.join(' ')+ '</a></div>'); $(newEl).click(function(arg) { cmd(params[0]) }); $(oldEl).replaceWith(newEl); if(readyFn) return readyFn(newEl); return; } $(newEl).addClass(wn+"_widget"); if( $(newEl).find("[informId]").length==0 && !$(newEl).attr("informId") ) { if(reading) { var a = $(oldEl).closest("form").find("input[type=submit][value=attr]"); $(newEl).attr("informId", devName+(a.length?"-a-":"-")+reading); } var addTitle = $("body").attr("data-addHtmlTitle"); if(reading != "state" && addTitle==1) $(newEl).attr("title", reading); } $(oldEl).replaceWith(newEl); if(newEl.activateFn) // CSS is not applied if newEl is not in the document newEl.activateFn(); if(readyFn) readyFn(newEl); } } function FW_queryValue(cmd, el) { log("FW_queryValue:"+cmd); var query = location.pathname+"?cmd="+encodeURIComponent(cmd)+"&XHR=1"; query = addcsrf(query); var qConn = new XMLHttpRequest(); qConn.onreadystatechange = function() { if(qConn.readyState != 4) return; var qResp = qConn.responseText.replace(/\n$/, ''); qResp = qResp.replace(/\n/g, '\u2424'); if(el.setValueFn) el.setValueFn(qResp); qConn.abort(); } qConn.open("GET", query, true); qConn.send(null); } /*************** TEXTFIELD **************/ function FW_createTextField(elName, devName, vArr, currVal, set, params, cmd) { if(vArr.length != 1 || (vArr[0] != "textField" && vArr[0] != "textFieldNL" && vArr[0] != "textField-long" && vArr[0] != "textFieldNL-long") || (params && params.length)) return undefined; var is_long = (vArr[0].indexOf("long") > 0); var newEl = $("<div style='display:inline-block'>").get(0); if(set && set != "state" && vArr[0].indexOf("NL") < 0) $(newEl).append(set+":"); $(newEl).append('<input type="text" size="30">'); var inp = $(newEl).find("input").get(0); if(elName) $(inp).attr('name', elName); if(currVal != undefined) $(inp).val(currVal); function addBlur() { if(cmd) $(inp).blur(function() { cmd($(inp).val()) }); }; newEl.setValueFn = function(arg){ $(inp).val(arg) }; addBlur(); var myFunc = function(){ $(inp).unbind("blur"); $('body').append( '<div id="editdlg" style="display:none">'+ '<textarea id="td_longText" rows="25" cols="60" style="width:99%"/>'+ '</div>'); var txt = $(inp).val(); txt = txt.replace(/\u2424/g, '\n'); $("#td_longText").val(txt); var cm; if(typeof AddCodeMirror == 'function') { AddCodeMirror($("#td_longText"), function(pcm) {cm = pcm;}); } $('#editdlg').dialog( { modal:true, closeOnEscape:true, width:$(window).width()*3/4, height:$(window).height()*3/4, close:function(){ $('#editdlg').remove(); }, buttons:[ { text:"Cancel", click:function(){ $(this).dialog('close'); addBlur(); }}, { text:"OK", click:function(){ if(cm) $("#td_longText").val(cm.getValue()); var res=$("#td_longText").val(); res = res.replace(/\n/g, '\u2424' ); $(this).dialog('close'); $(inp).val(res); addBlur(); }}] }); }; if(is_long) $(newEl).click(myFunc); return newEl; } /*************** select **************/ function FW_createSelect(elName, devName, vArr, currVal, set, params, cmd) { if(vArr.length < 2 || vArr[0] != "select" || (params && params.length)) return undefined; var newEl = document.createElement('select'); var vHash = {}; for(var j=1; j < vArr.length; j++) { var o = document.createElement('option'); o.text = o.value = vArr[j].replace(/#/g," "); vHash[vArr[j]] = 1; newEl.options[j-1] = o; } if(currVal) $(newEl).val(currVal); if(elName) $(newEl).attr('name', elName); if(cmd) $(newEl).change(function(arg) { cmd($(newEl).val()) }); newEl.setValueFn = function(arg) { if(vHash[arg]) $(newEl).val(arg); }; return newEl; } /*************** selectNumbers **************/ // Syntax: selectnumbers,<min value>,<step|step of exponent>,<max value>,<number of digits after decimal point>,lin|log10 function FW_createSelectNumbers(elName, devName, vArr, currVal, set, params, cmd) { if(vArr.length < 6 || vArr[0] != "selectnumbers" || (params && params.length)) return undefined; var min = parseFloat(vArr[1]); var stp = parseFloat(vArr[2]); var max = parseFloat(vArr[3]); var dp = parseFloat(vArr[4]); // decimal points var fun = vArr[5]; // function if(currVal != undefined) currVal = currVal.replace(/[^\d.\-]/g, ""); currVal = (currVal==undefined || currVal=="") ? min : parseFloat(currVal); if(max==min) return undefined; if(!(fun == "lin" || fun == "log10")) return undefined; if(currVal < min) currVal = min; if(currVal > max) currVal = max; var newEl = document.createElement('select'); var vHash = {}; var k = 0; var v = 0; if (fun == "lin") { for(var j=min; j <= max; j+=stp) { var o = document.createElement('option'); o.text = o.value = j.toFixed(dp); vHash[j.toString()] = 1; newEl.options[k] = o; k++; } } else if (fun == "log10") { if(min <= 0 || max <= 0) return undefined; for(var j=Math.log10(min); j <= Math.log10(max)+stp; j+=stp) { var o = document.createElement('option'); var w = Math.pow(10, j) if (w > max) w = max; if (v == w.toFixed(dp)) continue; v = w.toFixed(dp); o.text = o.value = v; vHash[v] = 1; newEl.options[k] = o; k++; } } if(currVal) $(newEl).val(currVal.toFixed(dp)); if(elName) $(newEl).attr('name', elName); if(cmd) $(newEl).change(function(arg) { cmd($(newEl).val()) }); newEl.setValueFn = function(arg) { if(vHash[arg]) $(newEl).val(arg); }; return newEl; } /*************** noArg **************/ function FW_createNoArg(elName, devName, vArr, currVal, set, params, cmd) { if(vArr.length != 1 || vArr[0] != "noArg" || (params && params.length)) return undefined; var newEl = $('<div style="display:none">').get(0); if(elName) $(newEl).append('<input type="hidden" name="'+elName+ '" value="">'); return(newEl); } /*************** slider **************/ function FW_createSlider(elName, devName, vArr, currVal, set, params, cmd) { // min, step, max, float if(vArr.length < 4 || vArr.length > 5 || vArr[0] != "slider" || (params && params.length)) return undefined; var min = parseFloat(vArr[1]); var stp = parseFloat(vArr[2]); var max = parseFloat(vArr[3]); var flt = (vArr.length == 5 && vArr[4] == "1"); var dp = 0; // decimal points for float if(flt) { var s = ""+stp; if(s.indexOf(".") >= 0) dp = s.substr(s.indexOf(".")+1).length; } if(currVal != undefined) currVal = currVal.replace(/[^\d.\-]/g, ""); currVal = (currVal==undefined || currVal=="") ? min : parseFloat(currVal); if(max==min) return undefined; if(currVal < min || currVal > max) currVal = min; var newEl = $('<div style="display:inline-block" tabindex="0">').get(0); var slider = $('<div class="slider" id="slider.'+devName+'">').get(0); $(newEl).append(slider); var sh = $('<div class="handle">'+currVal+'</div>').get(0); $(slider).append(sh); if(elName) $(newEl).append('<input type="hidden" name="'+elName+ '" value="'+currVal+'">'); var lastX=-1, offX=0, maxX=0, val=currVal; newEl.activateFn = function() { if(currVal < min || currVal > max) return; if(!slider.offsetWidth) return setTimeout(newEl.activateFn, 1); maxX = slider.offsetWidth-sh.offsetWidth; offX = (currVal-min)*maxX/(max-min); var strVal = (flt ? currVal.toFixed(dp) : ""+parseInt(currVal)); sh.innerHTML = strVal; sh.setAttribute('style', 'left:'+offX+'px;'); if(elName) slider.nextSibling.setAttribute('value', strVal); } $(newEl).keydown(function(e){ if(e.keyCode == 37) currVal -= stp; else if(e.keyCode == 39) currVal += stp; else return; if(currVal < min) currVal = min; if(currVal > max) currVal = max; offX = (currVal-min)*maxX/(max-min); var strVal = (flt ? currVal.toFixed(dp) : ""+parseInt(currVal)); sh.innerHTML = strVal; sh.setAttribute('style', 'left:'+offX+'px;'); if(cmd) cmd(strVal); if(elName) slider.nextSibling.setAttribute('value', strVal); }); function touchFn(e, fn) { e.preventDefault(); // Prevents Safari from scrolling! if(e.touches == null || e.touches.length == 0) return; e.clientX = e.touches[0].clientX; fn(e); } function mouseDown(e) { var oldFn1 = document.onmousemove, oldFn2 = document.onmouseup, oldFn3 = document.ontouchmove, oldFn4 = document.ontouchend; e.stopPropagation(); // Dashboard fix lastX = e.clientX; // Does not work on IE8 function mouseMove(e) { e.stopPropagation(); // Dashboard fix if(maxX == 0) // Forum #35846 maxX = slider.offsetWidth-sh.offsetWidth; var diff = e.clientX-lastX; lastX = e.clientX; offX += diff; if(offX < 0) offX = 0; if(offX > maxX) offX = maxX; val = offX/maxX * (max-min); val = flt ? (Math.floor(val/stp)*stp+min).toFixed(dp) : (Math.floor(Math.floor(val/stp)*stp)+min); sh.innerHTML = val; sh.setAttribute('style', 'left:'+offX+'px;'); } document.onmousemove = mouseMove; document.ontouchmove = function(e) { touchFn(e, mouseMove); } document.onmouseup = document.ontouchend = function(e) { e.stopPropagation(); // Dashboard fix document.onmousemove = oldFn1; document.onmouseup = oldFn2; document.ontouchmove = oldFn3; document.ontouchend = oldFn4; if(cmd) cmd(val); if(elName) slider.nextSibling.setAttribute('value', val); }; }; sh.onselectstart = function() { return false; } sh.onmousedown = mouseDown; sh.ontouchstart = function(e) { touchFn(e, mouseDown); } newEl.setValueFn = function(arg) { var res = arg.match(/[\d.\-]+/); // extract first number currVal = (res ? parseFloat(res[0]) : min); if(currVal < min || currVal > max) currVal = min; newEl.activateFn(); }; return newEl; } /*************** TIME **************/ function FW_createTime(elName, devName, vArr, currVal, set, params, cmd) { if(vArr.length != 1 || vArr[0] != "time" || (params && params.length)) return undefined; var open="-", closed="+"; var newEl = document.createElement('div'); $(newEl).append('<input type="text" size="5">'); $(newEl).append('<input type="button" value="'+closed+'">'); var inp = $(newEl).find("[type=text]"); var btn = $(newEl).find("[type=button]"); currVal = (currVal ? currVal : "12:00") .replace(/[^\d]*(\d\d):(\d\d).*/g,"$1:$2"); $(inp).val(currVal) if(elName) $(inp).attr("name", elName); var hh, mm; // the slider elements newEl.setValueFn = function(arg) { arg = arg.replace(/[^\d]*(\d\d):(\d\d).*/g,"$1:$2"); $(inp).val(arg); var hhmm = arg.split(":"); if(hhmm.length == 2 && hh && mm) { hh.setValueFn(hhmm[0]); mm.setValueFn(hhmm[1]); } }; $(btn).click(function(){ // Open/Close the slider view var v = $(inp).val(); if($(btn).val() == open) { $(btn).val(closed); $(newEl).find(".timeSlider").remove(); hh = mm = undefined; if(cmd) cmd(v); return; } $(btn).val(open); if(v.indexOf(":") < 0) { v = "12:00"; $(inp).val(v); } var hhmm = v.split(":"); function tSet(idx, arg) { if((""+arg).length < 2) arg = '0'+arg; hhmm[idx] = arg; $(inp).val(hhmm.join(":")); } $(newEl).append('<div class="timeSlider">'); var ts = $(newEl).find(".timeSlider"); hh = FW_createSlider(undefined, devName+"HH", ["slider", 0, 1, 23], hhmm[0], undefined, params, function(arg) { tSet(0, arg) }); mm = FW_createSlider(undefined, devName+"MM", ["slider", 0, 5, 55], hhmm[1], undefined, params, function(arg) { tSet(1, arg) }); $(ts).append("<br>"); $(ts).append(hh); hh.activateFn(); $(ts).append("<br>"); $(ts).append(mm); mm.activateFn(); }); return newEl; } /*************** MULTIPLE **************/ function FW_createMultiple(elName, devName, vArr, currVal, set, params, cmd) { if(vArr.length < 2 || (vArr[0]!="multiple" && vArr[0]!="multiple-strict") || (params && params.length)) return undefined; var newEl = $('<input type="text" size="30" readonly>').get(0); if(currVal) $(newEl).val(currVal); if(elName) $(newEl).attr("name", elName); newEl.setValueFn = function(arg){ $(newEl).val(arg) }; for(var i1=1; i1<vArr.length; i1++) vArr[i1] = vArr[i1].replace(/#/g, " "); $(newEl).focus(function(){ var sel = $(newEl).val().split(","), selObj={}; for(var i1=0; i1<sel.length; i1++) selObj[sel[i1]] = 1; var table = ""; for(var i1=1; i1<vArr.length; i1++) { var v = vArr[i1]; table += '<tr>'+ // funny stuff for ios6 style, forum #23561 '<td><div class="checkbox">'+ '<input name="'+v+'" id="multiple_'+v+'" type="checkbox"'+ (selObj[v] ? " checked" : "")+'/>'+'</div></td>'+ '<td><label for="multiple_'+v+'">'+v+'</label></td></tr>'; delete(selObj[v]); } var selArr=[]; for(var i1 in selObj) selArr.push(i1); var strict = (vArr[0] == "multiple-strict"); $('body').append( '<div id="multidlg" style="display:none">'+ '<table>'+table+'</table>'+(!strict ? '<input id="md_freeText" '+ 'value="'+selArr.join(',')+'"/>' : '')+ '</div>'); $('#multidlg').dialog( { modal:true, closeOnEscape:false, maxHeight:$(window).height()*3/4, buttons:[ { text:"Cancel", click:function(){ $('#multidlg').remove(); }}, { text:"OK", click:function(){ var res=[]; if($("#md_freeText").val()) res.push($("#md_freeText").val()); $("#multidlg table input").each(function(){ if($(this).prop("checked")) res.push($(this).attr("name")); }); $('#multidlg').remove(); $(newEl).val(res.join(",")); if(cmd) cmd(res.join(",")); }}]}); }); return newEl; } /*************** WIDGETS END **************/ /*************** SCRIPT LOAD FUNCTIONS START **************/ function loadScript(sname, callback, force) { var h = document.head || document.getElementsByTagName('head')[0]; sname = FW_root+"/"+sname; if(FW_scripts[sname]) { if(FW_scripts[sname].loaded) { if(callback) callback(); } else { FW_scripts[sname].callbacks.push(callback); } return; } if(!FW_docReady && !force) { FW_scripts[sname] = { callbacks:[ callback] }; return; } var script = document.createElement("script"); script.src = sname; script.async = script.defer = false; script.type = "text/javascript"; FW_scripts[sname] = { callbacks:[ callback] }; function scriptLoaded() { var p = FW_scripts[sname]; p.loaded = true; if(!p.called) { p.called = true; for(var i1=0; i1< p.callbacks.length; i1++) if(p.callbacks[i1]) // pushing undefined callbacks on the stack is ok p.callbacks[i1](); } delete(p.callbacks); } log("Loading script "+sname); if(FW_isIE) { script.onreadystatechange = function() { if(script.readyState == 'loaded' || script.readyState == 'complete') { script.onreadystatechange = null; scriptLoaded(); } } } else { if(FW_isiOS) FW_closeConn(); script.onload = function(){ scriptLoaded(); } } h.appendChild(script); } function loadLink(lname) { var h = document.head || document.getElementsByTagName('head')[0]; lname = FW_root+"/"+lname; var arr = h.getElementsByTagName("link"); for(var i1=0; i1<arr.length; i1++) if(lname == arr[i1].getAttribute("href")) return; var link = document.createElement("link"); link.href = lname; link.rel = "stylesheet"; log("Loading link "+lname); h.appendChild(link); } function scriptAttribute(sname) { var attr=""; $("head script").each(function(){ var src = $(this).attr("src"); if(src && src.indexOf(sname) >= 0) attr = $(this).attr("attr"); }); var ua={}; if(attr && attr != "") { try { ua=JSON.parse(attr); } catch(e){ FW_errmsg(sname+" Parameter "+e,5000); } } return ua; } /*************** SCRIPT LOAD FUNCTIONS END **************/ function print_call_stack() { var stack = new Error().stack; console.log("PRINTING CALL STACK"); console.log( stack ); } function FW_getSVG(emb) { if(emb.contentDocument) return emb.contentDocument; if(typeof emb.getSVGDocument == "function") { try { return emb.getSVGDocument(); } catch(err) { // dom not loaded -> fall through -> retry; } } return undefined; } /* =pod =begin html <li>noArg - show no input field.</li> <li>time - show a JavaScript driven timepicker.<br> Example: attr FS20dev widgetOverride on-till:time</li> <li>textField - show an input field.<br> Example: attr WEB widgetOverride room:textField</li> <li>textFieldNL - show the input field and hide the label.</li> <li>textField-long - show an input-field, but upon clicking on the input field open a textArea (60x25).</li> <li>textFieldNL-long - the behaviour is the same as :textField-long, but no label is displayed.</li> <li>slider,<min>,<step>,<max>[,1] - show a JavaScript driven slider. The optional ,1 at the end avoids the rounding of floating-point numbers.</li> <li>multiple,<val1>,<val2>,..." - present a multiple-value-selector with an additional textfield. The result is comman separated.</li> <li>multiple-strict,<val1>,<val2>,... - like :multiple, but without the textfield.</li> <li>selectnumbers,<min>,<step>,<max>,<number of digits after decimal point>,lin|log10" - display a select widget generated with values from min to max with step.<br> lin generates a constantly increasing series. log10 generates an exponentially increasing series to base 10, step is related to the exponent, e.g. 0.0625.</li> <li>select,<val1>,<val2>,... - show a dropdown with all values. <b>NOTE</b>: this is also the fallback, if no modifier is found.</li> =end html =begin html_DE <li>noArg - es wird kein weiteres Eingabefeld angezeigt.</li> <li>time - zeigt ein Zeitauswahlmenü. Beispiel: attr FS20dev widgetOverride on-till:time</li> <li>textField - zeigt ein Eingabefeld.<br> Beispiel: attr WEB widgetOverride room:textField</li> <li>textField-long - ist wie textField, aber beim Click im Eingabefeld wird ein Dialog mit einer HTML textarea (60x25) wird geöffnet.</li> <li>slider,<min>,<step>,<max>[,1] - zeigt einen Schieberegler. Das optionale 1 (isFloat) vermeidet eine Rundung der Fliesskommazahlen.</li> <li>multiple,<val1>,<val2>,... - zeigt eine Mehrfachauswahl mit einem zusätzlichen Eingabefeld. Das Ergebnis ist Komma separiert.</li> <li>multiple-strict,<val1>,<val2>,... - ist wie :multiple, bloß ohne Eingabefeld.</li> <li>selectnumbers,<min>,<step>,<max>,<number of digits after decimal point>,lin|log10" zeigt ein HTML-select mit einer Zahlenreihe vom Wert min bis Wert max mit Schritten von step angezeigt.<br> Die Angabe lin erzeugt eine konstant ansteigende Reihe. Die Angabe log10 erzeugt eine exponentiell ansteigende Reihe zur Basis 10, step bezieht sich auf den Exponenten, z.B. 0.0625.</li> <li>select,<val1>,<val2>,... - zeigt ein HTML select mit allen Werten. <b>Achtung</b>: so ein Widget wird auch dann angezeigt, falls kein passender Modifier gefunden wurde.</li> =end html_DE =cut */