/////////////////////////////////////////////// // Weekly Planning , 2017, Syrhus // v1.4.2 // updated for v4.9700 // 2018-09-20 : add Odd & Even week numbers // 2018-10-30 : bug on fixed // 2018-12-11 : Disable/Enable all setPoints (avoid to clear a planning to set it in standby mode) // Time precision to 15mins // Add i18n translations // Highlight on hover // Time range selection // 2018-12-20 : adjust table time range depending on setpoint minutes. // listen timers event [timersInitialized] & [timersLoaded] // improve speed performance // adujst to the right mode if less than 4 modes used and not "heatest" // 2019-11-18 : clear only previous timers id instead of calling the clearTimers function which erase all timers on all timerplan // 2019-11-26 : fix deletesetpointtimer instead of deletetimer for thermostat // 2020-07-29 : add 5mins and 2mins Time range // 2022-11-11 : support Off level for selector switch if not hidden /////////////////////////////////////////////// PlanningTimerSheet = function(options){ $('head').append(''); var bIsSelecting = false; var bAddClass = true; var modeSelected = 0; const MON = 1; const TUE = 2; const WED = 4; const THU = 8; const FRI = 16; const SAT = 32; const SUN = 64; const EVERYDAYS = 128; const WEEKDAYS = 256; const WEEKENDS = 512; const SUM_EVERYDAYS = 127; const SUM_WEEKDAYS = 31; const SUM_WEEKENDS = 96; const ODD = 8; const EVEN = 9; const ONTIME = 2; var $element = null; var $table = null; var $tbody = null; var $thead = null; var prevTimers = []; var planning = []; ////////////////////////////// var days = [ { "name": "Monday" }, { "name": "Tuesday" }, { "name": "Wednesday" }, { "name": "Thursday" }, { "name": "Friday" }, { "name": "Saturday" }, { "name": "Sunday" }]; var planning_translations = [ {'lng':'en', 'res':{ 'activate all': 'Activate all', 'deactivate all': 'Deactivate all', 'Comfort':'Comfort', 'Economic':'Economic', 'Nighttime':'Night time', 'NoFreeze':'Frost free', 'timeRange': 'Time range', 'hour':'hour', 'mins':'mins' }}, {'lng':'fr','res':{ 'activate all': 'Tout activer', 'deactivate all': 'Tout désactiver', 'Comfort':'Confort', 'Economic':'Economique', 'Nighttime':'Nuit', 'NoFreeze':'Hors gel', 'timeRange': 'Plage horaire', 'hour':'heure', 'mins':'mins' }} /* add others translations here ,{'lng':'XX', 'res':{ ... }} */ ]; var defaults = $.extend({ "devid":null, "container":"#deviceTimers", "commandClear":"clearsetpointtimers", "commandAdd":"addsetpointtimer", "buttonClass":"btnstyle3", "buttonModeClassSelected": "btnstyle3-sel", "odd_even_week" :false, "modes": [{ "value": 22, "class": "m0", "name": "Comfort"}, { "value": 19, "class": "m1", "name": "Economic"}, { "value": 16, "class": "m2", "name": "Nighttime"}, { "value": 4, "class": "m3", "name": "NoFreeze"}], "temperatureModes":true, "hover_activate":true, //to highlight row and column on mouse hover "nbTicksPerHour":2, //6 for 10mins, 4 for 15mins, 2 for 30mins, 1 for 1hour "propValue":"Temperature", "propValueAjax":"tvalue"}, options); ////////////////////////////////////////////////// Clear = function () { $tbody.find('td').removeClass(); }; //add or remove class to selected cell setSel = function (elt) { var vclass = getModeClass(); if (vclass == null) return; $(elt).removeClass(); if (bAddClass) $(elt).addClass(vclass); }; //apply class to row range setRowSel = function ($td) { if(!$td.attr('rowspan')) $td.siblings('td').removeClass().addClass(getModeClass()); else { // Find span count in first td. Select next rows. var spanCount = $td.attr('rowspan'); $td.parent().nextAll().addBack().slice(0, spanCount).children('td').removeClass().addClass(getModeClass()); } }; //apply class to col range setColSel = function ($col) { var $found = findTdCol($col); if($found) $found.removeClass().addClass(getModeClass()); }; findTdCol = function($col){ var h = $col.attr("h"); var m = $col.attr("m"); var colspan = $col.attr("colspan"); if(colspan == undefined) colspan = 1; else colspan = parseInt(colspan); var $found = null; if (h) { var index = parseInt(h); $found = $tbody.find("td[h=" + index + "]"); if(m){ if(colspan > 1){ var filters = []; var offset = (m === "00" ) ? 0 :30; for(var i =0; i < colspan; i++){ filters.push("td[m='" + pad2(offset + (30/colspan)*i) +"']"); } $found = $found.filter(filters.join(',')); } else if (colspan === 1) { $found = $found.filter("td[m='" + m +"']"); } } } else $found = $tbody.find("td"); return $found; }; getMode = function () { if (modeSelected < 0 || modeSelected >= defaults.modes.length) return null; else return defaults.modes[modeSelected]; }; getModeClass = function () { var _mode = getMode(); return (_mode != null ? _mode.class : null); }; getSlice2 = function(val){ return ("0" + val).slice(-2); }; pad2 = function(num) { var size=2; var s = num+""; while (s.length < size) s = "0" + s; return s;}; createPlanning = function (isOddEven) { planning = []; for (var i = 1; i <= (isOddEven ? 14 : 7); i++) planning.push({ "day": i, "triggers": [] }); }; getPlanning = function () { var plan = []; $tbody.find('tr').each(function (index, row) { var day = { "day": index+1 , "triggers" :[]}; var prev = null; var prevDeactivated = null; $(row).find("td[class!='']").each(function (ndx, td) { var $this = $(td); var deactivated = $this.hasClass("deactivate"); var cls = $this.prop("classList")[0]; if (cls != prev || prevDeactivated != deactivated) { prev = cls; prevDeactivated = deactivated; day.triggers.push({ "hour": + $this.attr("h"), "min": + $this.attr("m"), "value": getValueFromClass(cls) , "class" : cls, "active": !deactivated }); } }); plan.push(day); }); return plan; }; loadPlanning = function (json) { if(!$element.is(':visible')) return; convertSetPointstoTriggers(json); if($table && $table.find('tbody tr').length >0 ) Clear(); else initPlanningTable(); showPlanning(); }; showPlanning = function(){ setModes(); $.each(planning, function (index, day) { var $tds = $tbody.find("tr[index="+ (day.day) + "] td"); var prev = { "hour": -1, "class": "", "min":-1}; var nbticks = defaults.nbTicksPerHour; $.each(day.triggers, function (ndx, hour) { if (prev.hour !== -1) $tds.slice(getNbTicksToAdd(nbticks, prev), getNbTicksToAdd(nbticks, hour)).addClass(prev.class); prev = hour; $tds.slice(getNbTicksToAdd(nbticks, hour)).removeClass().addClass(hour.class); }); }); }; getNbTicksToAdd = function(nbticks, obj){ if(nbticks === 1) return obj.hour; else { var divider = 60/nbticks; return obj.hour *nbticks + (obj.min/divider); } }; convertTriggersToSetPoints = function (planning) { var setPoints = []; $.each(planning, function (index, day) { $.each(day.triggers, function (ndx, elt) { var setPoint = {"Active":(this.active ? "true" : "false"), "Date": "", "MDay": 0, "Month": 0, "Occurence": 0, "Type": 2 }; setPoint[defaults.propValue] = this.value; setPoint.Hour = this.hour; setPoint.Time = getSlice2(this.hour) + ":" + getSlice2(this.min) ;//("0" + this.hour).slice(-2) + ":" + ("0" + this.min).slice(-2); setPoint.Min = this.min; if(defaults.odd_even_week) setPoint.Type = (day.day > 7 ? EVEN : ODD ); setPoint.Days = Math.pow(2, day.day - (setPoint.Type === EVEN ? 8 : 1)); setPoints.push(setPoint); }); }); //search sames setPoint to another days to mutualize setPoint for (var i = 0; i < setPoints.length; i++) { var sp = setPoints[i]; var sameSetPoints = []; var newSetPoints = setPoints.filter(function (setpoint, index) { if (setpoint.Time === sp.Time && setpoint.Active === sp.Active && setpoint[defaults.propValue] == sp[defaults.propValue] && setpoint.Days != sp.Days && setpoint.Type === sp.Type) { sameSetPoints.push(setpoint); return false; } else return true; }) if (sameSetPoints.length) { for (var j = 0; j < sameSetPoints.length; j++) { sp.Days |= sameSetPoints[j].Days; } } setPoints = newSetPoints; } return setPoints; }; convertSetPointstoTriggers = function (SetPoints) { //check if you have to display odd&even week var isOddEven = false; var nbTicks = 2; $.each(SetPoints, function (ndx) { if (this.Type === ONTIME || this.Type === EVEN || this.Type === ODD){ if (this.Type === EVEN || this.Type === ODD){ isOddEven = true; return false; } } }); $element.find('#oew_oddeven').attr('checked', isOddEven); if(defaults.odd_even_week !== isOddEven){ defaults.odd_even_week =isOddEven; } createPlanning(isOddEven); var values = []; prevTimers = []; $.each(SetPoints, function (ndx) { //if (this.Active && (this.Type === ONTIME || this.Type === EVEN || this.Type === ODD)) { if (this.Type === ONTIME || this.Type === EVEN || this.Type === ODD) { prevTimers.push(this.idx); var trigger = {}; var day = 1; trigger.hour = parseInt(this.Time.substr(0, 2)); trigger.min = parseInt(this.Time.substr(3, 2)); trigger.value = this[defaults.propValue]; trigger.active = (this.Active === 'true'); if(this.Cmd ===1 && defaults.propValue === "Level" && this.Level === 100) trigger.value=0; if( values.indexOf(trigger.value) === -1) values.push(trigger.value); var days = []; if(this.Type === EVEN){ if (this.Days === EVERYDAYS) days.push(8, 9, 10, 11, 12, 13, 14); else if (this.Days === WEEKDAYS) days.push(8, 9, 10, 11, 12); else if (this.Days === WEEKENDS) days.push(13, 14); else { if (this.Days & MON) days.push(8); if (this.Days & TUE) days.push(9); if (this.Days & WED) days.push(10); if (this.Days & THU) days.push(11); if (this.Days & FRI) days.push(12); if (this.Days & SAT) days.push(13); if (this.Days & SUN) days.push(14); } } else{ if (this.Days === EVERYDAYS) days.push(1, 2, 3, 4, 5, 6, 7); else if (this.Days === WEEKDAYS) days.push(1, 2, 3, 4, 5); else if (this.Days === WEEKENDS) days.push(6, 7); else { if (this.Days & MON) days.push(1); if (this.Days & TUE) days.push(2); if (this.Days & WED) days.push(3); if (this.Days & THU) days.push(4); if (this.Days & FRI) days.push(5); if (this.Days & SAT) days.push(6); if (this.Days & SUN) days.push(7); } } $.each(days, function (ndx, val) { planning[val-1].triggers.push($.extend({},trigger)); }); }; }); values.sort(function (a, b) { return b - a; }); //desc order if(defaults.temperatureModes){ var valuescount = values.length; var modescount = defaults.modes.length; var $modes = $element.find('.modes .mode input'); if(valuescount< modescount){ for(var i = 0; i < valuescount ; i++){ var j = i; while(j < modescount){ if(values[i] >= defaults.modes[j].value || j === (modescount-1)){ defaults.modes[j].value = values[i]; break; } else if(values[i] < defaults.modes[j].value && values[i] > defaults.modes[j+1].value){ defaults.modes[j].value = values[i]; break; } j++; } } } else { for(var i= 0; i < defaults.modes.length; i++){ defaults.modes[i].value = values[i]; } } for(var i= 0; i < defaults.modes.length; i++){ $modes.filter('[index=' + i + ']').val(defaults.modes[i].value); } } $.each(planning, function (ndx, day) { $.each(day.triggers, function(index, trigger){ trigger.class = getClassFromValue(trigger.value); if(!trigger.active) trigger.class += " deactivate"; }); day.triggers.sort(function (a, b) { if (a.hour === b.hour) return 0; else return (a.hour < b.hour) ? -1 : 1; }); $.each(day.triggers, function (ndx, trigger) { if(trigger.min === 0) return; if( trigger.min % 30 === 0) nbTicks = (nbTicks < 2) ? 2 : nbTicks; else if(trigger.min % 15 === 0 ) nbTicks = (nbTicks < 4) ? 4 : nbTicks; else if(trigger.min % 10 === 0 ) nbTicks = (nbTicks < 6) ? 6 : nbTicks; else if(trigger.min % 5 === 0 ) nbTicks = (nbTicks < 12) ? 12 : nbTicks; else if(trigger.min % 2 === 0 ) nbTicks = (nbTicks < 30) ? 30 : nbTicks; if(nbTicks === 30) return false; }); }); if(defaults.nbTicksPerHour < nbTicks){ defaults.nbTicksPerHour = nbTicks; } }; updateSetPoints = function(active){ var plan = getPlanning(); var setPoints = convertTriggersToSetPoints(plan); var activeForced = (arguments.length === 1 ? true: false); var timers= []; $.each(setPoints, function(ndx, tsettings){ if(tsettings.Days === SUM_EVERYDAYS) tsettings.Days = EVERYDAYS; else if(tsettings.Days === SUM_WEEKDAYS) tsettings.Days = WEEKDAYS; else if(tsettings.Days === SUM_WEEKENDS) tsettings.Days = WEEKENDS; var urlcmd = "json.htm?type=command¶m=" + defaults.commandAdd +"&idx=" + defaults.devid + "&active=" + (activeForced ? active : tsettings.Active) + "&timertype=" + tsettings.Type + "&date=" + tsettings.Date + "&hour=" + tsettings.Hour + "&min=" + tsettings.Min + "&days=" + tsettings.Days + "&mday=" + tsettings.MDay + "&month=" + tsettings.Month + "&occurence=" + tsettings.Occurence + "&randomness=false"; if(!(defaults.propValue === "Level" && tsettings[defaults.propValue] === 0)) { urlcmd += "&" + defaults.propValueAjax +"=" + tsettings[defaults.propValue]; if(!defaults.temperatureModes && defaults.propValueAjax !== "command") urlcmd += "&command=0"; } else urlcmd += "&command=1"; timers.push(urlcmd); }); clearPrevSetPoints(addSetPointTimers, timers); }; clearPrevSetPoints = function(callbackFunc, param){ var timers = []; $.each(prevTimers, function(ndx, timerid){ timers.push("json.htm?type=command¶m=delete"+ (defaults.temperatureModes ? "setpoint" : "") + "timer&idx=" + timerid); }); runAjaxLoop(timers,'Problem deleting timer!',0, callbackFunc, param ); }; addSetPointTimers = function(param){ runAjaxLoop(param,'Problem adding timer!',0, defaults.refreshCallback); }; runAjaxLoop = function(loopParams, errorMsg, curIndex, callbackFunc, param){ if(loopParams.length > 0) $.ajax({ url: loopParams[curIndex], dataType: 'json', error: function () { HideNotify(); ShowNotify($.t(errorMsg), 2500, true); }, complete: function(){ curIndex++; if(curIndex < loopParams.length) runAjaxLoop(loopParams, errorMsg, curIndex, callbackFunc, param); else if(callbackFunc) callbackFunc(param); } }); else if(callbackFunc) callbackFunc(param); }; getValueFromClass = function (cls) { var res = $.grep(defaults.modes, function (elt, ndx) { return elt.class === cls; }); if (res != null && res.length > 0) return res[0].value; else return null; }; getClassFromValue = function (val) { var res = $.grep(defaults.modes, function (elt, ndx) { return elt.value == val; }); if (res != null && res.length > 0) return res[0].class; else return null; }; setModes = function(){ var _modes = []; $.each(defaults.modes, function(index, mode){ _modes.push( "
"); if(defaults.temperatureModes) _modes.push("°"); else _modes.push("
"); _modes.push("
"); }); $element.find('.modes').off().empty().append(_modes.join('')); /////click event on mode buttons $element.find('.modes').on("click", ".mode", function () { var $elt = $(this); if(defaults.buttonModeClassSelected != undefined){ $elt.siblings().removeClass(defaults.buttonModeClassSelected); $elt.addClass(defaults.buttonModeClassSelected); } modeSelected = $elt.attr("index"); }).on("change", ".mode input", function(){ var $elt = $(this); defaults.modes[parseInt($elt.attr('index'))].value = $elt.val(); }); }; getColSpan = function(level){ if(level === 0) { return defaults.nbTicksPerHour || 4; } else if (level === 1){ switch (defaults.nbTicksPerHour) { case 2: return 1; case 6: return 3; case 12: return 6; case 30: return 15; default: return 2; } } else return 1; }; createTable = function(){ var headers = []; headers.push(" 1) headers.push("rowspan="+ (defaults.nbTicksPerHour === 2 ? 2 : 3)); headers.push( (defaults.odd_even_week ? " colspan=2" : "") + ">" + $.t("All") + ""); for (var h = 0; h < 24 ; h++) { headers.push("" + h + ""); } headers.push(""); if(defaults.nbTicksPerHour >=2){ headers.push(""); var ticks = 2; for (var h = 0 ; h < 24; h++) { for (var m = 0; m < ticks; m++) { headers.push(""); } } headers.push(""); if(defaults.nbTicksPerHour >=4){ headers.push(""); ticks = defaults.nbTicksPerHour; for (var h = 0 ; h < 24; h++) { for (var m = 0; m < ticks; m++) { headers.push(""); } } headers.push(""); } } var tds = []; createBodyRows(tds, 0); if(defaults.odd_even_week) createBodyRows(tds, 7); if(defaults.odd_even_week) $table.addClass('odd-even'); else $table.removeClass('odd-even'); $thead.empty().append(headers.join('')); $tbody.empty().append(tds.join('')); }; createBodyRows = function (tds, delta) { for (var d = 0; d < days.length; d++) { if(d === 0 && defaults.odd_even_week) tds.push(""+ $.t( (delta === 0 ? "Odd Week Numbers": "Even Week Numbers")) +"" + $.t(days[d].name) + ""); else tds.push("" + $.t(days[d].name) + ""); var ticks = defaults.nbTicksPerHour; for (var h = 0 ; h < 24; h++) { for (var m = 0; m < ticks; m++) { tds.push(""); } } tds.push(""); } }; initUI = function(){ $element = $(defaults.container).find('#ts-planning'); if($element.length === 0 ) $element = $('
').appendTo(defaults.container); else { $element.off(); if($tbody) $tbody.off(); if($table) $table.off(); } $(defaults.container).find('#tabs').tabs() if(!(defaults.nbTicksPerHour === 1 || defaults.nbTicksPerHour === 2 || defaults.nbTicksPerHour ===4 || defaults.nbTicksPerHour ===6 || defaults.nbTicksPerHour ===12 || defaults.nbTicksPerHour ===30)) defaults.nbTicksPerHour === 2; $element.empty().append('
\
\
\ \ \ \
\
\
\
'+ $.t('Clear') +'
\
'+ $.t('Update') +'
\ \
'+ $.t('deactivate all') +'
\
'+ $.t('activate all') +'
\
'); if(defaults.buttonClass != undefined) $element.find('.ts-actions>div').addClass(defaults.buttonClass); $table = $element.find('table'); $tbody = $table.find('tbody'); $thead = $table.find('thead'); //////mouse events on tbody $tbody.on("mousedown", "td", function (event) { bIsSelecting = true; bAddClass = !$(this).hasClass(getModeClass()); setSel(this); }); $tbody.on("mouseup", "td", function (event) { setSel(this); bIsSelecting = false; }); $tbody.on("mouseover", "td", function (event) { if (bIsSelecting) setSel(this); }); $tbody.on("click", "th", function (event) { setRowSel($(this)); }); $table.on("click", "thead th", function (event) { setColSel($(this)); }); $element.find('.ts-clearTimeSheet').click(function(){ Clear(); }); $element.find('.ts-updateSetPoints').click(function(){ updateSetPoints(); }); $element.find('.ts-DisableSetPoints').click(function(){ updateSetPoints('false'); }); $element.find('.ts-EnableSetPoints').click(function(){ updateSetPoints('true'); }); $element.find('.timeRange').change(function(){ defaults.nbTicksPerHour = parseInt($(this).val()); initPlanningTable(); showPlanning(); }); $('#oew_oddeven').change(function () { defaults.odd_even_week = $(this).is(':checked'); initPlanningTable(); showPlanning(); }); if(defaults.hover_activate){ $tbody.on("mouseover", "th" , function(event){ $(this).parent().children("td").addClass("hover"); }); $tbody.on("mouseout", "th" , function(event){ $(this).parent().children("td").removeClass("hover"); }); $thead.on("mouseover", "th", function (event) { findTdCol($(this)).addClass("hover"); }); $thead.on("mouseout", "th", function (event) { findTdCol($(this)).removeClass("hover"); }); } }; initPlanningTable = function () { createTable(); if(defaults.nbTicksPerHour >= 12){ $element.find('.ts-wrapper').removeClass('zoom zoom2').addClass((defaults.nbTicksPerHour === 12)?'zoom':'zoom2'); }else $element.find('.ts-wrapper').removeClass('zoom zoom2'); $element.find('.timeRange option[value="'+ defaults.nbTicksPerHour + '"]').prop('selected',true); $element.show(); }; $( document ).on( "timersLoaded", function(event, items){ loadPlanning(items); }); $.each(planning_translations, function(idx, res){ $.i18n.addResources(res.lng,'translation',res.res); }); return initUI(); }; /////////////////////////////////////////// $( document ).on( "timersInitialized", function(event, vm,refreshTimers){ var options = {"devid":vm.deviceIdx, "refreshCallback":function(){refreshTimers();} }; if( !vm.isSetpointTimers && ((vm.isDimmer || vm.isSelector) || !vm.IsLED)){ $.extend(options, {"commandAdd":"addtimer", "commandClear":"cleartimers","temperatureModes":false} ); if((vm.isDimmer || vm.isSelector)) { var levels = vm.levelOptions; if(!vm.isCommandSelectionDisabled) levels.unshift({"label":"Off","value":0}); $.extend(options, { "modes": $.map( levels, function(val,i){ return {"value":val.value, "class":"m"+ (val.value == null? i: val.value/10) , "name":val.label };}), "propValue":"Level", "propValueAjax":"level"}); } else $.extend(options, { "modes": [{"value":0,"name":$.t("On"), "class":"green"},{"value":1,"name":$.t("Off"), "class":"red"}], "propValue":"Cmd", "propValueAjax":"command"}); } PlanningTimerSheet(options); }); ///////////////////////////////////////////