"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 embedLoadRetry = 100;
// 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 },
slider: { createFn:FW_createSlider },
time: { createFn:FW_createTime },
noArg: { createFn:FW_createNoArg },
multiple: { createFn:FW_createMultiple },
"multiple-strict": { createFn:FW_createMultiple },
textfield: { createFn:FW_createTextField },
"textfield-long": { createFn:FW_createTextField }
};
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+":
"+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");
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=[];
$(this).find("tr").each(function(){ // count the td's
var cnt=0;
$(this).find("td").each(function(){ cnt++; });
if(maxTd < cnt) maxTd = cnt;
tdCount.push(cnt);
});
$(this).find("tr").each(function(){ // set the colspan
$(this).find("td").last().attr("colspan", maxTd-tdCount.shift()+1);
});
});
// Replace the FORM-POST in detail-view by XHR
/* Inactive, as Internals and Attributes arent auto updated.
$("form input[type=submit]").click(function(e) {
var cmd = "";
$(this).parent().find("[name]").each(function() {
cmd += (cmd?"&":"")+$(this).attr("name")+"="+$(this).val();
});
if(cmd.indexOf("detail=") < 0)
return;
e.preventDefault();
FW_cmd(FW_root+"?"+cmd+"&XHR=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
FW_okDialog('
'+data+'', el); }); }); $("#saveCheck") .css("cursor", "pointer") .click(function(){ var parent = this; FW_cmd(FW_root+"?cmd=save ?&XHR=1", function(data) { FW_okDialog('
'+data+'',parent); }); }); $("form").each(function(){ // shutdown handling var input = $(this).find("input.maininput"); if(!input.length) return; $(this).on("submit", function(e) { var val = $(input).val(); if(val.match(/^\s*ver.*/)) { e.preventDefault(); $(input).val(""); return FW_showVersion(val); } else if(val.match(/^\s*shutdown/)) { FW_cmd(FW_root+"?XHR=1&cmd="+val); $(input).val(""); return false; } else if(val.match(/^\s*get\s+/)) { // make get use xhr instead of reload //return true; FW_cmd(FW_root+"?cmd="+encodeURIComponent(val)+"&XHR=1", function(data){ if( !data.match( /^.*<\/html>/ ) ) { data = data.replace( '<', '<' ); data = '
'+data+''; } 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; } $("#content").append(''); FW_cmd(FW_root+"?cmd=help "+dev+"&XHR=1", function(data) { if(!$("#devSpecHelp").length) // FHEM slow, user clicked again, #68166 return; $("#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(''+$(this).html()+'') .css({cursor:"pointer"}) .click(function(){ var aname = "#sel_attr"+$(this).attr("data-name").replace(/\./g,'_'); $(aname).val($(this).text()); FW_detailSelect(aname); }); }); $("[name=icon-filter]").on("change keyup paste", function() { clearTimeout($.data(this, 'delayTimer')); var wait = setTimeout(FW_filterIcons, 300); $(this).data('delayTimer', wait); }); FW_smallScreenCommands(); FW_inlineModify(); FW_rawDef(); } 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
'+txt+'', 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, '>'); } 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 var s = $('textarea[name="data"]'); if( s.length && !s[0].editor ) { s[0].editor = true; AddCodeMirror( s[0] ); } } $('#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"); if(!s[0].editor) { s[0].editor=true; 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; 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) { log("PSC reload"); $(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) { resp = FW_htmlQuote(resp); if(resp.indexOf("\n") >= 0) resp = ''+resp+''; return FW_okDialog(resp); } if(isDef) { if(newDef.indexOf("\n") >= 0) newDef = ''+newDef+''; 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; } $("#content").append(''+ ''+ ''+ ' Dump "Probably associated with" too '+ ''); function fillData(opt) { FW_cmd(FW_root+"?cmd=list "+opt+" "+dev+"&XHR=1", function(data) { var re = new RegExp("^define", "gm"); data = data.replace(re, "defmod"); $("#rawDef textarea").val(data); var off = $("#rawDef").position().top-20; $('body, html').animate({scrollTop:off}, 500); $("#rawDef button").hide(); $('#rawDef textarea').bind('input propertychange', function() { var nData = $("#rawDef textarea").val(); if(nData != data) $("#rawDef button").show(); else $("#rawDef button").hide(); }); }); } fillData("-r"); $("#rawDef input").click(function(){fillData(this.checked ?"-R":"-r")}); $("#rawDef button").click(function(){ var data = $("#rawDef textarea").val(); 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."+dev+"="+encodeURIComponent(str)+"&XHR=1", function(r){ if(r) return FW_okDialog(''+r+''); str = ""; doNext(); }); } else { doNext(); } } doNext(); }); }); }); } /*************** 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; // 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.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 { $("[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] = ''+d[2]+''; var ma = /^([\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) }); } }); } for(var w in FW_widgets) if(FW_widgets[w].updateLine) // updateLine is deprecated, use setValueFn FW_widgets[w].updateLine(d); devs.push(d); } for(var w in FW_widgets) if(FW_widgets[w].updateDevs) // used for SVG to avoid double-reloads 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 == "") { var sa = location.search.substring(1).split("&"); for(var i = 0; i < sa.length; i++) { if(sa[i].substring(0,5) == "room=") filter=sa[i]; if(sa[i].substring(0,7) == "detail=") filter=sa[i].substring(7); } } 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; } } 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; } 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); } /*************** LONGPOLL END **************/ /*************** WIDGETS START **************/ /*************** "Double" select in detail window ****/ function FW_detailSelect(selEl) { if(selEl.target) selEl = selEl.target; var selVal = $(selEl).val(); var div = $(selEl).closest("div.makeSelect"); var arg, listArr = $(div).attr("list").split(" "), devName = $(div).attr("dev"), cmd = $(div).attr("cmd"); for(var i1=0; i1selVal.length) vArr = arg.substr(selVal.length+1).split(","); var newEl = FW_replaceWidget($(selEl).next(), devName, vArr,undefined,selVal); if(cmd == "attr") FW_queryValue('{AttrVal("'+devName+'","'+selVal+'","")}', newEl); if(cmd == "set") FW_queryValue('{ReadingsVal("'+devName+'","'+selVal+'","")}', newEl); } function FW_replaceWidget(oldEl, devName, vArr, currVal, reading, set, params, cmd) { var newEl, wn; var elName = $(oldEl).attr("name"); if(!elName) elName = $(oldEl).find("[name]").attr("name"); if(vArr.length == 0) { // No parameters, input field newEl = FW_createTextField(elName, devName, ["textField"], currVal, set, params, cmd); wn = "textField"; } else { for(wn in FW_widgets) { if(FW_widgets[wn].createFn) { newEl = FW_widgets[wn].createFn(elName, devName, vArr, currVal, set, params, cmd); if(newEl) break; } } if(!newEl) { // Select as fallback vArr.unshift("select"); newEl = FW_createSelect(elName, devName, vArr, currVal, set, params, cmd); wn = "select"; } } if(!newEl) { // Simple link newEl = $(''); $(newEl).click(function(arg) { cmd(params[0]) }); $(oldEl).replaceWith(newEl); return newEl; } $(newEl).addClass(wn+"_widget"); if( $(newEl).find("[informId]").length == 0 && !$(newEl).attr("informId") ) { if(reading) $(newEl).attr("informId", devName+"-"+reading); if(reading != "state") $(newEl).attr("title", reading); } $(oldEl).replaceWith(newEl); if(newEl.activateFn) // CSS is not applied if newEl is not in the document newEl.activateFn(); return 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 = $(" ").get(0); if(set && set != "state" && vArr[0].indexOf("NL") < 0) $(newEl).append(set+":"); $(newEl).append(''); 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( ''); var txt = $(inp).val(); txt = txt.replace(/\u2424/g, '\n'); $("#td_longText").val(txt); var cm; if(typeof AddCodeMirror == 'function' && !$("#td_longText").get(0).editor) { $("#td_longText").get(0).editor = true; AddCodeMirror($("#td_longText").get(0), function(pcm) {cm = pcm;}); } $('#editdlg').dialog( { modal:true, closeOnEscape:true, width:$(window).width()*3/4, maxHeight:$(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; } /*************** 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 = $('