Skip to content

Instantly share code, notes, and snippets.

@wvhn
Last active February 13, 2026 18:42
Show Gist options
  • Select an option

  • Save wvhn/e083451bf324aaa8d6550644bd4780dc to your computer and use it in GitHub Desktop.

Select an option

Save wvhn/e083451bf324aaa8d6550644bd4780dc to your computer and use it in GitHub Desktop.
smartVISU v3.6 fix for device.uzsugraph other than smarthomeNG
/**
* -----------------------------------------------------------------------------
* @package smartVISU
* @author Martin Gleiß and others
* @copyright 2012 - 2025
* @license GPL [http://www.gnu.de]
* -----------------------------------------------------------------------------
*/
/**
* Standard blind
*
* @param {id=} unique id for this widget (optional)
* @param {text=} name of the blind
* @param {item(bool,num)=} an item for the up and down movement (optional, value_top/value_bottom will be sent to item_pos if omitted)
* @param {item(bool,num)=} an item for stopping the movement (optional)
* @param {item(num)} an item for the absolute position of the blinds
* @param {item(bool,num)=} an item for increase and decrease of the blade (optional)
* @param {item(num)=} an item for the absolute angle of the blade (optional)
* @param {value=0} the value for opened (optional, default 0)
* @param {value=255} the value for closed (optional, default 255)
* @param {value=5} step between two values (optional, default: 5)
* @param {value=1} 'live mode': if enabled, values will be sent during sliding. '0' sends values only when sliding is stopped, after click into track or if value is edited in input field. (optional, default = 1)
*
* @info inspired by Frank Berlenz
*/
{% macro blind(id, txt, item_move, item_stop, item_pos, item_shift, item_angle, value_top, value_bottom, step, live) %}
{% import "@widgets/basic.html" as basic %}
<div class="blind"{% if not id is empty %} id="{{ uid(page, id) }}"{% endif %}>
<table align="center" cellpadding="0">
<tr>
<td valign="top">
<div class="set">
{% if item_move is empty %}
{{ basic.stateswitch(id~'-up', item_pos, '', value_top|default('0'), 'carat-u') }}
{% else %}
{{ basic.stateswitch(id~'-up', item_move, '', value_bottom|default('255') < value_top|default('0') ? 1 : 0, 'carat-u', '', '', 'blink' ) }}
{% endif %}
</div>
</td>
<td rowspan="3" align="left" class="pos">
{{ basic.slider(id~'-pos', item_pos, value_top|default('0'), value_bottom|default('255'), step, 'vertical', '', '', '', live) }}</td>
<td rowspan="2" valign="bottom" class="angle">
{{ txt }}<br />
{% if item_angle %} {{ basic.slider(id~'-angle', item_angle, value_top|default('0'), value_bottom|default('255'), step, 'semicircle', '', '', '', live) }} {% endif %}
</td>
</tr>
<tr>
<td>
{% if item_stop %}
<div class="set">{{ basic.stateswitch(id~'-stop', item_stop, '', 1, 'delete') }}</div> {% endif %}</td>
</tr>
<tr>
<td valign="bottom">
<div class="set">
{% if item_move is empty %}
{{ basic.stateswitch(id~'-down', item_pos, '', value_bottom|default('255'), 'carat-d') }}
{% else %}
{{ basic.stateswitch(id~'-down', item_move, '', value_bottom|default('255') < value_top|default('0') ? 0 : 1, 'carat-d', '', '', 'blink' ) }}
{% endif %}
</div>
</td>
<td valign="bottom">
{% if item_shift %}
<div class="set">
<span style="float: left;">{{ basic.stateswitch(id~'-minus', item_shift, '', 0, 'minus') }}</span>
<span style="float: right;">{{ basic.stateswitch(id~'-plus', item_shift, '', 1, 'plus') }}</span>
</div>
{% endif %}
</td>
</tr>
</table>
</div>
{% endmacro %}
/**
* The Codepad is used to protect some widgets. You have to enter a correct code to use the widgets for a time.
* You may specify a global code in the config.php for all codepads.
*
* @param {id} unique id for this widget
* @param {value=} code to be entered (optional, default in config)
* @param {text=} a title for the pad (optional)
* @param {duration=10s} how long should the widgets be unlocked (optional, default: 10 seconds)
*/
{% macro codepad(id, code, title, duration) %}
<div{% if not id is empty %} id="{{ uid(page, id) }}"{% endif %} data-id="{{ id }}" data-widget="device.codepad" data-val="{{ code|default(config_codepad_code)|md5 }}" data-duration="{{ duration|default('10s') }}"
data-role="popup" data-overlay-theme="a" class="alert" style="width: 166px;" >
<div data-role="header" data-theme="c"><h1>{{ title|default('Enter Code') }}</h1></div>
<input type="password" style="text-align: center;"/>
<div style="margin: 10px;">
{% for i in 1..9 %}
<a data-val="{{ i }}" data-role="button" data-inline="true" data-iconpos="nopic" data-mini="true" >{{ i }}</a>
{% endfor %}
<a data-val="-" data-role="button" data-inline="true" data-iconpos="nopic" data-mini="true" >-</a>
<a data-val="0" data-role="button" data-inline="true" data-iconpos="nopic" data-mini="true" >0</a>
<a data-val="ok" data-role="button" data-inline="true" data-iconpos="nopic" data-mini="true" >OK</a>
</div>
</div>
{% endmacro %}
/**
* Standard dimmer with switching and dimming function
*
* @param {id=} unique id for this widget (optional)
* @param {text=} name of the dimmer
* @param {item(bool,num)} an item for switching
* @param {item(num)} an item for dimming
* @param {value=0} the minimum value if the slider is moved to total left (optional, default: 0)
* @param {value=255} the maximum value if the slider is moved to total right (optional, default: 255)
* @param {value=5} step between two values (optional, default: 5)
* @param {image=light_light} the pic for the 'on' state (optional)
* @param {image=light_light} the pic for the 'off' state (optional)
* @param {color(icon0to5)=icon1} the color for the 'on' state (optional)
* @param {color(icon0to5)=icon0} the color for the 'off' state (optional)
* @param {text(input,handle,both,none)=none} how should the value be shown; possible options: 'input', 'handle', 'both', 'none' (optional, default 'none')
* @param {value=} the minimum value to display if the slider is moved to total left if this should differ from sent/received value (optional, default like min)
* @param {value=} the maximum value to display if the slider is moved to total right if this should differ from sent/received value (optional, default like max)
* @param {text(left,right)=left} position of the switch: left (default) or right
* @param {value=1} 'live mode': if enabled, values will be sent during sliding. '0' sends values only when sliding is stopped, after click into track or if value is edited in input field. (optional, default = 1)
* @param {type=icon} icon or button type. Valid types: 'micro', 'mini', 'midi', 'icon' (optional, default: icon)
*/
{% macro dimmer(id, txt, item_switch, item_value, min, max, step, pic_on, pic_off, color_on, color_off, value_display, min_display, max_display, picpos, live, type) %}
{% import "@widgets/basic.html" as basic %}
<div class="
{{ picpos is empty or picpos=='left' ? 'dimmer-left' : 'dimmer-right' }}
{{ type in ['midi', 'mini', 'micro'] ? 'dimmer-'~type : '' }}
{{ txt is empty ? 'dimmer-notext' : '' }} "
data-widget="device.dimmer"{% if not id is empty %} id="{{ uid(page, id) }}"{% endif %}>
{{ basic.stateswitch(id~'-switch', item_switch, type|default('icon'), '', [ pic_off|default('light_light.svg'), pic_on|default('light_light.svg') ], '', [ color_off , color_on|default('icon1') ]) }}
<p>{{ txt|e }}</p>
{{ basic.slider(id~'-slider', item_value, min, max, step, 'horizontal', value_display|default('none'), min_display, max_display, live) }}
</div>
{% endmacro %}
/**
* Roof window
*
* @param {id=} unique id for this widget (optional)
* @param {text=} name of the window
* @param {item(bool,num)=} an item for the open and close movement (optional, value_top/value_bottom will be sent to item_pos if omitted)
* @param {item(bool,num)=} an item for stopping the movement (optional)
* @param {item(num)} an item for the absolute position
* @param {item(num)=} an item for some saved positions (optional)
* @param {value=0} the value for closed (optional, default 0)
* @param {value=255} the value for opened (optional, default 255)
* @param {value=5} step between two values (optional, default 5)
* @param {value=0} the value to send for position 1 (optional, default 0)
* @param {value=1} the value to send for position 2 (optional, default 1)
* @param {text=Pos1} the text printed on button for position 1 (optional, default Pos1)
* @param {text=Pos2} the text printed on button for position 2 (optional, default Pos2)
*
* @author Stefan Widmer
*/
{% macro roofwindow(id, txt, item_move, item_stop, item_pos, item_saved, value_closed, value_opened, step, value_pos_1, value_pos_2, text_pos_1, text_pos_2) %}
{% import "@widgets/basic.html" as basic %}
{% import "@widgets/icon.html" as icon %}
<div{% if not id is empty %} id="{{ uid(page, id) }}"{% endif %} data-widget="device.roofwindow" data-item="{{ item_pos }}"
data-min="{{ value_closed|default('0') }}" data-max="{{ value_opened|default('255') }}" data-step="{{ step|default(5) }}"
class="roofwindow">
<table align="center" cellpadding="0">
<tr>
<td valign="top">
<div class="set">
{% if item_move is empty %}
{{ basic.stateswitch(id~'-up', item_pos, '', value_opened|default('255'), 'carat-u') }}
{% else %}
{{ basic.stateswitch(id~'-up', item_move, '', value_opened|default('255') < value_top|default('0') ? 1 : 0, 'carat-u', '', '', 'blink' ) }}
{% endif %}
</div>
</td>
<td rowspan="3" valign="center" class="pos">
<div class="control hide">
<img class="icon pos" src= "{{ icon0 }}/control_arrow_up_down.svg" alt="up/down" />
</div>
{{ icon.roofwindow(id~'-window', '', item_pos, value_closed|default('0'), value_opened|default('255')) }}
</td>
<td valign="top">
{% if item_saved %}
<div class="set">{{ basic.stateswitch(id~'-saved1', item_saved, '', value_pos_1|default(0), 'grid', text_pos_1|default('Pos1'), '', 'blink') }}</div>
{% endif %}
</td>
</tr>
<tr>
<td valign="center">
{% if item_stop %}
<div class="set">{{ basic.stateswitch(id~'-stop', item_stop, '', 1, 'delete') }}</div>
{% endif %}
</td>
<td valign="center">{{ txt }}</td>
</tr>
<tr>
<td valign="bottom">
<div class="set">
{% if item_move is empty %}
{{ basic.stateswitch(id~'-down', item_pos, '', value_closed|default('0'), 'carat-d') }}
{% else %}
{{ basic.stateswitch(id~'-down', item_move, '', value_opened|default('255') < value_top|default('0') ? 0 : 1, 'carat-d', '', '', 'blink' ) }}
{% endif %}
</div>
</td>
<td valign="bottom">
{% if item_saved %}
<div class="set">{{ basic.stateswitch(id~'-saved2', item_saved, '', value_pos_2|default(1), 'grid', text_pos_2|default('Pos2'), '', 'blink') }}</div>
{% endif %}
</td>
</tr>
</table>
</div>
{% endmacro %}
/**
* Standard RTR (Room Temperatur Regulator)
*
* @param {id=} unique id for this widget (optional)
* @param {text=} name of the rtr
* @param {item(num)} an item for the actual temperature
* @param {item(num)} an item for the set temperature
* @param {item(bool,num)=} an item for comfort / standby (3x bit or 1x byte, optional)
* @param {item(bool,num)=} an item for night (3x bit or 1x byte, optional)
* @param {item(bool,num)=} an item for frost (3x bit or 1x byte, optional)
* @param {item(bool,num)=} an item for the current state of the actuator (optional)
* @param {item(str)=} an item for a text (some rtr have a text-display), (optional)
* @param {value=0.5} step for plus/minus buttons (optional, default: 0.5)
* @param {item(num)=} an item for the offset temperature (optional, if provided set temperature changes will be written to this item instead of 'item_set')
* @param {text[]=} list of additional widgets / content to display
*/
{% macro rtr(id, txt, item_actual, item_set, item_comfort, item_night, item_frost, item_state, item_txt, step, item_offset, supplements) %}
{% import "@widgets/basic.html" as basic %}
<div{% if not id is empty %} id="{{ uid(page, id) }}"{% endif %} data-widget="device.rtr" class="rtr">
<div class="actual">
<div class="temp">{{ basic.print('', item_actual, 'temp' ) }}</div>
<div class="text">{{ txt }} {% if item_txt %} {{ basic.print('', item_txt, 'text') }} {% endif %}</div>
</div>
<br>
{% if item_set %}
<div class="set">
{{ basic.offset(id~'-minus', item_offset|default(item_set), -(step|default(0.5)), 'minus', '', 'micro' ) }}
<div class="temp">{{ basic.print('', item_set, 'temp' ) }}</div>
{{ basic.offset(id~'-plus', item_offset|default(item_set), step|default(0.5), 'plus', '', 'micro' ) }}
</div>
{% endif %}
<br>
{% if item_comfort is not empty or item_night is not empty or item_frost is not empty or item_state is not empty or supplements is not empty %}
<div class="control">
{% if item_comfort is not empty and (item_comfort == item_night or item_night is empty) and (item_comfort == item_frost or item_frost is empty) %}
{{ basic.stateswitch(id~'-comfort', item_comfort, 'icon', (config_driver == 'linknx' ? ['comfort', 'standby'] : [1, 2]), ['user_available.svg', 'user_away.svg'], '', ['icon1', 'icon0']) }}
{{ basic.stateswitch(id~'-night', item_comfort, 'icon', (config_driver == 'linknx' ? ['night', 'standby'] : [3, 2]), ['scene_night.svg', 'scene_night.svg'], '', ['icon1', 'icon0']) }}
{{ basic.stateswitch(id~'-frost', item_comfort, 'icon', (config_driver == 'linknx' ? ['frost', 'standby'] : [4, 2]), ['weather_frost.svg', 'weather_frost.svg'], '', ['icon1', 'icon0']) }}
{% else %}
{% if item_comfort is not empty %}
{{ basic.stateswitch(id~'-comfort', item_comfort, 'icon', '', ['user_away.svg', 'user_available.svg']) }}
{% endif %}
{% if item_night is not empty %}
{{ basic.stateswitch(id~'-night', item_night, 'icon', '', ['scene_night.svg', 'scene_night.svg']) }}
{% endif %}
{% if item_frost is not empty %}
{{ basic.stateswitch(id~'-frost', item_frost, 'icon', '', ['weather_frost.svg', 'weather_frost.svg']) }}
{% endif %}
{% endif %}
{% if item_state is not empty %}
{{ basic.stateswitch(id~'-state', item_state, 'icon', '', 'sani_heating.svg') }}
{% endif %}
{% for supplement in supplements %}
{{ supplement|raw }}
{% endfor %}
</div>
{% endif %}
</div>
{% endmacro %}
/**
* Displays a round slider for RTR
* @param {id=} unique id for this widget (optional)
* @param {item(num)} an item for the actual temperature
* @param {item(num)} an item for the set temperature
* @param {item(bool,num)=} an item for comfort / standby (3x bit or 1x byte, optional)
* @param {item(bool,num)=} an item for night (3x bit or 1x byte, optional)
* @param {item(bool,num)=} an item for frost (3x bit or 1x byte, optional)
* @param {item(bool,num)=} an item for the current state of the actuator (optional)
* @param {value=18} the minimum value if the slider is moved to total left (optional, default: 18)
* @param {value=28} the maximum value if the slider is moved to total right (optional, default: 28)
* @param {value=.1} step between two values (optional, default: .1)
* @param {text(false,true)=false} display a scale with values along the slider (optional, default='true')
* @param {value=1} scale-integer, the scale-divider for the scale
* @param {item(num)=} an item for the offset temperature (optional, if provided set temperature changes will be written to this item instead of 'item_set')
* @param {text[]=} list of additional widgets / content to display
* @param {value=0} tolerance by which values entered with +/- buttons are allowed to exeed min/max values (optional, default: limiting to min/max)
*
* @author Bonze, Wolfram v. Hülsen
*/
{% macro rtrslider(id, item_actual, item_set, item_comfort, item_night, item_frost, item_state, min, max, step, scale, scale_interval, item_offset, supplements, tolerance) %}
{% import "@widgets/basic.html" as basic %}
/** set displayed decimals to decimals of step parameter */
{% set setformat = lang ('format', 'temp') %}
{% set stepparts = step|default('.1')|split('.') %}
{% set decs = (stepparts[1] >0 ? stepparts[1]|length : 0) %} {% set setformat = setformat|replace(',1f', ','~decs~'f') %}
<div {% if not id is empty %} id="{{ uid(page, id) }}"{% endif %} data-widget="device.rtrslider" data-item="{{ item_actual }}, {{ item_set }}, {{ item_offset }}"
data-scale_min="{{min | default('18')}}" data-scale_max="{{max| default('28')}}" data-step="{{step | default('.1')}}"
data-scale_interval="{{scale_interval | default('1')}}" style="margin:auto; position:relative;">
<div id="container" style="display: inline-flex; align-content: center; position:relative; height: 140px; width:140px; margin: 30px 30px -30px 30px;">
<div class="outerslider" id ="{{ uid(page, id) }}" style="position:absolute; top:0px; margin:auto; z-index: 0;"> </div>
<div class="innerslider" id ="{{ uid(page, id) }}" style="position:absolute; margin:auto; top:8px; left:8px;z-index: 2;"> </div>
<div class="set" style="position:absolute; top:80px; left: 32px; min-width:76px; z-index: 1;">
<div class="temp" style="margin-bottom:15px;">Soll: {{ basic.print('', item_set, setformat ) }}</div>
</div>
</div>
</div>
/** elements copied from device.rtr **/
{% if item_set %}
{% if tolerance is empty %} {% set tolerance = '0' %} {% endif %}
{% set min = min | default('18') - tolerance %}
{% set max = max | default('28') + tolerance %}
<div class="set" style="position:relative; z-index: 3; padding: 0 65px 0px 65px">
<div>
{{ basic.offset(id~'-minus', item_offset|default(item_set), -(step|default(0.1)), 'minus', '', 'micro', min, max) }}
{{ basic.offset(id~'-plus', item_offset|default(item_set), step|default(0.1), 'plus', '', 'micro', min, max) }}
</div>
</div>
{% endif %}
{% if item_comfort is not empty or item_night is not empty or item_frost is not empty or item_state is not empty or supplements is not empty %}
<div class="control" style="position:relative;">
{% if item_comfort is not empty and (item_comfort == item_night or item_night is empty) and (item_comfort == item_frost or item_frost is empty) %}
{{ basic.stateswitch(id~'-comfort', item_comfort, 'icon', (config_driver == 'linknx' ? ['comfort', 'standby'] : [1, 2]), ['user_available.svg', 'user_away.svg'], '', ['icon1', 'icon0']) }}
{{ basic.stateswitch(id~'-night', item_comfort, 'icon', (config_driver == 'linknx' ? ['night', 'standby'] : [3, 2]), ['scene_night.svg', 'scene_night.svg'], '', ['icon1', 'icon0']) }}
{{ basic.stateswitch(id~'-frost', item_comfort, 'icon', (config_driver == 'linknx' ? ['frost', 'standby'] : [4, 2]), ['weather_frost.svg', 'weather_frost.svg'], '', ['icon1', 'icon0']) }}
{% else %}
{% if item_comfort is not empty %}
{{ basic.stateswitch(id~'-comfort', item_comfort, 'icon', '', ['user_away.svg', 'user_available.svg']) }}
{% endif %}
{% if item_night is not empty %}
{{ basic.stateswitch(id~'-night', item_night, 'icon', '', ['scene_night.svg', 'scene_day.svg']) }}
{% endif %}
{% if item_frost is not empty %}
{{ basic.stateswitch(id~'-frost', item_frost, 'icon', '', ['weather_frost.svg', 'weather_frost.svg']) }}
{% endif %}
{% endif %}
{% if item_state is not empty %}
{{ basic.stateswitch(id~'-state', item_state, 'icon', '', 'sani_heating.svg') }}
{% endif %}
{% for supplement in supplements %}
{{ supplement|raw }}
{% endfor %}
</div>
{% endif %}
{% endmacro %}
/**
* Standard shutter
*
* @param {id=} unique id for this widget (optional)
* @param {text=} name of the shutter
* @param {item(bool,num)=} an item for the up and down movement (optional, value_top/value_bottom will be sent to item_pos if omitted)
* @param {item(bool,num)=} an item for stopping the movement (optional)
* @param {item(num)} an item for the absolute position of the blinds
* @param {item(bool,num)=} an item for increase and decrease of the blade (optional, for future use)
* @param {item(num)=} an item for the absolute angle of the blade (optional)
* @param {item(num)=} an item for some saved positions (optional)
* @param {value=0} the value for opened (optional, default 0)
* @param {value=255} the value for closed (optional, default 255)
* @param {value=5} step between two values (optional, default 5)
* @param {text(half,full)=} 'half' blade turns from -1 to +1, 'full' blade turns from 0 to +1 (optional, default 'half')
* @param {image=} a background image url (relative to smartVISU directory or absolute); optimal size is 100px x 180px (optional)
* @param {value=0} the value to send for position 1 (optional, default 0)
* @param {value=1} the value to send for position 2 (optional, default 1)
* @param {text=Pos1} the text printed on button for position 1 (optional, default Pos1)
* @param {text=Pos2} the text printed on button for position 2 (optional, default Pos2)
*
* @info inspired by Joerg Gutowski
*/
{% macro shutter(id, txt, item_move, item_stop, item_pos, item_shift, item_angle, item_saved, value_top, value_bottom, step, mode, background, value_pos_1, value_pos_2, text_pos_1, text_pos_2) %}
{% import "@widgets/basic.html" as basic %}
<div class="blind"{% if not id is empty %} id="{{ uid(page, id) }}"{% endif %}>
<table align="center" cellpadding="0">
<tr>
<td valign="top">
<div class="set">
{% if item_move is empty %}
{{ basic.stateswitch(id~'-up', item_pos, '', value_top|default('0'), 'carat-u') }}
{% else %}
{{ basic.stateswitch(id~'-up', item_move, '', value_bottom|default('255') < value_top|default('0') ? 1 : 0, 'carat-u', '', '', 'blink' ) }}
{% endif %}
</div>
</td>
<td rowspan="3" valign="center" class="pos">
{{ basic.shutter(id~'-shutter', item_pos, item_angle, value_top|default('0'), value_bottom|default('255'), step, mode, background) }}
</td>
<td valign="top">
{% if item_saved %}
<div class="set">{{ basic.stateswitch(id~'-saved1', item_saved, '', value_pos_1|default(0), 'grid', text_pos_1|default('Pos1'), '', 'blink') }}</div>
{% endif %}
</td>
</tr>
<tr>
<td valign="center">
{% if item_stop %}
<div class="set">{{ basic.stateswitch(id~'-stop', item_stop, '', 1, 'delete') }}</div>
{% endif %}
</td>
<td valign="center">{{ txt }}</td>
</tr>
<tr>
<td valign="bottom">
<div class="set">
{% if item_move is empty %}
{{ basic.stateswitch(id~'-down', item_pos, '', value_bottom|default('255'), 'carat-d') }}
{% else %}
{{ basic.stateswitch(id~'-down', item_move, '', value_bottom|default('255') < value_top|default('0') ? 0 : 1, 'carat-d', '', '', 'blink' ) }}
{% endif %}
</div>
</td>
<td valign="bottom">
{% if item_saved %}
<div class="set">{{ basic.stateswitch(id~'-saved2', item_saved, '', value_pos_2|default(1), 'grid', text_pos_2|default('Pos2'), '', 'blink') }}</div>
{% endif %}
</td>
</tr>
</table>
</div>
{% endmacro %}
/**
* Small RTR (Room Temperatur Regulator) as table line with popup
*
* @param {id=} unique id for this widget
* @param {text=} name of the rtr
* @param {item(num)} a gad/item for the actual temperature (mandatory)
* @param {item(num)} a gad/item for the set temperature (mandatory)
* @param {item(bool,num)=} an item for comfort / standby (3x bit or 1x byte, optional)
* @param {item(bool,num)=} an item for night (3x bit or 1x byte, optional)
* @param {item(bool,num)=} an item for frost (3x bit or 1x byte, optional)
* @param {item(bool,num)=} an item for the current state of the actuator (optional)
* @param {value=0.5} step for plus/minus buttons (optional, default: 0.5)
* @param {text[]=} list of additional widgets / content to display
* @param {text[bool,num]=} type of the actor and valve state: bool=on/off, num = PWM percentage value (optional, default = bool)
*/
{% macro smallrtr(id, txt, item_actual, item_set, item_comfort, item_night, item_frost, item_state, step, supplements, statetype) %}
{% import "@widgets/basic.html" as basic %}
{% set uid = uid(page, id) %}
<div id="{{ uid }}" class="rtr">
<table style="width:100%; text-align: left; table-layout: fixed;">
<tr align = "center">
<th width="20%">{% if txt %} {{ txt }} {% endif %}</th>
<td width="11%"><div class="temp">{{ basic.print(id~'actual', item_actual, '°C' ) }}</div></td>
<td width="9%">
{{ basic.offset(id~'minus', item_set, -(step|default(0.5)), 'minus', '', 'micro') }}
</td>
<td width="11%"><div class="temp">{{ basic.print(id~'set', item_set, '°C' ) }}</div></td>
<td width="9%">
{{ basic.offset(id~'plus', item_set, (step|default(0.5)), 'plus', '', 'micro') }}
</td>
<td width="10%" >
{% if item_state %}
{% if statetype == 'num' %}
{{ basic.print(id~'state', item_state, '%', '', 0.1, ['icon0', 'icon1'] ) }}
{% else %}
{{ basic.symbol(id~'state', item_state, '', 'sani_floor_heating.svg', [0,1], '', ['icon0', 'icon1'], '','', 'mini') }}
{% endif %}
{% endif %}
</td>
<td width="20%">
{% for supplement in supplements %}
{{ supplement|raw }}
{% endfor %}
</td>
<td width="10%">
{% if item_comfort is not empty %}
{{ basic.symbol('', item_comfort, '', ['user_away', 'user_available', 'user_away'], [1,2], '>', '', '#'~uid~'-popup', 'popup', 'mini' ) }}
<div id="{{ uid }}-popup" data-role="popup">
<div>
{% if item_comfort is not empty and (item_comfort == item_night or item_night is empty) and (item_comfort == item_frost or item_frost is empty) %}
{{ basic.stateswitch(id~'sw_comfort', item_comfort, 'icon', [1, 2], ['user_available.svg', 'user_away.svg'], '', ['icon1', 'icon0']) }}
{{ basic.stateswitch(id~'sw_night', item_comfort, 'icon', [3, 2], ['scene_night.svg', 'scene_night.svg'], '', ['icon1', 'icon0']) }}
{{ basic.stateswitch(id~'sw_frost', item_comfort, 'icon', [4, 2], ['weather_frost.svg', 'weather_frost.svg'], '', ['icon1', 'icon0']) }}
{% else %}
{% if item_comfort is not empty %}
{{ basic.stateswitch(id~'sw_comfort', item_comfort, 'icon', '', ['user_away.svg', 'user_available.svg']) }}
{% endif %}
{% if item_night is not empty %}
{{ basic.stateswitch(id~'sw_night', item_night, 'icon', '', ['scene_night.svg', 'scene_night.svg']) }}
{% endif %}
{% if item_frost is not empty %}
{{ basic.stateswitch(id~'sw_frost', item_frost, 'icon', '', ['weather_frost.svg', 'weather_frost.svg']) }}
{% endif %}
{% endif %}
{% endif %}
</div>
</div>
</td>
</tr>
</table>
</div>
{% endmacro %}​
/**
* Displays a simple shutter control assembly in table format
*
* @param {id=} unique id for this widget (optional)
* @param {text=} name of the shutter
* @param {item(bool,num)} a gad/item for long move
* @param {item(bool,num)} a gad/item for stopping movement
* @param {item(num)} a gad/item for the actual position 0=open / 255=closed
* @param {item(bool,num)=} a gad/item to activate a saved position in the actuator (bool) or send a fixed position value (num) (optional)
* @param {type=micro} valid types: 'micro', 'mini', 'midi' (optional, default: micro)
* @param {value=1} a value to send to item_saved in order to trigger a saved position in the actuator (bool) or send a position value (num) (optional, default=1)
*
* @author Michael Stemmer and Wolfram v. Huelsen
*/
{% macro smallshut(id, txt, item_move, item_stop, item_position, item_saved, type, value_saved) %}
{% import "@widgets/basic.html" as basic %}
{% import "@widgets/icon.html" as icon %}
{% set uid = uid(page, id) %}
<div class="smallshut"{% if not id is empty %} id="{{ uid(page, id) }}"{% endif %} >
<span class="ui-shut-txt">
{% if txt %} {{ txt }} {% endif %}
</span>
<span class="ui-shut-btn">
{{ basic.stateswitch(id~'-up', item_move, type|default('micro'), '0', 'carat-u') }}
</span>
<span class="ui-shut-btn">
{{ basic.stateswitch(id~'-stop', item_stop, type|default('micro'), 1, 'delete') }}
</span>
<span class="ui-shut-btn">
{{ basic.stateswitch(id~'-down', item_move, type|default('micro'), '1', 'carat-d') }}
</span>
<span class="ui-shut-btn">
{% if item_saved %}
{{ basic.stateswitch(id~'-saved', item_saved, type|default('micro'), value_saved|default(1), 'fts_shutter_50.svg') }}
{% endif %}
</span>
<span class="ui-shut-pic">
{% if item_position %}
{{ icon.shutter(id~'position', '', item_position) }}
{% endif %}
</span>
</div>
{% endmacro %}
/**
* Graphical widget for usage of UZSU plugin.
Drag points, add new ones by clicking on background and edit or delete them.
*
* @param {id=} unique id for this widget (optional)
* @param {item(dict)} the item of the UZSU (NOT the item which is switched by UZSU)
* @param {text=UZSU} title of the graph (optional, default: UZSU)
* @param {value(0,1)=0} set to '1' in order to allow changes by dragging points (optional, default: 0)
* @param {text(bool,num)=bool} supported types are 'bool' and 'num' (optional, default: bool)
* @param {text[]=} parameter list for y-axis depending on valueType (optional):
bool: ['text on','text off'] or ['text1:value1','text2:value2'],
num: [min, max, step] min/max limits the y-axis scale, step is used for rounding values while dragging / adding points
* @param {text(false,true)=true} shows a scrollbar below the x-Axis (optional, default: true)
* @param {value=} identifier for the backend: 0=smarthomeNG style, 2= fhem style (not necessary, default is derived from io driver)
*
* @author Stefan Widmer
*/
{% macro uzsugraph(id, item, headline, editable, valueType, valueParameterList, scrollbar, _backend) %}
{% import "@widgets/basic.html" as basic %}
<div{% if not id is empty %} id="{{ uid(page, id) }}"{% endif %} data-widget="device.uzsugraph" data-item="{{ item }}"
data-headline="{{ headline }}" data-editable="{{ editable|default(0) }}" data-designType="{{ _backend }}"
data-valueType="{{ valueType|default('bool') }}" data-valueParameterList="{{ implode(valueParameterList) }}"
data-scrollbar="{{ scrollbar|default('true') }}" class="plot">
</div>
{% endmacro %}
/**
* Widget for usage of UZSU plugin
*
* @param {id=} unique id for this widget (optional)
* @param {item(dict)} the item of the UZSU (NOT the item which is switched by UZSU)
* @param {text=UZSU} headline of popup (optional, default: UZSU)
* @param {image=time_timer_switch} the pic for the 'on' state (optional)
* @param {image=time_timer_switch} the pic for the 'off' state (optional)
* @param {text(bool,num,list,text)=bool} supported types are 'bool', 'num', 'list', 'text' (optional, default: bool)
* @param {text[?]=} valueParameterList depending on valueType (optional):
bool: ['text on','text off'] or ['text1:value1','text2:value2'] for flipswitch (default: 'On', 'Off'),
num: [min, max, step] as limits and step for numeric input (default: ''),
list: ['value1','value2','value3'] or ['text1:value1','text2:value2','text3:value3'] as parameter set for a select element (default: '')
text: no value parameter
* @param {color(icon0to5)=icon1} the color for the 'on' state, e.g. '#f00' for red (default 'icon1' of the design)
* @param {color(icon0to5)=icon0} the color for the 'off' state, e.g. '#f00' for red (default 'icon0' of the design)
* @param {type=icon} button sized 'micro', 'mini' or 'midi' or icon (optional, default = 'icon')
* @param {value=} identifier for the backend: 0=smarthomeNG style, 2= fhem style (not necessary, default is derived from io driver, parameter is hidden in docu)
*
* @author Michael Wuertenberger
*/
{% macro uzsuicon(id, item, headline, pic_on, pic_off, valueType, valueParameterList, color_on, color_off, type, _backend) %}
{% import "@widgets/basic.html" as basic %}
{% if type not in ['micro','mini','midi'] %}
{% set type = 'icon' %}
{% else %}
{% set uitype = 'ui-'~type %}
{% endif %}
<span{% if not id is empty %} id="{{ uid(page, id) }}"{% endif %} data-widget="device.uzsuicon" data-item="{{ item }}"
data-headline="{{ headline|default('UZSU') }}" data-designType="{{ _backend }}"
data-valueType="{{ valueType|default('bool') }}" data-valueParameterList="{{ implode(valueParameterList) }}"
{% if type == 'icon' %} class="icon" {% else %} class="ui-btn {{ uitype }} ui-corner-all ui-btn-inline ui-shadow" {% endif %} >
<a>
<span class="icon-off">{{ basic.icon(pic_off|deficon('time_timer_switch.svg'), color_off) }}</span>
<span class="icon-on hide">{{ basic.icon(pic_on|deficon('time_timer_switch.svg'), color_on|default('icon1')) }}</span>
</a>
</span>
{% endmacro %}
/**
* UZSU-Timetable widget for usage with the UZSU-Plugin
*
* @param {id=} unique id for this widget (optional)
* @param {item(dict)} the item of the UZSU (NOT the item which is switched by UZSU)
* @param {text=} headline of Table (optional, default: empty)
* @param {value[?]=1} value for 'on' state (optional), if using array form keep in mind to get color array same length, if not last color would be used for missing entries - for correct tool-tip valueparameter list should include all values
* @param {value[?]=0} value for 'off' state (optional), if using array form keep in mind to get color array same length, if not last color would be used for missing entries - for correct tool-tip valueparameter list should include all values
* @param {color[?]=green} the color for the 'on' state, e.g. '#f00' for red (default 'green' of the design)
* @param {color[?]=red} the color for the 'off' state, e.g. '#f00' for red (default 'red' of the design)
* @param {color=lightgrey} the color for the 'off' state for disabled entries, e.g. '#f00' for red (default 'lightgrey' of the design)
* @param {color=grey} the color for the 'off' state for disabled entries, e.g. '#f00' for red (default 'grey' of the design)
* @param {text(false,true)=true} fill the times from ON to OFF with ON-color (default 'true' )
* @param {duration=1h} granularity of the timetable, valid values '5m', '10m','15m','30m','1h' (default '1h' )
* @param {text[?]=solid} style of the cell border, valid values solid,horizontal,hourly, none (default 'solid' )
also valid: combination with granularity as a list: ['5m:solid','10m:hourly','15m:hourly','30m:horizontal','1h:horizontal']
* @param {text(false,true)=true} show the zoom button for different granularity (default 'true' )
* @param {text(false,true)=true} show the switching times in a tooltip (default 'true' )
* @param {text(false,true)=true} show the sunrise/sunset-times as line on the table (default 'true' )
* @param {text(false,true)=true} show the actual time as line on the table (default 'true' )
* @param {value(1,2,3)=1} style of inactive-mode 1=opacity 50% / 2=Text "Inactive". You can combine 1+2=3 (default '1' )
* @param {text(bool,num,list,text)=bool} supported types are 'bool', 'num', 'list', 'text' (optional, default: bool)
* @param {text[]=} parameter list for value editing - depending on valueType (optional):
bool: ['text on','text off'] or ['text1:value1','text2:value2'] for flipswitch (default: 'On', 'Off'),
num: [min, max, step] as limits and step for numeric input (default: ''),
list: ['value1','value2','value3'] or ['text1:value1','text2:value2','text3:value3'] as parameter set for a select element (default: '')
text: no value parameter
* @param {value=} identifier for the backend: 0=smarthomeNG style, 2= fhem style (not necessary, default is derived from io driver)
*
* @author Andre Kohler
*/
{% macro uzsutable(id, item, headline, value_on, value_off, color_on, color_off, color_on_disabled, color_off_disabled, fill, granularity, borderstyle, showzoombutton, showtooltip, showsun, showactualtime, inactivestyle, valueType, valueparameterlist, _backend) %}
<div {% if not id is empty %} id="{{ uid(page, id) }}"{% endif %} data-widget="device.uzsutable" data-item="{{ item }}"
data-headline="{{ headline|default('') }}" data-designType="{{ _backend|default('') }}"
data-val-on="{{ implode(value_on)|default(implode(1)) }}" data-val-off="{{ implode(value_off)|default(implode(0)) }}"" data-granularity="{{ granularity|default('1h') }}"
data-color-on="{{ implode(color_on)|default(implode('green')) }}" data-color-off="{{ implode(color_off)|default(implode('red')) }}"
data-color-on-disabled="{{ color_on_disabled|default('lightgrey') }}" data-color-off-disabled="{{ color_off_disabled|default('grey') }}"
data-fill="{{ fill|default('true') }}" data-borderstyle="{{ implode(borderstyle)|default('solid') }}"
data-showtooltip="{{ showtooltip|default('true') }}" data-showsun="{{ showsun|default('true') }}" data-showactualtime="{{ showactualtime|default('true') }}"
data-valueType="{{ valueType|default('bool') }}" data-valueparameterlist="{{ implode(valueparameterlist)|default(implode(['on:1','off:0'])) }}"
data-showzoombutton="{{ showzoombutton|default('true') }}" data-inactivestyle="{{ inactivestyle|default('1') }}">
<svg id="{{ uid(page, item) }}" class="highcharts-root" width="100%" viewbox="0 0 200 160" xmlns="http://www.w3.org/2000/svg" version="1.1">
</svg>
{% if showzoombutton|default('true') == 'true' or showzoombutton == 'True' %}
<table width=50% style="font-size:12px;margin-left: auto;margin-right: auto;">
<tr>
<td width=20%> <button type="button" id="5m" >5m</button></td>
<td width=20%> <button type="button" id="10m">10m</button></td>
<td width=20%> <button type="button" id="15m">15m</button></td>
<td width=20%> <button type="button" id="30m">30m</button></td>
<td width=20%> <button type="button" id="1h"> 1h</button></td>
</tr>
</table>
{% endif %}
</div>
{% endmacro %}
/**
* Displays window status and a simple shutter control assembly in a popup
*
* @param {id=} unique id for this widget
* @param {text=} title of the window (optional)
* @param {item(bool,num)} item for the up- and down- movement of the shutter
* @param {item(bool,num)} item for stopping the movement of the shutter (optional)
* @param {item(num)=} item for the position of the shutter (optional)
* @param {item(num)=} item to move the shutters to the shade position (optional)
* @param {item(num)=} item to display the window's right wing status (optional)
* @param {item(num)=} item to display the window's left wing status (optional)
* @param {type=micro} type: 'micro', 'mini', 'midi', 'icon' (optional, default: mini)
* @param {value=0} the value for opened (optional, default 0)
* @param {value=255} the value for closed (optional, default 255)
* @param {value=5} step between two values (optional, default 5)
* @param {color[?](icon0to5,!)=icon1} colors for the 'closed' and 'open' state, e.g. ['icon2','icon3'] or '#f00' or ['green,'red']. Prepend a '!' to make the color constant ('!#f00'). (optional, default ['icon0','icon1'])
* @param {value=1} 'live mode': if enabled, values will be sent during sliding. '0' sends values only when sliding is stopped or after click into the track. (optional, default = 1)
*
* @author Wolfram v. Hülsen
*/
{% macro window (id, txt, item_move, item_stop, item_pos, item_shade, item_window_r, item_window_l, type, min, max, step, color, live ) %}
{% import "@widgets/basic.html" as basic %}
{% set uid = uid(page, id) %}
{% set popupid = id is empty ? uid(page, 'popup') : uid %}
<a id="{{ uid }}-button" href="#{{ popupid }}" {% if txt %}title="{{ txt }}"{% endif %} data-rel="popup"
class=" {%- if type == 'icon' -%} /**- display as icon -*/
switch ui-link
{%- else -%} /**- display as button -*/
ui-btn ui-{{ type|default('mini') }} ui-corner-all ui-btn-inline
{%- endif -%}"
>
{{ basic.window (uid~'-icon', item_pos, item_window_r, item_window_l, min, max, color) }}
</a>
<div id="{{ popupid }}" data-role="popup" style="min-width:200px;">
<div>
{{ basic.window(popupid~'-icon1', item_pos, item_window_r, item_window_l, min, max, '') }}
{{ txt|e }}
</div>
<div data-role="controlgroup" data-type="horizontal">
{{ basic.stateswitch(popupid~'-up', item_move, 'mini', 0, 'control_arrow_up.svg') }}
{{ basic.stateswitch(popupid~'-down', item_move, 'mini', 1, 'control_arrow_down.svg') }}
{% if item_stop %}{{ basic.stateswitch(popupid~'-stop', item_stop, 'mini', 1, 'audio_stop.svg') }}{% endif %}
{% if item_shade %}{{ basic.stateswitch(popupid~'-shade', item_shade, 'mini', 1, 'fts_shutter_50.svg') }}{% endif %}
</div>
{% if item_pos %}
<div class="pos">
{{ basic.slider(popupid~'-pos', item_pos, min , max , step, 'horizontal', 'handle', '', '', live ) }}
</div>
{% endif %}
<a href="#" data-rel="back" data-role="button" data-icon="delete" data-iconpos="notext" class="ui-btn-right">Schließen</a>
</div>
{% endmacro %}
// ----- device.codepad -------------------------------------------------------
$.widget("sv.device_codepad", $.sv.widget, {
initSelector: 'div[data-widget="device.codepad"]',
options: {
duration: '5s',
val: '0000'
},
_create: function() {
this._super();
var codepad = this.element;
var id = this.options.id;
var clickpreventer = $('<div style="position: absolute; width: 3px; height: 3px;">')
.on('click', function(event) {
if (!$(this).closest('[data-bind="' + id + '"]').data('access')) {
codepad.popup('open');
codepad.find('input').val('').focus();
codepad.data('originally-clicked', $(this).parent());
event.stopPropagation();
event.stopImmediatePropagation();
event.preventDefault();
}
});
$('[data-bind="' + id + '"]')
.on('mouseenter', function(event) {
if (!$(this).closest('[data-bind="' + id + '"]').data('access'))
clickpreventer.appendTo(this)
.css({
left: parseInt(event.pageX - clickpreventer.offsetParent().offset().left)-1,
top: parseInt(event.pageY - clickpreventer.offsetParent().offset().top)-1,
zIndex: Number($(this).css('zIndex'))+1
});
})
.on('mousemove', '*', function(event) {
if (!$(this).closest('[data-bind="' + id + '"]').data('access'))
clickpreventer.appendTo(this)
.css({
left: parseInt(event.pageX - clickpreventer.offsetParent().offset().left)-1,
top: parseInt(event.pageY - clickpreventer.offsetParent().offset().top)-1,
zIndex: Number($(this).css('zIndex'))+1
});
});
},
_events: {
'keyup': function (event) {
if (event.keyCode == 13) {
this.element.find('[data-val="ok"]').click();
}
},
'click > div > a': function (event) {
var node = this.element; //$(event.target).parent().parent();
var code = node.find('input');
var key = $(event.target).data('val');
code.focus();
if (key == "ok") {
if (this.options.val == code.val().md5()) {
$('[data-bind="' + node.attr('data-id') + '"]').data('access', new Date().getTime()).removeClass('codepad');
this._delay(function () {
$('[data-bind="' + this.options.id + '"]').data('access', '').addClass('codepad');
}, new Date().duration(this.options.duration).valueOf());
node.popup("close");
node.data("originally-clicked").trigger("click");
}
else {
console.log('[device.codepad] ' + node.attr('id') + ' wrong code ' + code.val());
code.val('');
node.addClass('ui-focus');
setTimeout(function () {
node.removeClass('ui-focus');
}, 400);
}
}
else if (key == "-") {
code.val('');
}
else {
code.val(code.val() + key);
}
}
}
});
// ----- device.roofwindow ------------------------------------------------------
$.widget("sv.device_roofwindow", $.sv.widget, {
initSelector: 'div[data-widget="device.roofwindow"]',
options: {
min: 0,
max: 255,
step: 5,
},
_getVal: function(event) {
var min = this.options.min;
var max = this.options.max;
var step = this.options.step;
var offset = $(event.currentTarget).offset();
var y = event.pageY - offset.top;
return max - Math.floor(y / $(event.currentTarget).outerHeight() * (max - min) / step) * step;
},
_events: {
'click .pos': function (event) {
this._write(this._getVal(event));
},
'mouseenter .pos': function (event) {
this.element.find('.control').fadeIn(400);
},
'mouseleave .pos': function (event) {
this.element.find('.control').fadeOut(400);
},
'mousemove .pos': function (event) {
$(event.currentTarget).attr('title', this._getVal(event));
}
},
_enable: function(){
this.element.removeClass('ui-state-disabled');
},
_disable: function(){
this.element.addClass('ui-state-disabled');
}
});
// ----- device.rtrslider-------------------------------------------------------
$.widget("sv.device_rtrslider", $.sv.widget, {
initSelector: 'div[data-widget="device.rtrslider"]',
options: {
step: 0.1,
scale_interval: 1,
scale_min: 18,
scale_max: 28,
},
_create: function() {
this._super();
var element = this.element;
var that = this;
var outerSlider = $(element).find('.outerslider');
var innerSlider = $(element).find('.innerslider');
var item_names = this.options.item.explode();
var scale_min = this.options.scale_min; //18;
var scale_max = this.options.scale_max; //28;
var step = this.options.step * 1; //0.1;
var decs = step.decimals();
var unit = "°C";
var scale_interval = this.options.scale_interval;
// some RTR use a different item for temperature offset, eg. MDT
if (item_names[2] != "") {
var set_old = response[1];
var offset_old = response[2];
}
// slider for actual value
$(outerSlider).roundSlider({
value: scale_min,
min: scale_min,
max: scale_max,
step: step,
sliderType: "min-range",
radius: 70,
showTooltip: true,
editableTooltip: false,
circleShape: "full",
startAngle: "315",
endAngle: "225",
handleShape: "round",
handleSize: "0",
lineCap: "none",
width: "8",
svgMode: true,
readOnly: true,
update: function(args) {},
create: function(args) {
var o = this.options;
for (var i = o.min; i <= o.max; i += scale_interval) {
var angle = this._valueToAngle(i);
var numberTag = this._addSeperator(angle, "rs-custom");
var number = numberTag.children();
number.clone().css({
"width": o.width + this._border(),
"margin-top": this._border(true) / -2,
"margin-right": '10px',
}).appendTo(numberTag);
number.removeClass().addClass("rs-number").html(i).rsRotate(-angle);
$(outerSlider).find(".rs-number").css("left", "-25px");
$(outerSlider).find(".rs-seperator").css("width","6px");
$(outerSlider).find(".rs-seperator").css("margin-left","-6px");
};
//scala gerade striche (kurz)
var interval = scale_interval/2;
for (var i = o.min; i <= o.max; i += interval) {
var angle = this._valueToAngle(i);
var numberTag = this._addSeperator(angle, "rs-custom_1");
numberTag.addClass( "rs-seperator_1" );
$(outerSlider).find(".rs-seperator_1 .rs-seperator").css("width","4px");
$(outerSlider).find(".rs-seperator_1 .rs-seperator").css("margin-left", "0px");
$(outerSlider).find(".rs-seperator_1 .rs-seperator").css("height","1px");
};
},
tooltipFormat:function (args){
return"<span style='position: relative;top:-2.2em;font-size:0.2em;'>Ist: </span></br><span id ='val' style='position: relative;top:-2.7em;font-weight:bold;font-size:0.45em;'>" + args.value.toFixed(decs) + unit +"</span>";
},
});
// slider for set value
$(innerSlider).roundSlider({
value: scale_min,
min: scale_min,
max: scale_max,
step: step,
width: 12,
sliderType: "min-range",
radius: 62,
showTooltip: false,
circleShape: "full",
startAngle: "315",
endAngle: "225",
handleShape: "round",
handleSize: "25",
lineCap: "none",
editableTooltip: false,
svgMode: true,
update: function (args) {
if (item_names[2] != "") {
var delta = args.value - set_old;
io.write(item_names[2], (offset_old + delta).toFixed(decs));
}
else
io.write(item_names[1], args.value);
},
create: function() {
$(innerSlider).find(".inner-handle").css({
'position': 'absolute',
'left': '-35px'}
);
}
});
},
_update: function(response){
var outerSlider = this.element.find('.outerslider');
var innerSlider = this.element.find('.innerslider');
var step = this.options.step * 1; //0.1;
var decs = step.decimals();
var unit = "°C";
var actualValue = response[0];
var setValue = response[1];
var actualString = (actualValue < 10 ? '0' : '') + actualValue.toFixed(decs)+unit;
$(outerSlider).roundSlider('setValue', actualValue);
$(outerSlider).find(".rs-tooltip #val").html(actualString);
$(innerSlider).roundSlider('setValue', setValue);
},
_enable: function(){
this.element.removeClass('ui-state-disabled').find('.innerslider').roundSlider('enable').end()
.next('.set').find('[data-widget]').filter(':data("sv-widget")').widget('enable').end().end()
.next('.control').find('[data-widget]').filter(':data("sv-widget")').widget('enable');
},
_disable: function(){
this.element.addClass('ui-state-disabled').find('.innerslider').roundSlider('disable').end()
.next('.set').find('[data-widget]').filter(':data("sv-widget")').widget('disable').end().end()
.next('.control').find('[data-widget]').filter(':data("sv-widget")').widget('disable');
}
});
// ----- device.uzsu ----------------------------------------------------------
// ----------------------------------------------------------------------------
//
// Neugestaltetes UZSU Widget zur Bedienung UZSU Plugin
//
// Darstellung der UZSU Einträge und Darstellung Widget in Form eine Liste mit den Einträgen
// Umsetzung
// (c) Michael Würtenberger 2014,2015,2016
// (c) Andre Kohler, Wolfram v. Hülsen: neue Funktion für Zeitserien 2021
//
// APL 2.0 Lizenz
//
// Basis der Idee des dynamischen Popups übernommen von John Chacko
// jQuery Mobile runtime popup
// 16. November 2012 · 0 Comments
// http://johnchacko.net/?p=44
//
// ----------------------------------------------------------------------------
// Basis der Architektur: document.update und document.click baut die handler in die Seite für das Popup ein.
// document.update kopiert bei einem update die Daten aus dem Backend (per Websocket) in das DOM Element ("uzsu") ein
// document.click übernimmt die Daten aus dem DOM Element in Variable des JS Bereichs und baut über runtimepopup
// dynamisch header, body und footer des popup zusammen und hängt sie an die aktuelle seite an (append, pagecreate)
// danach werden die Daten aus den Variablen in die Elemente der Seite kopiert. Die Elemente der Seite bilden immer
// den aktuellen Stand ab und werden von dort in die Variablen zurückgespeichert, wenn notwendig (save, sort).
// In der Struktur können Zeilen angehängt (add) oder gelöscht werden (del). Dies geschieht immer parallel in den Variablen
// und den Elementen der Seite. Die Expertenzeilen werden immer sofort mit angelegt, sind aber zu Beginn nicht sichtbar.
// Beim verlassen des Popups werden die dynamisch angelegten DOM Elemente wieder gelöscht (remove).
//
// Datenmodell: Austausch über JSON Element
// { "active" : bool,
// "once" : bool, gesamte Liste wird nur einmal ausgeführt
// "list" : Liste von einträgen mit schaltzeiten
// [{"active" :false, Ist der einzelne Eintrag darin aktiv ?
// "once" :false, Eintrag wird nur einmal ausgeführt
// "rrule" :'', Wochen / Tag Programmstring
// "value" :0, Wert, der gesetzt wird
// "time" :'00:00' Uhrzeitstring des Schaltpunktes, '19:00<sunset+15m<22:00' bei SUN-Ebents, 'series' bei Zeitreihen
// Dies wird intern wie folgt zerlegt (nicht Bestandteil des Dict)
// "timeMin" :'', Untere Schranke SUN
// "timeMax" :'', Oberere Schranke SUN
// "timeCron" :'00:00', Schaltzeitpunkt
// "timeOffset":'' Offset für Schaltzeitpunkt in Minuten oder Grad
// "timeOffsetType":'m' 'm' = Offset in Minuten, '' Offset in Höhengrad (Altitude)
// "series" : Liste für die Beschreibung der Zeitserien-Parameter
// "active": true,
// "timeSeriesMin": "16:00<sunset+20m<20:00" Startzeit der Serie
// "timeSeriesMax": "04:00" Endzeit der Serie
// "timeSeriesCount": 4 Anzahl Zyklen (alternativ zu timeSeriesMax)
// "timeSeriesIntervall": "01:00"} Intervall für die Serie
// "condition" : { Ein Struct für die Verwendung mit conditions (aktuell nur FHEM), weil dort einige Option mehr angeboten werden
// "deviceString" : text Bezeichnung des Devices oder Auswertestring
// "type" : text Auswertetype (logische Verknüpfung oder Auswahl String)
// "value" : text Vergleichwert
// "active" : bool Aktiviert ja/nein
// }
// "delayedExec": { Ein Struct für die Verwendung mit delayed exec (aktuell nur FHEM), weil dort einige Option mehr angeboten werden
// "deviceString" : text Bezeichnung des Devices oder Auswertestring
// "type" : text Auswertetype (logische Verknüpfung oder Auswahl String)
// "value" : text Vergleichwert
// "active" : bool Aktiviert ja/nein
// }
// "holiday": {
// "workday" : bool Aktiviert ja/nein
// "weekend" : bool Aktiviert ja/nein
// }
// ]
// }
// Base widget for device_uzsuicon, device_uzsugraph and device_uzsutable
$.widget("sv.device_uzsu", $.sv.widget, {
_create: function() {
this._super();
this.options.designtype = String(this.options.designtype);
if(this.options.designtype === undefined || this.options.designtype === '') {
this.options.designtype = io.uzsu_type;
}
},
_update: function(response) {
// wenn keine Daten vorhanden, bzw. nicht mindestens die Eigenschaft "active" vorhanden ist, dann ist kein UZSU-item angelegt / initialisiert und es wird nichts gemacht
if (response.length === 0 || !response[0].hasOwnProperty("active")){
var supplement = (this.options.item.substr(-5) != ".uzsu") ? "Seems this is not an uzsu item. Try '" + this.options.item+".uzsu'." : "";
notify.message("error", "UZSU widget", "No UZSU data available in item '" + this.options.item + "'" + (this.options.id ? " for widget '" + this.options.id + "'. " : ". ") + supplement) ;
return;
}
// enable widget and buttons which may have been disabled after user has edited the dict
$('[data-widget="device.uzsuicon"][data-item="'+ this.options.item + '"]').removeClass('blink');
$('[data-widget*="device.uzsu"][data-item="'+ this.options.item + '"]').widget('enable').find('.uzsu-active-toggler').removeClass('hidden').show();
this._uzsudata = jQuery.extend(true, {}, response[0]);
// Initialisierung: hier ist "active" vorhanden, also ein UZSU-item angelegt. Wenn "list" fehlt, wird dies angelegt und active = false gesetzt, damit im Popup Daten eingetragen werden können
// anderenfalls ist der Status von active gleich dem gesetzten Status
if (!(this._uzsudata.list instanceof Array)) {
this._uzsudata = { active: false, list: [] };
}
},
_uzsuBuildTableHeader: function() {
// Kopf und überschrift des Popups
var tt = "";
// hier kommt der Popup Container mit der Beschreibung ein Eigenschaften
tt += "<div data-role='popup' data-overlay-theme='b' data-theme='a' class='messagePopup' id='uzsuPopupContent' data-dismissible = 'false' data-history='false' data-position-to='window'>" +
"<button data-rel='back' data-icon='delete' data-iconpos='notext' class='ui-btn-right' id='uzsuClose'></button>" +
"<div class='uzsuClear'>" +
"<div class='uzsuPopupHeader'>" + this.options.headline + "</div>" +
"<div class='uzsuTableMain' id='uzsuTable'>";
return tt;
},
_uzsuBuildTableRow: function(numberOfRow) {
// default Werte setzen fuer valueParameterList
var valueType = this.options.valuetype;
var valueParameterList = this.options.valueparameterlist.explode();
if(valueParameterList.length === 0){
if(valueType === 'bool') valueParameterList = ['On','Off'];
else if (valueType === 'num') valueParameterList = [''];
else if (valueType === 'text') valueParameterList = [''];
else if (valueType === 'list') valueParameterList = [''];
}
// Tabelleneinträge
var tt = "";
tt += "<div class='uzsuRow'>" +
"<div class='uzsuCell'>" +
"<div class='uzsuCellText'>" + sv_lang.uzsu.weekday + "</div>" +
"<fieldset data-role='controlgroup' data-type='horizontal' data-mini='true' class='uzsuWeekday'>";
var daydate = new Date(0);
$.each([ 'MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU' ], function(index, value) {
daydate.setDate(5 + index); // Set date to one on according weekday (05.01.1970 was a monday)
tt += "<label title='" + daydate.transUnit('l') + "'><input type='checkbox' value='" + value + "'>" + daydate.transUnit('D') + "</label>";
});
tt += "</fieldset>" +
"</div>";
tt += "<div class='uzsuCell uzsuValueCell'>" +
"<div class='uzsuCellText'>" + sv_lang.uzsu.value + "</div>";
if (this.options.valuetype === 'bool') {
// Unterscheidung Anzeige und Werte
if (valueParameterList[0].split(':')[1] === undefined) {
tt += "<select data-role='flipswitch'>" +
"<option value='0'>" + valueParameterList[1] + "</option>" +
"<option value='1'>" + valueParameterList[0] + "</option>" +
"</select>";
}
else {
tt += "<select data-role='flipswitch'>" +
"<option value='" + valueParameterList[1].split(':')[1] + "'>" + valueParameterList[1].split(':')[0] + "</option>" +
"<option value='" + valueParameterList[0].split(':')[1] + "'>" + valueParameterList[0].split(':')[0] + "</option>" +
"</select>";
}
}
else if (this.options.valuetype === 'num') {
var addedclass = (parseFloat(valueParameterList[0]) < 0) ? "" : " positivenumbers";
tt += "<input type='number' min='" + parseFloat(valueParameterList[0]) + "' max='" + parseFloat(valueParameterList[1]) + "' step='" + parseFloat(valueParameterList[2]) + "' data-clear-btn='false' class='uzsuValueInput" + addedclass + "' pattern='[0-9]*'>";
}
else if (this.options.valuetype === 'text') {
tt += "<input type='text' data-clear-btn='false' class='uzsuTextInput'>";
}
else if (this.options.valuetype === 'list') {
// das Listenformat mit select ist sehr umfangreich nur einzubauen.
tt += "<select data-mini='true'>";
for (var numberOfListEntry = 0; numberOfListEntry < valueParameterList.length; numberOfListEntry++) {
// Unterscheidung Anzeige und Werte
if (valueParameterList[0].split(':')[1] === undefined) {
tt += "<option value='" + valueParameterList[numberOfListEntry].split(':')[0] + "'>"+ valueParameterList[numberOfListEntry].split(':')[0] + "</option>";
}
else {
tt += "<option value='" + valueParameterList[numberOfListEntry].split(':')[1] + "'>"+ valueParameterList[numberOfListEntry].split(':')[0] + "</option>";
}
}
tt += "</select>";
}
tt+= "</div>"
tt+= "<div class='uzsuCell'>" +
"<div class='uzsuCellText'>" + sv_lang.uzsu.time + "</div>" +
"<input type='time' data-clear-btn='false' class='uzsuTimeInput uzsuTimeCron'>" +
"</div>" +
"<div class='uzsuCell'>" +
"<div class='uzsuCellText'></div>" +
"<fieldset data-role='controlgroup' data-type='horizontal' data-mini='true'>" +
"<label><input type='checkbox' class='uzsuActive'>" + sv_lang.uzsu.act + "</label>";
if (this.hasOnce)
tt+= "<label><input type='checkbox' class='uzsuOnce'>1 x</label>";
tt+= "</fieldset>" +
"</div>" +
"<div class='uzsuCellExpert'>" +
"<div class='uzsuCellText'>" + sv_lang.uzsu.expert + "</div>" +
"<button data-mini='true' data-icon='arrow-d' data-iconpos='notext' class='ui-icon-shadow'></button>" +
"</div>"
if (this.options.designtype == '0' && this.hasSeries){
tt+= "<div class='uzsuCellSeries'>" +
"<div class='uzsuCellText'>" + sv_lang.uzsu.series + "</div>" +
"<button data-mini='true' data-icon='arrow-r' data-iconpos='notext' class='ui-icon-shadow'></button>" +
"</div>"
}
tt+= "<div class='uzsuCell'>" +
"<div class='uzsuCellText'></div>" +
"<fieldset data-role='controlgroup' data-type='horizontal' data-mini='true'>" +
"<button class='uzsuDelTableRow' data-mini='true'>" + sv_lang.uzsu.del + "</button>" +
"</fieldset>" +
"</div>";
// Tabelle Zeile abschliessen
tt += "</div>";
// unsichtbare Expertenzeile
tt += this._uzsuSetExpertLines(numberOfRow, '');
// unsichtbare Time-Serieszeile mit Expertenzeilen für Start/Ende
if (this.options.designtype == '0' && this.hasSeries){
tt += "<div class='uzsuRowSeries' style='display:none;'>" +
"<div class='uzsuCellText'></div>" +
"<div class='uzsuRowSeriesLine' style='float: left;'>" +
"<div class='uzsuRowExpertText'>" + 'Series' + "</div>" +
"<div class='uzsuCell'>" +
"<div class='uzsuCellText'>" + sv_lang.uzsu.seriesstart + "</div>" +
"<input type='time' data-clear-btn='false' class='uzsuTimeMaxMinInput uzsuTimeSerieMin uzsuTimeCronseriesstart'>" +
"</div>" +
"<div class='uzsuCell'>" +
"<div class='uzsuCellText'>" + sv_lang.uzsu.seriesintervall + "&nbsp;&nbsp;HH : MM</div>" +
"<input type='time' data-clear-btn='false' class='uzsuTimeMaxMinInput uzsuSerieTimeInterval'>" +
"</div>" +
"<div class='uzsuCell'>" +
"<div class='uzsuCellText'></div>" +
"<fieldset data-role='controlgroup' data-type='horizontal' data-mini='true' class='uzsuSeriesEndTypeInput'>" +
"<label title='"+sv_lang.uzsu.seriesend+"'><input type='radio' name='uzsuSeriesEndTypeInput"+numberOfRow+"' value='t' checked='checked'>t</label>" +
"<label title='"+sv_lang.uzsu.seriescount+"'><input type='radio' name='uzsuSeriesEndTypeInput"+numberOfRow+"' value=''>#</label>" +
"</fieldset>" +
"</div>" +
"<div class='uzsuCell'>" +
"<div class='uzsuCellText uzsuTimeSerieMaxText'>" + sv_lang.uzsu.seriesend + "</div>" +
"<input type='time' min='0' data-clear-btn='false' class='uzsuTimeMaxMinInput uzsuTimeSerieMax uzsuValueInput uzsuTimeCronseriesend'>" +
"</div>" +
"<div class='uzsuCell'>" +
"<div class='uzsuCellText'></div>" +
"<fieldset data-role='controlgroup' data-type='horizontal' data-mini='true'>" +
"<label><input type='checkbox' class='uzsuSeriesActive'>" + sv_lang.uzsu.act + "</label>" +
"</fieldset>" +
"</div>" +
"</div>" +
"<div class='uzsuCellExpertSeries'>" +
"<div class='uzsuCellText'>" + sv_lang.uzsu.expert + "</div>" +
"<button data-mini='true' data-icon='arrow-d' data-iconpos='notext' class='ui-icon-shadow'></button>" +
"</div> </div>"
// unsichtbare Expertenzeilen für Serienstart und -end
tt += this._uzsuSetExpertLines(numberOfRow, 'seriesstart');
tt += this._uzsuSetExpertLines(numberOfRow, 'seriesend');
//tt += "</div>";
}
return tt;
},
_uzsuSetExpertLines: function(numberOfRow, caller){
tt = "<div class='uzsuRowExpHoli uzsuExpertLine "+(caller != '' ? 'Series' : '')+"' style='display:none;'>" +
"<div class='uzsuCellText'>" + (caller != '' ? sv_lang.uzsu.series + ' ' + sv_lang.uzsu[caller] : '') + "</div>" +
"<div class='uzsuRowExpert' style='float: left;'>" +
"<div class='uzsuRowExpertText'>" + sv_lang.uzsu.sun + "</div>" +
"<div class='uzsuCell'>" +
"<div class='uzsuCellText'>" + sv_lang.uzsu.earliest + "</div>" +
"<input type='time' data-clear-btn='false' class='uzsuTimeMaxMinInput uzsuTimeMin"+caller+"'>" +
"</div>" +
"<div class='uzsuCell uzsuEvent"+caller+"'>" +
"<div class='uzsuCellText'>Event</div>" +
"<select data-mini='true' data-native-menu='false'>" +
"<option value='sunrise'>" + sv_lang.uzsu.sunrise + "</option>" +
"<option value='sunset'>" + sv_lang.uzsu.sunset + "</option>" +
"</select>" +
"</div>" +
"<div class='uzsuCell'>" +
"<div class='uzsuCellText'>+/-" + (this.options.designtype == '0' ? '' : ' min.') +"</div>" +
"<input type='number' data-clear-btn='false' class='uzsuTimeMaxMinInput uzsuTimeOffsetInput"+caller+"'>" +
"</div>";
// Auswahl für Offset in Grad oder Minuten (nur für SmartHomeNG)
if (this.options.designtype == '0'){
tt += "<div class='uzsuCell'>" +
"<div class='uzsuCellText'></div>" +
"<fieldset data-role='controlgroup' data-type='horizontal' data-mini='true' class='uzsuTimeOffsetTypeInput"+caller+"'>" +
"<label title='"+sv_lang.uzsu.minutes+"'><input type='radio' name='uzsuTimeOffsetTypeInput"+caller+numberOfRow+"' value='m' checked='checked'>m</label>" +
"<label title='"+sv_lang.uzsu.degrees+"'><input type='radio' name='uzsuTimeOffsetTypeInput"+caller+numberOfRow+"' value=''>°</label>" +
"</fieldset>" +
"</div>";
}
tt += "<div class='uzsuCell'>" +
"<div class='uzsuCellText'>" + sv_lang.uzsu.latest + "</div>" +
"<input type='time' data-clear-btn='false' class='uzsuTimeMaxMinInput uzsuTimeMax"+caller+"'>" +
"</div>" +
"<div class='uzsuCell'>" +
"<div class='uzsuCellText'></div>" +
"<fieldset data-role='controlgroup' data-type='horizontal' data-mini='true'>" +
"<label><input type='checkbox' class='expertActive"+caller+" uzsuSunActive"+caller+"'>" + sv_lang.uzsu.act + "</label>" +
"</fieldset>" +
"</div>";
// UZSU Interpolation
if(sv_lang.uzsu.interpolation && this.hasInterpolation){
tt += "<div class='uzsuCell'>" +
"<div class='uzsuCellText'>" + sv_lang.uzsu.calculated + "</div>" +
"<div data-tip='" + sv_lang.uzsu.calculatedtip + "'>" +
"<input type='time' data-clear-btn='false' class='uzsuTimeMaxMinInput uzsuCalculated"+caller+"' disabled>" +
"</div>" +
"</div>";
}
tt += "</div>"; // end .uzsuRowExpert
// hier die Einträge für holiday weekend oder nicht
if (this.options.designtype == '2'){
tt += "<div class='uzsuRowHoliday' style='float: left;'>" +
"<div class='uzsuRowHolidayText'>Holiday</div>" +
"<div class='uzsuCell'>" +
"<div class='uzsuCellText'>Holiday</div>" +
"<fieldset data-role='controlgroup' data-type='horizontal' data-mini='true'>" +
"<label><input type='checkbox' class='expertActive uzsuHolidayWorkday'>!WE</label>" +
"<label><input type='checkbox' class='expertActive uzsuHolidayWeekend'>WE</label>" +
"</fieldset>" +
"</div>" +
"</div>";
}
tt+= "</div>"; //end .uzsuRowExpHoli
// unsichtbare Condition und delayed Exec Zeile
if(this.options.designtype == '2'){
tt += "<div class='uzsuRowCondition uzsuExpertLine' style='display:none;'>" +
"<div class='uzsuRowConditionText'>Condition</div>" +
"<div class='uzsuCell'>" +
"<div class='uzsuCellText'>Device / String</div>" +
"<input type='text' data-clear-btn='false' class='uzsuConditionDeviceStringInput'>" +
"</div>" +
"<div class='uzsuCell'>" +
"<div class='uzsuCellText'>Condition Type</div>" +
"<select data-mini='true' class='uzsuConditionType'>" +
"<option value='eq'>=</option>" +
"<option value='<'><</option>" +
"<option value='>'>></option>" +
"<option value='>='>>=</option>" +
"<option value='<='><=</option>" +
"<option value='ne'>!=</option>" +
"<option value='String'>String</option>" +
"</select>" +
"</div>" +
"<div class='uzsuCell'>" +
"<div class='uzsuCellText'>Value</div>" +
"<input type='text' data-clear-btn='false' class='uzsuConditionValueInput'>" +
"</div>" +
"<div class='uzsuCell'>" +
"<div class='uzsuCellText'></div>" +
"<fieldset data-role='controlgroup' data-type='horizontal' data-mini='true'>" +
"<label><input type='checkbox' class='expertActive uzsuConditionActive'>Act</label>" +
"</fieldset>" +
"</div>" +
"</div>"; // end .uzsuRowCondition
// delayed exec zeile
tt += "<div class='uzsuRowDelayedExec uzsuExpertLine' style='display:none;'>" +
"<div class='uzsuRowDelayedExecText'>DelayedExec</div>" +
"<div class='uzsuCell'>" +
"<div class='uzsuCellText'>Device / String</div>" +
"<input type='text' data-clear-btn='false' class='uzsuDelayedExecDeviceStringInput'>" +
"</div>" +
"<div class='uzsuCell'>" +
"<div class='uzsuCellText'>DelayedExec Type</div>" +
"<select data-mini='true' class='uzsuDelayedExecType'>" +
"<option value='eq'>=</option>" +
"<option value='<'><</option>" +
"<option value='>'>></option>" +
"<option value='>='>>=</option>" +
"<option value='<='><=</option>" +
"<option value='ne'>!=</option>" +
"<option value='String'>String</option>" +
"</select>" +
"</div>" +
"<div class='uzsuCell'>" +
"<div class='uzsuCellText'>Value</div>" +
"<input type='text' data-clear-btn='false' class='uzsuDelayedExecValueInput'>" +
"</div>" +
"<div class='uzsuCell'>" +
"<div class='uzsuCellText'></div>" +
"<fieldset data-role='controlgroup' data-type='horizontal' data-mini='true'>" +
"<label><input type='checkbox' class='expertActive uzsuDelayedExecActive'>Act</label>" +
"</fieldset>" +
"</div>" +
"</div>"; // end .uzsuRowDelayedExec
}
return tt;
},
_uzsuFillTableRow: function(responseEntry, tableRow) {
var self = this;
// dann die Werte einer Tabellenzeile füllen
uzsuCurrentRows = $(tableRow).nextUntil('.uzsuRow').addBack();
if(responseEntry.value != null) {
// beim Schreiben der Daten Unterscheidung, da sonst das Element falsch genutzt wird mit Flipswitch für die bool Variante
if (self.options.valuetype === 'bool') {
uzsuCurrentRows.find('.uzsuValueCell select').val(responseEntry.value).flipswitch("refresh");
}
// mit int Value für die num Variante
else if ((self.options.valuetype === 'num') || (self.options.valuetype === 'text')) {
uzsuCurrentRows.find('.uzsuValueCell input').val(responseEntry.value);
}
else if (self.options.valuetype === 'list') {
uzsuCurrentRows.find('.uzsuValueCell select').val(responseEntry.value).selectmenu('refresh', true);
}
}
// Values in der Zeile setzen
uzsuCurrentRows.find('.uzsuActive').prop('checked',responseEntry.active).checkboxradio("refresh");
if (this.hasOnce)
uzsuCurrentRows.find('.uzsuOnce').prop('checked',responseEntry.once).checkboxradio("refresh");
// hier die conditions, wenn sie im json angelegt worden sind und zwar pro zeile !
if(self.options.designtype == '2'){
// Condition
uzsuCurrentRows.find('.uzsuConditionDeviceStringInput').val(responseEntry.condition.deviceString);
uzsuCurrentRows.find('select.uzsuConditionType').val(responseEntry.condition.type).selectmenu('refresh', true);
uzsuCurrentRows.find('.uzsuConditionValueInput').val(responseEntry.condition.value);
uzsuCurrentRows.find('.uzsuConditionActive').prop('checked',responseEntry.condition.active).checkboxradio("refresh");
// Delayed Exec Zeile
uzsuCurrentRows.find('.uzsuDelayedExecDeviceStringInput').val(responseEntry.delayedExec.deviceString);
uzsuCurrentRows.find('select.uzsuDelayedExecType').val(responseEntry.delayedExec.type).selectmenu('refresh', true);
uzsuCurrentRows.find('.uzsuDelayedExecValueInput').val(responseEntry.delayedExec.value);
uzsuCurrentRows.find('.uzsuDelayedExecActive').prop('checked',responseEntry.delayedExec.active).checkboxradio("refresh");
}
// fill expert line
uzsuCurrentRows.find('.uzsuTimeMin').val(responseEntry.timeMin);
uzsuCurrentRows.find('.uzsuTimeOffsetInput').val(parseInt(responseEntry.timeOffset));
if(self.options.designtype == '0') {
uzsuCurrentRows.find('.uzsuTimeOffsetTypeInput').find(':radio').prop('checked', false).checkboxradio("refresh")
.end().find('[value="'+responseEntry.timeOffsetType+'"]:radio').prop('checked', true).checkboxradio("refresh");
}
uzsuCurrentRows.find('.uzsuTimeMax').val(responseEntry.timeMax);
uzsuCurrentRows.find('.uzsuTimeCron').val(responseEntry.timeCron);
if(responseEntry.calculated != null)
uzsuCurrentRows.find('.uzsuCalculated').val(responseEntry.calculated);
// fill time series
if(self.options.designtype == '0' && this.hasSeries) {
if (responseEntry.hasOwnProperty("series"))
{
uzsuCurrentRows.find('.uzsuSeriesActive').prop('checked',responseEntry.series.active).checkboxradio("refresh");
uzsuCurrentRows.find('.uzsuTimeSerieMin').val(responseEntry.series.timeSeriesMin);
uzsuCurrentRows.find('.uzsuSerieTimeInterval').val(responseEntry.series.timeSeriesIntervall);
var timeSeriesEndType = "t";
if (responseEntry.series.hasOwnProperty("timeSeriesCount"))
timeSeriesEndType ="";
uzsuCurrentRows.find('.uzsuSeriesEndTypeInput').find(':radio').prop('checked', false).checkboxradio("refresh")
.end().find('[value="'+timeSeriesEndType+'"]:radio').prop('checked', true).checkboxradio("refresh");
self._uzsuSetSeriesEndType(uzsuCurrentRows.find('.uzsuSeriesEndTypeInput'));
uzsuCurrentRows.find('.uzsuTimeSerieMax').val(timeSeriesEndType == "t" ? responseEntry.series.timeSeriesMax : responseEntry.series.timeSeriesCount);
// fill expert lines for series
uzsuCurrentRows.find('.uzsuTimeMinseriesstart').val(responseEntry.series.start.timeMin);
uzsuCurrentRows.find('.uzsuTimeOffsetInputseriesstart').val(parseInt(responseEntry.series.start.timeOffset));
uzsuCurrentRows.find('.uzsuTimeOffsetTypeInputseriesstart').find(':radio').prop('checked', false).checkboxradio("refresh")
.end().find('[value="'+responseEntry.series.start.timeOffsetType+'"]:radio').prop('checked', true).checkboxradio("refresh");
uzsuCurrentRows.find('.uzsuTimeMaxseriesstart').val(responseEntry.series.start.timeMax);
uzsuCurrentRows.find('.uzsuTimeCronseriesstart').val(responseEntry.series.start.timeCron);
if(responseEntry.hasOwnProperty('seriesCalculated') && responseEntry.seriesCalculated[0].seriesMin != null)
uzsuCurrentRows.find('.uzsuCalculatedseriesstart').val(responseEntry.seriesCalculated[0].seriesMin);
uzsuCurrentRows.find('.uzsuTimeMinseriesend').val(responseEntry.series.end.timeMin);
uzsuCurrentRows.find('.uzsuTimeOffsetInputseriesend').val(parseInt(responseEntry.series.end.timeOffset));
uzsuCurrentRows.find('.uzsuTimeOffsetTypeInputseriesend').find(':radio').prop('checked', false).checkboxradio("refresh")
.end().find('[value="'+responseEntry.series.end.timeOffsetType+'"]:radio').prop('checked', true).checkboxradio("refresh");
uzsuCurrentRows.find('.uzsuTimeMaxseriesend').val(responseEntry.series.end.timeMax);
uzsuCurrentRows.find('.uzsuTimeCronseriesend').val(responseEntry.series.end.timeCron);
if(responseEntry.hasOwnProperty('seriesCalculated') && responseEntry.seriesCalculated[0].seriesMax != null)
uzsuCurrentRows.find('.uzsuCalculatedseriesend').val(responseEntry.seriesCalculated[0].seriesMax);
// den Status richtig setzen
if (responseEntry.series.active == true)
{
self._uzsuSetSeriesColor(uzsuCurrentRows.find('.uzsuSeriesActive').first());
self._uzsuSetSeriesActiveState(uzsuCurrentRows.find('.uzsuRowSeriesLine .uzsuSeriesActive'))
}
}
}
// und die pull down Menüs richtig, damit die Einträge wieder stimmen und auch der active state gesetzt wird
if(responseEntry.event === 'time'){
uzsuCurrentRows.find('.uzsuSunActive').prop('checked',false).checkboxradio("refresh");
}
else{
uzsuCurrentRows.find('.uzsuSunActive').prop('checked',true).checkboxradio("refresh");
uzsuCurrentRows.find('.uzsuRowExpert .uzsuEvent select').val(responseEntry.event).selectmenu('refresh', true);
}
if(responseEntry.timeCron === 'series'){
if(responseEntry.series.start.event === 'time'){
uzsuCurrentRows.find('.uzsuSunActiveseriesstart').prop('checked',false).checkboxradio("refresh");
}
else{
uzsuCurrentRows.find('.uzsuSunActiveseriesstart').prop('checked',true).checkboxradio("refresh");
uzsuCurrentRows.find('.uzsuRowExpert .uzsuEventseriesstart select').val(responseEntry.series.start.event).selectmenu('refresh', true);
}
if(responseEntry.series.end.event === 'time'){
uzsuCurrentRows.find('.uzsuSunActiveseriesend').prop('checked',false).checkboxradio("refresh");
}
else{
uzsuCurrentRows.find('.uzsuSunActiveseriesend').prop('checked',true).checkboxradio("refresh");
uzsuCurrentRows.find('.uzsuRowExpert .uzsuEventseriesend select').val(responseEntry.series.end.event).selectmenu('refresh', true);
}
}
// in der Tabelle die Werte der rrule, dabei gehe ich von dem Standardformat FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR,SA,SU aus und setze für jeden Eintrag den Button.
var rrule = responseEntry.rrule;
if (typeof rrule === "undefined") {
rrule = '';
}
var ind = rrule.indexOf('BYDAY');
// wenn der Standard drin ist
if (ind > 0) {
var days = rrule.substring(ind);
// Setzen der Werte
uzsuCurrentRows.find('.uzsuWeekday input[type="checkbox"]').each(function(numberOfDay) {
$(this).prop('checked', days.indexOf($(this).val()) > 0).checkboxradio("refresh");
});
}
// jetzt die holiday themen für fhem
if(self.options.designtype == '2'){
uzsuCurrentRows.find('.uzsuHolidayWorkday').prop('checked', responseEntry.holiday.workday).checkboxradio("refresh");
uzsuCurrentRows.find('.uzsuHolidayWeekend').prop('checked', responseEntry.holiday.weekend).checkboxradio("refresh");
}
// Fallunterscheidung für den Expertenmodus
self._uzsuSetSunActiveState(uzsuCurrentRows.find('.uzsuRowExpert .uzsuEvent select'),'');
self._uzsuSetExpertColor(uzsuCurrentRows.find('.expertActive').first());
if(self.options.designtype == '0' && this.hasSeries){
self._uzsuSetSunActiveState(uzsuCurrentRows.find('.uzsuRowExpert .uzsuEventseriesstart select'), 'seriesstart');
self._uzsuSetExpertColor(uzsuCurrentRows.find('.expertActiveseriesstart').first());
self._uzsuSetSunActiveState(uzsuCurrentRows.find('.uzsuRowExpert .uzsuEventseriesend select'), 'seriesend');
self._uzsuSetExpertColor(uzsuCurrentRows.find('.expertActiveseriesend').first());
}
},
//----------------------------------------------------------------------------
// Funktionen für das dynamische Handling der Seiteninhalte des Popups
//----------------------------------------------------------------------------
// Setzt die Farbe des Expertenbuttons, je nach dem, ob eine der Optionen aktiv geschaltet wurde
_uzsuSetExpertColor: function(changedCheckbox){
var sourceRow = changedCheckbox.parents('.uzsuRowExpHoli, .uzsuRowCondition, .uzsuRowDelayedExec'); //.prev('.uzsuRow, .uzsuRowSeries').first().nextUntil('.uzsuRow').addBack();
var targetRow = sourceRow.prevAll('.uzsuRow, .uzsuRowSeries').first();
if (sourceRow.find('[class*="expertActive"]').is(':checked'))
{
targetRow.find('[class*="uzsuCellExpert"] button').addClass('ui-btn-active');
targetRow.find('[class*="uzsuCellSeries"] button').removeClass('ui-btn-active');
}
else{
// wenn der Expertenbutton in eine Serienzeile steht, schalte ihn nur inaktiv, wenn beide Zeilen (seriesstart und seriesend) inaktiv sind
if (!(sourceRow.hasClass('Series') && sourceRow.next().hasClass('Series') ? sourceRow.next().find('[class*="expertActive"]').is(':checked') : sourceRow.prev().find('[class*="expertActive"]').is(':checked')))
targetRow.find('[class*="uzsuCellExpert"] button').removeClass('ui-btn-active');
}
},
// Toggelt die eingabemöglichkeit für SUN Elemente in Abhängigkeit der Aktivschaltung
_uzsuSetSunActiveState: function(element, caller){
// status der eingaben setzen, das brauchen wir an mehreren stellen
var uzsuRowExpHoli = element.parents('.uzsuRowExpHoli');
var searchClass = '.uzsuTimeCron'+caller;
var searchLine = (caller == '' ? '.uzsuRowExpHoli' : '.uzsuRowSeriesLine')
var uzsuTimeCron = uzsuRowExpHoli.prevUntil(searchLine).find(searchClass).last();
var uzsuCalc = uzsuRowExpHoli.find('.uzsuCalculated'+caller).val();
if (uzsuRowExpHoli.find('.uzsuSunActive'+caller).is(':checked')){
if (caller == 'seriesend'){
uzsuRowExpHoli.prevUntil(searchLine).find('.uzsuSeriesEndTypeInput').last().find(':radio').prop('checked', false).checkboxradio("refresh").checkboxradio( "disable" )
.end().find('[value="t"]:radio').prop('checked', true).checkboxradio("refresh").checkboxradio( "disable" )
.parents().find('.uzsuCellText.uzsuTimeSerieMaxText').text(sv_lang.uzsu.seriesend);
}
uzsuTimeCron.attr('type','input').val(uzsuRowExpHoli.find('.uzsuEvent'+caller+' select').val()).textinput('disable');
var myExpertrow = uzsuRowExpHoli.next();
myExpertrow.find('.uzsuSeriesActive').prop('checked',false).checkboxradio("refresh");
}
else{
if(uzsuTimeCron.val().indexOf('sun')===0)
uzsuTimeCron.attr('type','time').val((uzsuCalc == undefined || uzsuCalc == '') ? '00:00' : uzsuCalc);
if(uzsuTimeCron.val().indexOf('series')!=0)
uzsuTimeCron.textinput('enable');
if (caller == 'seriesend'){
uzsuRowExpHoli.prevUntil(searchLine).find('.uzsuSeriesEndTypeInput').last().find(':radio').checkboxradio( "enable" )
.end().find('[value="t"]:radio').checkboxradio( "enable" )
}
}
},
// Toggelt die Eingabemöglichkeit für SERIES Elemente in Abhängigkeit der Aktivschaltung
_uzsuSetSeriesActiveState: function(element){
// status der eingaben setzen, das brauchen wir an mehreren stellen
var uzsuRowSeries = element.parents('.uzsuRowSeries');
var uzsuTimeCron = uzsuRowSeries.prevUntil('.uzsuRowSeries').find('.uzsuTimeCron');
var uzsuCalc = uzsuRowSeries.find('.uzsuCalculated').val();
if (uzsuRowSeries.find('.uzsuSeriesActive').is(':checked')){
uzsuTimeCron.attr('type','input').val('series').textinput('disable');
//uzsuTimeCron.attr('type','input').textinput('disable');
var myExpertrow = uzsuRowSeries.prevUntil('.uzsuRow');
myExpertrow.find('.uzsuSunActive').prop('checked',false).checkboxradio("refresh");
}
else{
if(uzsuTimeCron.val().indexOf('series')===0)
uzsuTimeCron.attr('type','time').val((uzsuCalc == undefined || uzsuCalc == '') ? '00:00' : uzsuCalc);
uzsuTimeCron.textinput('enable');
}
},
// Setzt die Farbe des Seriesbuttons, wenn aktiv geschaltet
_uzsuSetSeriesColor: function(changedCheckbox){
var rows = changedCheckbox.parents('.uzsuRowSeries, .uzsuRowCondition, .uzsuRowDelayedExec').prevAll('.uzsuRow').first().nextUntil('.uzsuRow').addBack();
if (rows.find('.uzsuSeriesActive').is(':checked'))
{
rows.find('.uzsuCellSeries button').addClass('ui-btn-active');
rows.find('[class*="uzsuCellExpert"] button').removeClass('ui-btn-active');
}
else
rows.find('.uzsuCellSeries button').removeClass('ui-btn-active');
},
// switch input format for series end time / number of cycles
_uzsuSetSeriesEndType: function(element){
var uzsuRowSeriesLine = element.parents('.uzsuRowSeries');
var uzsuSeriesEndType = element.find(':radio:checked').val();
uzsuRowSeriesLine.find('input.uzsuTimeMaxMinInput.uzsuTimeSerieMax').attr('type', uzsuSeriesEndType == 't' ? 'time' : 'number');
uzsuRowSeriesLine.find('.uzsuCellText.uzsuTimeSerieMaxText').text(uzsuSeriesEndType == 't' ? sv_lang.uzsu.seriesend : sv_lang.uzsu.seriescount);
},
// Serienzeile mit Eingaben auf der Hauptzeile benutzbar machen oder sperren bzw. die Statusupdates in die Zeile eintragen
_uzsuShowSeriesLine: function(e) {
// erst einmal alle verschwinden lassen
this._uzsuHideAllSeriesLines();
// Tabellezeile ermitteln, wo augerufen wurde
var uzsuSerieButton = $(e.currentTarget);
var row = uzsuSerieButton.closest('.uzsuRow');
// Zeile anzeigen
row.nextUntil('.uzsuRow', '.uzsuRowSeries').show();
// jetzt noch den Button einfärben
uzsuSerieButton.buttonMarkup({ icon: "arrow-l" });
},
//Expertenzeile mit Eingaben auf der Hauptzeile benutzbar machen oder sperren bzw. die Statusupdates in die Zeile eintragen
_uzsuShowExpertLine: function(e, caller) {
// erst einmal alle verschwinden lassen
this._uzsuHideAllExpertLines();
// Tabellezeile ermitteln, wo augerufen wurde
var uzsuExpertButton = $(e.currentTarget);
var row = uzsuExpertButton.closest(caller == 'Series' ? '.uzsuRowSeries' : '.uzsuRow');
if (caller == "Series")
row.nextUntil('.uzsuRow', '.Series').show();
else
row.nextUntil('.uzsuRow', '.uzsuExpertLine').not('.Series').show();
// jetzt noch den Button in der Zeile drüber auf arrow up ändern
uzsuExpertButton.buttonMarkup({ icon: "arrow-u" });
},
_uzsuHideAllExpertLines: function() {
$('.uzsuRowExpHoli, .uzsuRowCondition, .uzsuRowDelayedExec').hide();
$('[class*="uzsuCellExpert"] button').buttonMarkup({ icon: "arrow-d" });
},
_uzsuHideAllSeriesLines: function() {
$('.uzsuRowSeries, .Series').hide();
$('.uzsuCellSeries button').buttonMarkup({ icon: "arrow-r" });
$('.uzsuCellExpertSeries button').buttonMarkup({ icon: "arrow-d" });
},
//Interpolationszeile
_uzsuShowInterpolationLine: function(e) {
// erst einmal alle verschwinden lassen
// this._uzsuHideInterpolationLine();
// Tabellezeile ermitteln, wo augerufen wurde
var uzsuInterpolationButton = $(e.currentTarget);
$('#uzsuRowInterpolation').show();
// jetzt noch den Button in der Zeile drüber auf arrow up ändern
uzsuInterpolationButton.buttonMarkup({ icon: "arrow-u" });
},
_uzsuHideInterpolationLine: function() {
$('#uzsuRowInterpolation').hide();
$('.uzsuCellInterpolation button').buttonMarkup({ icon: "arrow-d" });
},
_uzsuSaveTableRow: function(responseEntry, tableRow) {
var self = this;
uzsuCurrentRows = $(tableRow).nextUntil('.uzsuRow').addBack();
responseEntry.value = uzsuCurrentRows.find('.uzsuValueCell select, .uzsuValueCell input').val();
responseEntry.active = uzsuCurrentRows.find('.uzsuActive').is(':checked');
if (this.hasOnce)
responseEntry.once = uzsuCurrentRows.find('.uzsuOnce').is(':checked');
// hier die conditions, wenn im json angelegt
if(self.options.designtype == '2'){
// conditions
responseEntry.condition.deviceString = uzsuCurrentRows.find('.uzsuConditionDeviceStringInput').val();
responseEntry.condition.type = uzsuCurrentRows.find('select.uzsuConditionType').val();
responseEntry.condition.value = uzsuCurrentRows.find('.uzsuConditionValueInput').val();
responseEntry.condition.active = uzsuCurrentRows.find('.uzsuConditionActive').is(':checked');
// deleayed exec
responseEntry.delayedExec.deviceString = uzsuCurrentRows.find('.uzsuDelayedExecDeviceStringInput').val();
responseEntry.delayedExec.type = uzsuCurrentRows.find('select.uzsuDelayedExecType').val();
responseEntry.delayedExec.value = uzsuCurrentRows.find('.uzsuDelayedExecValueInput').val();
responseEntry.delayedExec.active = uzsuCurrentRows.find('.uzsuDelayedExecActive').is(':checked');
}
responseEntry.timeMin = uzsuCurrentRows.find('.uzsuTimeMin').val();
responseEntry.timeOffset = uzsuCurrentRows.find('.uzsuTimeOffsetInput').val();
if(self.options.designtype == '0'){
responseEntry.timeOffsetType = uzsuCurrentRows.find('.uzsuTimeOffsetTypeInput :radio:checked').val();
}
responseEntry.timeMax = uzsuCurrentRows.find('.uzsuTimeMax').val();
responseEntry.timeCron = uzsuCurrentRows.find('.uzsuTimeCron').val();
// event etwas komplizierter, da übergangslösung
if(uzsuCurrentRows.find('.uzsuSunActive').is(':checked')){
responseEntry.event = uzsuCurrentRows.find('.uzsuEvent select').val();
}
else{
responseEntry.event = 'time';
}
// in der Tabelle die Werte der rrule, dabei gehe ich von dem Standardformat FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR,SA,SU aus und setze für jeden Eintrag den Button. Setzen der Werte.
var first = true;
var rrule = '';
uzsuCurrentRows.find('.uzsuWeekday input[type="checkbox"]').each(function(numberOfDay) {
if ($(this).is(':checked')) {
if (first) {
first = false;
rrule = 'FREQ=WEEKLY;BYDAY=' + $(this).val();
}
else {
rrule += ',' + $(this).val();
}
}
});
responseEntry.rrule = rrule;
// jetzt die holiday themem für fhem
if(self.options.designtype === '2'){
responseEntry.holiday.workday = uzsuCurrentRows.find('.uzsuHolidayWorkday').is(':checked');
responseEntry.holiday.weekend = uzsuCurrentRows.find('.uzsuHolidayWeekend').is(':checked');
}
// Serienzeiten
if(self.options.designtype === '0' && this.hasSeries){
if (uzsuCurrentRows.find('.uzsuCellSeries button').hasClass("ui-btn-active") == true)
{
responseEntry.series = {};
responseEntry.series.active = uzsuCurrentRows.find('.uzsuCellSeries button').hasClass("ui-btn-active")
responseEntry.series.timeSeriesMin = uzsuCurrentRows.find('.uzsuTimeSerieMin').val();
var timeSeriesEndType = uzsuCurrentRows.find('.uzsuSeriesEndTypeInput :radio:checked').val();
if (timeSeriesEndType == "t"){
responseEntry.series.timeSeriesMax = uzsuCurrentRows.find('.uzsuTimeSerieMax').val();
if (responseEntry.series.hasOwnProperty('timeSeriesCount'))
delete responseEntry.series.timeSeriesCount;
}
else {
responseEntry.series.timeSeriesCount = uzsuCurrentRows.find('.uzsuTimeSerieMax').val();
if (responseEntry.series.hasOwnProperty('timeSeriesMax'))
delete responseEntry.series.timeSeriesMax;
}
responseEntry.series.timeSeriesIntervall = uzsuCurrentRows.find('.uzsuSerieTimeInterval').val();
responseEntry.series.start = {};
responseEntry.series.start.timeCron = uzsuCurrentRows.find('.uzsuTimeCronseriesstart').val();
responseEntry.series.start.timeMin = uzsuCurrentRows.find('.uzsuTimeMinseriesstart').val();
responseEntry.series.start.timeMax = uzsuCurrentRows.find('.uzsuTimeMaxseriesstart').val();
responseEntry.series.start.timeOffset = uzsuCurrentRows.find('.uzsuTimeOffsetInputseriesstart').val();;
responseEntry.series.start.timeOffsetType = uzsuCurrentRows.find('.uzsuTimeOffsetTypeInputseriesstart :radio:checked').val();
if(uzsuCurrentRows.find('.uzsuSunActiveseriesstart').is(':checked'))
responseEntry.series.start.event = uzsuCurrentRows.find('.uzsuEventseriesstart select').val();
else
responseEntry.series.start.event = 'time';
responseEntry.series.end = {};
responseEntry.series.end.timeCron = uzsuCurrentRows.find('.uzsuTimeCronseriesend').val();
responseEntry.series.end.timeMin = uzsuCurrentRows.find('.uzsuTimeMinseriesend').val();
responseEntry.series.end.timeMax = uzsuCurrentRows.find('.uzsuTimeMaxseriesend').val();
responseEntry.series.end.timeOffset = uzsuCurrentRows.find('.uzsuTimeOffsetInputseriesend').val();;
responseEntry.series.end.timeOffsetType = uzsuCurrentRows.find('.uzsuTimeOffsetTypeInputseriesend :radio:checked').val();
if(uzsuCurrentRows.find('.uzsuSunActiveseriesend').is(':checked'))
responseEntry.series.end.event = uzsuCurrentRows.find('.uzsuEventseriesend select').val();
else
responseEntry.series.end.event = 'time';
if (responseEntry.hasOwnProperty('seriesCalculated'))
delete responseEntry.seriesCalculated;
}
else
{
try
{ delete responseEntry.series; }
catch (e)
{}
}
};
if (responseEntry.hasOwnProperty('activeToday') && (this.popupStartDay != new Date().getDay() || uzsuCurrentRows.hasClass('changed')))
responseEntry.activeToday = false;
},
//----------------------------------------------------------------------------
// Funktionen für den Seitenaufbau
//----------------------------------------------------------------------------
_uzsuBuildTableFooter: function() {
var tt = "";
// Zeileneinträge abschliessen und damit die uzsuTableMain
tt += "</div>";
// Aufbau des Footers
tt += "<div class='uzsuTableFooter'>" +
"<div class='uzsuRowFooter'>" +
"<span style='float:right'>";
// UZSU Interpolation
if(sv_lang.uzsu.interpolation && this.hasInterpolation){
tt += "<div class='uzsuCellInterpolation' style='float:left;'>" +
"<div class='uzsuCellText'>" + sv_lang.uzsu.options + "</div>" +
"<button data-mini='true' data-icon='arrow-d' data-iconpos='notext' class='ui-icon-shadow'></button>" +
"</div>";
}
tt+= "<div class='uzsuCell' style='float: left'>" +
"<fieldset data-mini='true'>" +
"<label><input type='checkbox' id='uzsuGeneralActive'>" + sv_lang.uzsu.active + "</label>" +
"</fieldset>"+
"</div>";
if (this.hasOnce)
tt+= "<div class='uzsuCell' style='float: left'>" +
"<fieldset data-mini='true'>" +
"<label><input type='checkbox' id='uzsuGeneralOnce'>"+ sv_lang.uzsu.once +"</label>" +
"</fieldset>" +
"</div>";
tt+="<div class='uzsuCell' style='float: left'>" +
"<div data-role='controlgroup' data-type='horizontal' data-inline='true' data-mini='true'>" +
"<button id='uzsuAddTableRow'>" + sv_lang.uzsu.add + "</button>" +
"<button id='uzsuSortTime'>" + sv_lang.uzsu.sort + "</button>" +
"</div>" +
"</div>" +
"<div class='uzsuCell' style='float: right'>" +
"<div data-role='controlgroup' data-type='horizontal' data-inline='true' data-mini='true'>" +
"<button id='uzsuCancel'>" + sv_lang.uzsu.cancel + "</button>" +
"<button id='uzsuSaveQuit'>" + sv_lang.uzsu.ok + "</button>" +
"</div>" +
"</div>" +
"</div>" +
"</span>";
if(sv_lang.uzsu.interpolation && this.hasInterpolation){
tt +=
"<div id='uzsuRowInterpolation' style='display: none; float: right; margin: 0 15px 5px 0;'>" +
"<span style='float:left; margin-right: 12px;'>" +
"<div class='uzsuRowExpertText'>" + sv_lang.uzsu.interpolation + "</div>" +
"<div class='uzsuCell'>" +
"<div class='uzsuCellText'>" + sv_lang.uzsu.interpolation + "</div>" +
"<select data-mini='true' data-native-menu='false' id='uzsuInterpolationType'>" +
"<option value='none'>" + sv_lang.uzsu.nointerpolation + "</option>" +
"<option value='cubic'>" + sv_lang.uzsu.cubic + "</option>" +
"<option value='linear'>" + sv_lang.uzsu.linear + "</option>" +
"</select>" +
"</div>"
if(this.hasOnce)
tt += "<div class='uzsuCell'>" +
"<div class='uzsuCellText'>" + sv_lang.uzsu.intperiod + "</div>" +
"<select data-mini='true' data-native-menu='false' id='uzsuInterpolationPeriod'>" +
"<option value='false'>" + sv_lang.uzsu.conti + "</option>" +
"<option value='true'>" + sv_lang.uzsu.perday + "</option>" +
"</select>" +
"</div>"
tt += "<div class='uzsuCell'>" +
"<div class='uzsuCellText'>" + sv_lang.uzsu.intervaltime + "</div>" +
"<input type='number' data-clear-btn='false' id='uzsuInterpolationInterval' style='width:50px;' min='0' class='uzsuValueInput positivenumbers'>" +
"</div>" +
"</span>" +
"<span style='float:left; margin-left: 12px;'>" +
"<div class='uzsuRowExpertText'>" + sv_lang.uzsu.inittime + "</div>" +
"<div class='uzsuCell'>" +
"<div class='uzsuCellText'>" + sv_lang.uzsu.inittime_header + "</div>" +
"<input type='number' data-clear-btn='false' id='uzsuInitAge' style='width:50px;' min='0' class='uzsuValueInput positivenumbers'>" +
"<div class='uzsuCellText' style='visibility:hidden'><label><input type='checkbox' id='uzsuInitialized'>Init</label></div>" +
"</div>" +
"</span>" +
"</div>";
}
tt +=
"</div>";
// und der Abschluss des uzsuClear als Rahmen für den float:left und des uzsuPopup divs
tt += "</div></div>";
return tt;
},
_uzsuFillTable: function(response) {
var self = this;
// Tabelle füllen. Es werden die Daten aus der Variablen response gelesen und in den Status Darstellung der Widgetblöcke zugewiesen. Der aktuelle Status in dann in der Darstellung enthalten !
var numberOfEntries = response.list.length;
// jetzt wird die Tabelle befüllt allgemeiner Status, bitte nicht mit attr, sondern mit prop, siehe // https://github.com/jquery/jquery-mobile/issues/5587
// INTERPOLATION
if(this.hasInterpolation) {
$('#uzsuInterpolationType').val(response.interpolation.type).selectmenu("refresh", true);
if(this.hasOnce){
$('#uzsuInterpolationPeriod').val(response.interpolation.perday.toString().toLowerCase()).selectmenu("refresh", true);
}
$('#uzsuInterpolationInterval').val(response.interpolation.interval);
$('#uzsuInitAge').val(response.interpolation.initage);
$('#uzsuInitialized').prop('checked', response.interpolation.initialized).checkboxradio("refresh");
}
$('#uzsuGeneralActive').prop('checked', response.active).checkboxradio("refresh");
if (this.hasOnce)
$('#uzsuGeneralOnce').prop('checked', response.once).checkboxradio("refresh");
// dann die Werte der Tabelle
$('.uzsuRow').each(function(numberOfRow, tableRow) {
var responseEntry = response.list[numberOfRow];
self._uzsuFillTableRow(responseEntry, tableRow);
});
// Verhindern negativer Zahleneingabe
$('.positivenumbers').keypress(function(evt){
var charCode = (evt.which) ? evt.which : event.keyCode;
return !(charCode > 31 && (charCode < 48 || charCode > 57));
});
},
_uzsuSaveTable: function(response, saveSmarthome) {
var self = this;
// Tabelle auslesen und speichern
var numberOfEntries = response.list.length;
// hier werden die Daten aus der Tabelle wieder in die items im Backend zurückgespielt bitte darauf achten, dass das zurückspielen exakt dem der Anzeige enspricht. Gesamthafte Aktivierung
response.active = $('#uzsuGeneralActive').is(':checked');
if (this.hasOnce)
response.once = $('#uzsuGeneralOnce').is(':checked');
// Interpolation
if(this.hasInterpolation) {
response.interpolation.type = $('#uzsuInterpolationType').val();
response.interpolation.interval = $('#uzsuInterpolationInterval').val();
response.interpolation.initage = $('#uzsuInitAge').val();
response.interpolation.initialized = $('#uzsuInitialized').is(':checked');
if (this.hasOnce)
response.interpolation.perday = $('#uzsuInterpolationPeriod').val() == "true";
}
// Einzeleinträge
$('.uzsuRow').each(function(numberOfRow, tableRow) {
var responseEntry = response.list[numberOfRow];
self._uzsuSaveTableRow(responseEntry, tableRow);
});
if (response.hasOwnProperty('SunCalculated'))
delete response.SunCalculated;
try {
delete response.sunrise;
delete response.sunset;
delete response.lastvalue;
}
catch (e) {}
// über json Interface / Treiber herausschreiben
if (saveSmarthome) {
this._uzsuCollapseTimestring(response);
this._write(response);
}
},
//----------------------------------------------------------------------------
// Funktionen für das Erweitern und Löschen der Tabelleneinträge
//----------------------------------------------------------------------------
_uzsuAddTableRow: function(response) {
// Tabellenzeile einfügen
// alten Zustand mal in die Liste rein. da der aktuelle Zustand ja nur im Widget selbst enthalten ist, wird er vor dem Umbau wieder in die Variable response zurückgespeichert.
this._uzsuSaveTable(response, false);
// ich hänge immer an die letzte Zeile dran ! erst einmal das Array erweitern
response.list.push({active:false,rrule:'',value:null,event:'time',timeMin:'',timeMax:'',timeCron:'00:00',timeOffset:'',timeOffsetType:'m',condition:{deviceString:'',type:'String',value:'',active:false},delayedExec:{deviceString:'',type:'String',value:'',active:false},holiday:{workday:false,weekend:false}});
// dann eine neue HTML Zeile genenrieren
tt = this._uzsuBuildTableRow(response.list.length);
// Zeile in die Tabelle einbauen
$(tt).appendTo('#uzsuTable').enhanceWithin();
// und Daten ausfüllen. hier werden die Zeile wieder mit dem Status beschrieben. Status ist dann wieder im Widget
this._uzsuFillTable(response);
// nach unten scrollen
$("#uzsuTable").scrollTop(function() { return this.scrollHeight; });
},
_uzsuDelTableRow: function(response, e) {
// Zeile und Zeilennummer heraus finden
var row = $(e.currentTarget).closest('.uzsuRow');
var numberOfRowToDelete = row.parent().find('.uzsuRow').index(row);
// Daten aus der Liste löschen
response.list.splice(numberOfRowToDelete, 1);
// Die entsprechende Zeile inkl. den nachfolgenden Expertenzeilen aus dem DOM entfernen
row.nextUntil('.uzsuRow').addBack().remove();
},
//----------------------------------------------------------------------------
// Funktionen für das Sortieren der Tabelleneinträge
//----------------------------------------------------------------------------
_uzsuSortTime: function(response, e) {
// erst aus dem Widget zurücklesen, sonst kann nicht im Array sortiert werden (alte daten)
this._uzsuSaveTable(response, false);
// sortieren der Listeneinträge nach zeit
response.list.sort(function(a, b) {
// sort Funktion, wirklich vereinfacht für den speziellen Fall
// ergänzt um das sunrise und sunset Thema
var A = a.timeCron.replace(':', '');
var B = b.timeCron.replace(':', '');
// Reihenfolge ist erst die Zeiten, dann sunrise, dann sunset
if(A == 'sunrise') A = '2400';
if(A == 'sunset') A = '2401';
if(B == 'sunrise') B = '2400';
if(B == 'sunset') B = '2401';
return (A - B);
});
// dann die Einträge wieder schreiben
this._uzsuFillTable(response);
},
//----------------------------------------------------------------------------
// Funktionen für den Aufbau des Popups und das Einrichten der Callbacks
//----------------------------------------------------------------------------
_uzsuRuntimePopup: function(response) {
var self = this;
this.popupStartDay = new Date().getDay();
// Steuerung des Popups erst einmal wird der Leeranteil angelegt
// erst den Header, dann die Zeilen, dann den Footer
var tt = this._uzsuBuildTableHeader();
for (var numberOfRow = 0; numberOfRow < response.list.length; numberOfRow++) {
tt += this._uzsuBuildTableRow(numberOfRow);
}
tt += this._uzsuBuildTableFooter();
// dann hängen wir das an die aktuelle Seite
var uzsuPopup = $(tt).appendTo(this.element).enhanceWithin().popup().on({
popupbeforeposition: function(ev, ui) {
var maxHeight = $(window).height() - 230;
$(this).find('.uzsuTableMain').css('max-height', maxHeight + 'px').css('overflow-y','auto').css('overflow-x','hidden');
},
popupafteropen: function(ev, ui) {
$(this).popup('reposition', {y: 30})
},
popupafterclose: function(ev, ui) {
$(this).remove();
$(window).off('resize', self._onresize);
var target = $(self.element).parents('[data-role="popup"].uzsu_reopen');
if (target.length != 0)
target.popup('open');
}
});
// dann speichern wir uns für cancel die ursprünglichen im DOM gespeicherten Werte in eine Variable ab
var responseCancel = jQuery.extend(true, {}, response);
// dann die Werte eintragen.
this._uzsuFillTable(response);
// Popup schliessen mit close rechts oben in der Box oder mit Cancel in der Leiste
uzsuPopup.find('#uzsuClose, #uzsuCancel').bind('click', function(e) {
// wenn keine Änderungen gemacht werden sollen (cancel), dann auch im cache die alten Werte
uzsuPopup.popup('close');
});
// speichern mit SaveQuit
uzsuPopup.find('#uzsuSaveQuit').bind('click', function(e) {
// jetzt wird die Kopie auf das Original kopiert und geschlossen
self._uzsuSaveTable(response, true);
uzsuPopup.popup('close');
// disable editing until dict update has been received
self._disabled = true;
$('[data-widget="device.uzsuicon"][data-item="'+ self.options.item + '"]').addClass('blink');
$('[data-widget*="device.uzsu"][data-item="'+ self.options.item + '"]').widget('disable').find('.uzsu-active-toggler').addClass('hidden').hide();
setTimeout(function(){$('[data-widget*="device.uzsu"][data-item="'+ self.options.item + '"]').widget('enable').find('.uzsu-active-toggler').removeClass('hidden').show();}, 15000); // fallback in case update is not coming
});
// Eintrag hinzufügen mit add
uzsuPopup.find('#uzsuAddTableRow').bind('click', function(e) {
self._uzsuAddTableRow(response);
});
// Eintrag sortieren nach Zeit
uzsuPopup.find('#uzsuSortTime').bind('click', function(e) {
self._uzsuSortTime(response);
});
// Löschen mit del als Callback eintragen
uzsuPopup.delegate('.uzsuDelTableRow', 'click', function(e) {
self._uzsuDelTableRow(response, e);
});
// call time series mode
uzsuPopup.delegate('.uzsuCellSeries button', 'click', function(e) {
if($(this).hasClass('ui-icon-arrow-l'))
self._uzsuHideAllSeriesLines();
else
self._uzsuShowSeriesLine(e);
});
// call Expert Mode
uzsuPopup.delegate('[class*="uzsuCellExpert"] button', 'click', function(e) {
var caller = $(this).parent()[0].className;
caller = (caller.length > 14 ? caller.substring(14) : '');
if($(this).hasClass('ui-icon-arrow-u'))
self._uzsuHideAllExpertLines();
else
self._uzsuShowExpertLine(e, caller);
});
// Handler, um den expert button Status zu setzen
uzsuPopup.delegate('input[class*="expertActive"]', 'change', function (){
self._uzsuSetExpertColor($(this));
});
// Handler, um den Status anhand des Pulldowns SUN zu setzen
uzsuPopup.delegate('.uzsuRowExpert [class*="uzsuEvent"] select, input[class*="uzsuSunActive"]', 'change', function (){
var searchClass = $(this)[0].className;
if ($(this)[0].nodeName == "SELECT")
searchClass = $(this).parents()[2].className;
var searchPos = searchClass.lastIndexOf('series');
var caller = (searchPos < 0 ? '' : searchClass.substring(searchPos));
self._uzsuSetSunActiveState($(this), caller);
});
// Handler, um den Series button Status zu setzen
uzsuPopup.delegate('input.uzsuSeriesActive', 'change', function (){
self._uzsuSetSeriesColor($(this));
});
uzsuPopup.delegate('.uzsuRowSeries .uzsuEvent select, input.uzsuSeriesActive', 'change', function (){
self._uzsuSetSeriesActiveState($(this));
});
// switch input type for series end mode
uzsuPopup.delegate('.uzsuRowSeries fieldset.uzsuSeriesEndTypeInput', 'change', function (){
self._uzsuSetSeriesEndType($(this));
});
// call Interpolation Mode
uzsuPopup.delegate('.uzsuCellInterpolation button', 'click', function(e) {
if($(this).hasClass('ui-icon-arrow-u'))
self._uzsuHideInterpolationLine();
else
self._uzsuShowInterpolationLine(e);
});
// mark changed entries
uzsuPopup.delegate('input, select', 'change', function (e){
$(e.target).parents('.uzsuRow').addClass('changed');
});
// hier wir die aktuelle Seite danach durchsucht, wo das Popup ist und im folgenden das Popup initialisiert, geöffnet und die schliessen
// Funktion daran gebunden. Diese entfernt wieder das Popup aus dem DOM Baum nach dem Schliessen mit remove
uzsuPopup.popup('open'); //.css({ position: 'fixed', top: '30px' });
},
_uzsuParseAndCheckResponse: function(response) {
var designType = this.options.designtype;
var valueType = this.options.valuetype;
var self = this;
// Fehlerbehandlung für ein nicht vorhandenes DOM Objekt. Das response Objekt ist erst da, wenn es mit update angelegt wurde. Da diese
// Schritte asynchron erfolgen, kann es sein, dass das Icon bereits da ist, clickbar, aber noch keine Daten angekommen. Dann darf ich nicht auf diese Daten zugreifen wollen !
if(response.list === undefined){
var supplement = (this.options.item.substr(-5) != ".uzsu") ? "Seems this is not an uzsu item. Try '" + this.options.item+".uzsu'." : "";
notify.message("error", "UZSU widget", "No UZSU data available in item '" + this.options.item + "'" + (this.options.id ? " for widget '" + this.options.id + "'. " : ". ") + supplement);
return false;
}
// jetzt kommt noch die Liste von Prüfungen, damit hinterher keine Fehler passieren, zunächst fehlerhafter designType (unbekannt)
if ((designType != '0') && (designType != '2')) {
notify.message("error", "UZSU widget", "Design type '" + designType + "' is not supported in widget " + this.id + ".");
return false;
}
// fehlerhafter valueType (unbekannt)
if ((valueType !== 'bool') && (valueType !== 'num') && (valueType !== 'text') && (valueType !== 'list')) {
notify.message("error", "UZSU widget", "Value type '" + valueType + "' is not supported in widget " + this.id + ".");
return false;
}
this.hasOnce = false;
// Interpolation für SmartHomeNG setzen
if(designType == '0') {
if(response.interpolation === undefined){
this.hasInterpolation = false
console.log('UZSU interpolation not available. Update the smarthomeNG UZSU plugin to enable interpolation.');
//response.interpolation = {type:'none',interval:0,initage:0,initialized:false,itemtype:'none'};
}
else if(!response.interpolation.itemtype in ['num']) {
this.hasInterpolation = false
notify.message("warning", 'UZSU interpolation not supported by itemtype');
}
else {
this.hasInterpolation = true
console.log('UZSU interpolation for item "' + this.options.item + '" set to ' + response.interpolation.type);
}
//plugin version in dict has been introduced with the series functionality - no explicit version check necessary
if (response.plugin_version === undefined){
this.hasSeries = false
console.log('UZSU series not available. Update the smarthomeNG UZSU plugin to enable series.');
}
else {
this.hasSeries = true;
if (response.plugin_version >= '2.1.0')
this.hasOnce = true;
}
}
//
// Umsetzung des time parameters in die Struktur, die wir hinterher nutzen wollen
//
$.each(response.list, function(numberOfRow, entry) {
// bei designType '0' wird rrule nach Wochentagen umgewandelt und ein festes Format vorgegegeben hier sollte nichts versehentlich überschrieben werden
if (designType == '0') {
// "time" von SmartHomeNG parsen
var timeParts = (entry.time || "").match(/^((\d{1,2}:\d{1,2})<)?(sunrise|sunset)(([+-]\d+)([m°]?))?(<(\d{1,2}:\d{1,2}))?$/);
if(timeParts == null) { // entry.time is a plain time string
entry.event = "time";
entry.timeCron = entry.time;
entry.timeMin = "";
entry.timeMax = "";
entry.timeOffset = "";
entry.timeOffsetType = "m";
}
else { // entry.time is a sun event
entry.event = timeParts[3];
entry.timeCron = '00:00';
entry.timeMin = timeParts[2];
entry.timeMax = timeParts[8];
entry.timeOffset = Number(timeParts[5]);
entry.timeOffsetType = timeParts[6];
}
delete entry.time;
if(entry.timeCron == 'series')
{
self.hasSeries = true;
entry.series.start = {};
var timeParts = (entry.series.timeSeriesMin || "").match(/^((\d{1,2}:\d{1,2})<)?(sunrise|sunset)(([+-]\d+)([m°]?))?(<(\d{1,2}:\d{1,2}))?$/);
if(timeParts == null) { // entry.series.timeSeriesMin is a plain time string
entry.series.start.event = "time";
entry.series.start.timeCron = entry.series.timeSeriesMin;
entry.series.start.timeMin = "";
entry.series.start.timeMax = "";
entry.series.start.timeOffset = "";
entry.series.start.timeOffsetType = "m";
}
else { // entry.time is a sun event
entry.series.start.event = timeParts[3];
entry.series.start.timeCron = '00:00';
entry.series.start.timeMin = timeParts[2];
entry.series.start.timeMax = timeParts[8];
entry.series.start.timeOffset = Number(timeParts[5]);
entry.series.start.timeOffsetType = timeParts[6];
}
delete entry.series.timeSeriesMin;
entry.series.end = {};
var timeParts = (entry.series.timeSeriesMax || "").match(/^((\d{1,2}:\d{1,2})<)?(sunrise|sunset)(([+-]\d+)([m°]?))?(<(\d{1,2}:\d{1,2}))?$/);
if(timeParts == null) { // entry.series.timeSeriesMax is a plain time string
entry.series.end.event = "time";
entry.series.end.timeCron = (entry.series.timeSeriesMax != undefined ? entry.series.timeSeriesMax : entry.series.timeSeriesCount);
entry.series.end.timeMin = "";
entry.series.end.timeMax = "";
entry.series.end.timeOffset = "";
entry.series.end.timeOffsetType = "m";
}
else { // entry.time is a sun event
entry.series.end.event = timeParts[3];
entry.series.end.timeCron = '00:00';
entry.series.end.timeMin = timeParts[2];
entry.series.end.timeMax = timeParts[8];
entry.series.end.timeOffset = Number(timeParts[5]);
entry.series.end.timeOffsetType = timeParts[6];
}
delete entry.series.timeSeriesMax;
}
// test, ob die RRULE fehlerhaft ist
if (entry.rrule && (entry.rrule.length > 0) && (entry.rrule.indexOf('FREQ=WEEKLY;BYDAY=') !== 0)) {
if (!confirm("Error: Parameter designType is '0', but saved RRULE string in UZSU '" + entry.rrule + "' does not match default format FREQ=WEEKLY;BYDAY=MO... on item " + this.options.item + ". Should this entry be overwritten?")) {
return false;
}
}
}
// wenn designType = '2' und damit fhem auslegung ist muss der JSON String auf die entsprechenden einträge erweitert werden (falls nichts vorhanden)
if (designType == '2') {
// test, ob die einträge für conditions vorhanden sind
if (entry.condition === undefined){
entry.condition = {deviceString:'',type:'String',value:'',active:false};
}
// test, ob die einträge für delayed exec vorhanden sind
if (entry.delayedExec === undefined){
entry.delayedExec = {deviceString:'',type:'String',value:'',active:false};
}
// test, ob die einträge für holiday gesetzt sind
if (entry.holiday === undefined){
entry.holiday = {workday:false, weekend:false};
}
}
});
if (designType = "0" && io.address != undefined && io.address != '') // preserve plugin_version only in offline driver
delete response.plugin_version;
return true;
},
_uzsuCollapseTimestring: function(response){
var self = this;
// Clear unused properties for FHEM
if (self.options.designtype == '2') {
delete response.interpolation;
}
$.each(response.list, function(numberOfEntry, entry) {
// zeitstring wieder zusammenbauen, falls Event <> 'time', damit wir den richtigen Zusammenbau im zeitstring haben
if(entry.event === 'time'){
// wenn der eintrag time ist, dann kommt die zeit rein
entry.time = entry.timeCron;
}
else{
// ansonsten wird er aus den Bestandteilen zusammengebaut
entry.time = '';
if(entry.timeMin != null && entry.timeMin.length > 0){
entry.time += entry.timeMin + '<';
}
entry.time += entry.event;
if(entry.timeOffset > 0){
entry.time += '+' + entry.timeOffset + (entry.timeOffsetType == undefined ? '' : entry.timeOffsetType);
}
else if(entry.timeOffset < 0){
entry.time += entry.timeOffset + (entry.timeOffsetType == undefined ? '' : entry.timeOffsetType);
}
if(entry.timeMax != null && entry.timeMax.length > 0){
entry.time += '<' + entry.timeMax;
}
}
if (entry.time === 'series'){
if(entry.series.start.event === 'time'){
// wenn der eintrag time ist, dann kommt die zeit rein
entry.series.timeSeriesMin = entry.series.start.timeCron;
}
else{
// ansonsten wird er aus den Bestandteilen zusammengebaut
entry.series.timeSeriesMin = '';
if(entry.series.start.timeMin != null && entry.series.start.timeMin.length > 0){
entry.series.timeSeriesMin += entry.series.start.timeMin + '<';
}
entry.series.timeSeriesMin += entry.series.start.event;
if(entry.series.start.timeOffset > 0){
entry.series.timeSeriesMin += '+' + entry.series.start.timeOffset + (entry.series.start.timeOffsetType == undefined ? '' : entry.series.start.timeOffsetType);
}
else if(entry.series.start.timeOffset < 0){
entry.series.timeSeriesMin += entry.series.start.timeOffset + (entry.series.start.timeOffsetType == undefined ? '' : entry.series.start.timeOffsetType);
}
if(entry.series.start.timeMax != null && entry.series.start.timeMax.length > 0){
entry.series.timeSeriesMin += '<' + entry.series.start.timeMax;
}
}
if (!entry.series.hasOwnProperty('timeSeriesCount')){
if(entry.series.end.event === 'time'){
// wenn der eintrag time ist, dann kommt die zeit rein
entry.series.timeSeriesMax = entry.series.end.timeCron;
}
else{
// ansonsten wird er aus den Bestandteilen zusammengebaut
entry.series.timeSeriesMax = '';
if(entry.series.end.timeMin != null && entry.series.end.timeMin.length > 0){
entry.series.timeSeriesMax += entry.series.end.timeMin + '<';
}
entry.series.timeSeriesMax += entry.series.end.event;
if(entry.series.end.timeOffset > 0){
entry.series.timeSeriesMax += '+' + entry.series.end.timeOffset + (entry.series.end.timeOffsetType == undefined ? '' : entry.series.end.timeOffsetType);
}
else if(entry.series.end.timeOffset < 0){
entry.series.timeSeriesMax += entry.series.end.timeOffset + (entry.series.end.timeOffsetType == undefined ? '' : entry.series.end.timeOffsetType);
}
if(entry.series.end.timeMax != null && entry.series.end.timeMax.length > 0){
entry.series.timeSeriesMax += '<' + entry.series.end.timeMax;
}
}
}
delete entry.series.start;
delete entry.series.end;
}
// Clear unused properties for SmartHomeNG
if (self.options.designtype == '0') {
delete entry.event;
delete entry.timeCron;
delete entry.timeMin;
delete entry.timeMax;
delete entry.timeOffset;
delete entry.timeOffsetType;
delete entry.condition;
delete entry.delayedExec;
delete entry.holiday;
}
// Clear unused properties for FHEM
else if (self.options.designtype == '2') {
//delete entry.time; TODO: unsure if this is used in FHEM or not. if not, the code above does not need to be executed in designType=2
}
});
},
});
// ----- device.uzsuicon ------------------------------------------------------
// ----------------------------------------------------------------------------
$.widget("sv.device_uzsuicon", $.sv.device_uzsu, {
initSelector: '[data-widget="device.uzsuicon"]',
options: {
valueparameterlist: null,
headline: '',
designtype: '',
valuetype: 'bool'
},
_update: function(response) {
this._super(response);
// Das Icon wird aktiviert, falls Status auf aktiv, ansonsten deaktiviert angezeigt. Basiert auf der Implementierung von aschwith
if(this._uzsudata.active === true) {
this.element.find('.icon-off').hide();
this.element.find('.icon-on').show();
}
else {
this.element.find('.icon-on').hide();
this.element.find('.icon-off').show();
}
},
_events: {
'click': function(event) {
// hier werden die Parameter aus den Attributen herausgenommen und beim Öffnen mit .open(....) an das Popup Objekt übergeben
// und zwar mit deep copy, damit ich bei cancel die ursprünglichen werte nicht überschrieben habe
var response = jQuery.extend(true, {}, this._uzsudata);
var that = this;
if (this._uzsuParseAndCheckResponse(response)) {
// Öffnen des Popups bei clicken des icons und Ausführung der Eingabefunktion
// vorher das übergeordnete Popup schließen, falls uzsuicon in einem Popup platziert ist
var target = $(this.element).parents('[data-role="popup"]');
if (target.length != 0){
target.popup().on('popupafterclose', function(){
that._uzsuRuntimePopup(response);
target.popup().off('popupafterclose');
});
target.popup('close');
}
else
this._uzsuRuntimePopup(response);
}
}
},
});
// ----- device.uzsugraph -----------------------------------------------------
// ----------------------------------------------------------------------------
$.widget("sv.device_uzsugraph", $.sv.device_uzsu, {
initSelector: '[data-widget="device.uzsugraph"]',
options: {
valueparameterlist: null,
headline: '',
designtype: '',
valuetype: 'bool',
editable: false,
scrollbar: true
},
weekDays: {
'MO': 0,
'TU': 1,
'WE': 2,
'TH': 3,
'FR': 4,
'SA': 5,
'SU': 6
},
rruleDays: {},
_startTimestamp: 4*1000*60*60*24 + new Date(0).getTimezoneOffset()*1000*60, //1.1.1970 was a Thursday -> 4 days until Monday
_create: function() {
this._super(); //call _create method of prototype widget sv.device_uzsu
var self = this;
// init data (used if no update follows because item does not exist yet)
this._uzsudata = { active : true, list : [] }
var valueParameterList = this.options.valueparameterlist.explode();
if(valueParameterList.length === 0){
if(this.options.valuetype === 'bool') valueParameterList = ['1', '0', '1'];
else
valueParameterList = [];
}
var min = null, max = null, step = 1;
if(this.options.valuetype === 'bool') {
min = parseFloat(valueParameterList[1].split(':')[1] !== undefined ? valueParameterList[1].split(':')[1] : valueParameterList[1]);
max = parseFloat(valueParameterList[0].split(':')[1] !== undefined ? valueParameterList[0].split(':')[1] : valueParameterList[0]);
step = 1;
}
if(this.options.valuetype === 'num' && valueParameterList.length != 0) {
min = parseFloat(valueParameterList[0]);
max = parseFloat(valueParameterList[1]);
step = parseFloat(valueParameterList[2]) || 1;
}
var timeStep = 1000*60*5; // round to 5 minutes
// Highcharts symbols for sunrise and sunset
Highcharts.SVGRenderer.prototype.symbols.sunrise = function (x, y, w, h) {
var wt = w/300, ht = h/300;
var xt = x-20*wt, yt = y-20*ht-120*ht;
return [
'M',xt+ 79*wt,yt+267*ht,'h', -4*wt, 'c',-3*wt,0 ,-5*wt, 2*ht,-5*wt, 5*ht, 'c',0,3*ht,2*wt,5*ht,5*wt,5*ht,'h', 4*wt, 'c', 3*wt, 0 , 5*wt, -2*ht, 5*wt, -5*ht, 'c',0,-3*ht,-2*wt,-5*ht,-5*wt,-5*ht,'z',
'M',xt+232*wt,yt+267*ht,'h', -48*wt, 'c',-7*wt,0 ,-7*wt,10*ht, 0 ,10*ht, 'h', 48*wt, 'c', 7*wt, 0 , 7*wt,-10*ht, 0 ,-10*ht, 'z',
'M',xt+ 72*wt,yt+217*ht,'l', 18*wt,19*ht,'c', 5*wt,5*ht,12*wt,-3*ht, 8*wt,-7*ht, 'l',-19*wt,-19*ht,'c',-5*wt,-4*ht,-12*wt, 3*ht, -7*wt, 7*ht, 'z',
'M',xt+127*wt,yt+172*ht,'v', 49*ht, 'c', 0 ,6*ht,10*wt, 6*ht,10*wt, 0*ht, 'v',-49*ht, 'c', 0 ,-7*ht,-10*wt, -7*ht,-10*wt, 0 , 'z',
'M',xt+201*wt,yt+194*ht,'l', -34*wt,35*ht,'c',-5*wt,4*ht, 2*wt,12*ht, 7*wt, 7*ht, 'l', 34*wt,-35*ht,'c', 5*wt,-4*ht, -2*wt,-11*ht, -7*wt, -7*ht, 'z',
'M',xt+195*wt,yt+239*ht,'l', -14*wt, 5*ht,'c',-2*wt,1*ht,-4*wt, 4*ht,-3*wt, 7*ht,0 , 2*ht, 3*wt, 4*ht, 6*wt, 3*ht,'l', 14*wt, -6*ht,'c', 2*wt,-1*ht, 4*wt, -3*ht, 3*wt, -6*ht, 0 ,-2*ht,-3*wt,-4*ht,-6*wt,-3*ht,'z',
'M',xt+154*wt,yt+206*ht,'l', -6*wt,14*ht,'c',-1*wt,3*ht, 1*wt, 6*ht, 4*wt, 6*ht,2*wt, 1*ht, 5*wt,-1*ht, 6*wt,-3*ht,'l', 5*wt,-14*ht,'c', 1*wt,-2*ht, -1*wt, -5*ht, -3*wt, -6*ht,-3*wt,-1*ht,-5*wt, 1*ht,-6*wt, 3*ht,'z',
'M',xt+101*wt,yt+209*ht,'l', 6*wt,14*ht,'c', 1*wt,2*ht, 3*wt, 4*ht, 6*wt, 3*ht,3*wt, 0 , 5*wt,-3*ht, 4*wt,-6*ht,'l', -6*wt,-14*ht,'c',-1*wt,-2*ht, -3*wt, -4*ht, -6*wt, -3*ht, 's',-5*wt,4*ht,-4*wt,6*ht,'z',
'M',xt+286*wt,yt+281*ht,'H',xt+75*wt, 'c',-7*wt,0 ,-7*wt,10*ht, 0 ,10*ht, 'h',211*wt, 'c', 7*wt, 0 , 7*wt,-10*ht, 0*wt,-10*ht, 'z',
'M',xt+ 74*wt,yt+252*ht,'l', 8*wt, 4*ht,'c', 2*wt,1*ht, 5*wt,-1*ht, 6*wt,-4*ht,0 ,-3*ht,-1*wt,-5*ht,-4*wt,-6*ht,'l', -8*wt, -3*ht,'c',-2*wt,-1*ht, -5*wt, 1*ht, -6*wt, 3*ht,-1*wt, 3*ht, 1*wt, 5*ht, 4*wt, 6*ht,'z',
'M',xt+ 94*wt,yt+280*ht,'v', -29*ht, 'c', 0 ,-11*ht,9*wt,-20*ht,20*wt,-20*ht, 'h', 35*wt, 'c',11*wt, 0 , 20*wt, 9*ht, 20*wt, 20*ht, 'v',29*ht
];
};
Highcharts.SVGRenderer.prototype.symbols.sunset = function (x, y, w, h) {
var wt = w/300, ht = h/300;
var xt = x-20*wt+w*1.1, yt = y-20*ht-120*ht;
return [
'M',xt+ wt-(79*wt),yt+267*ht,'h', wt-(-4*wt), 'c',wt-(-3*wt),0 ,wt-(-5*wt), 2*ht,wt-(-5*wt), 5*ht, 'c',0,3*ht,wt-(2*wt),5*ht,wt-(5*wt),5*ht,'h', wt-(4*wt), 'c', wt-(3*wt), 0 , wt-(5*wt), -2*ht, wt-(5*wt), -5*ht, 'c',0,-3*ht,wt-(-2*wt),-5*ht,wt-(-5*wt),-5*ht,'z',
'M',xt+wt-(232*wt),yt+267*ht,'h', wt-(-48*wt), 'c',wt-(-7*wt),0 ,wt-(-7*wt),10*ht, 0 ,10*ht, 'h', wt-(48*wt), 'c', wt-(7*wt), 0 , wt-(7*wt),-10*ht, 0 ,-10*ht, 'z',
'M',xt+ wt-(72*wt),yt+217*ht,'l', wt-(18*wt),19*ht,'c', wt-(5*wt),5*ht,wt-(12*wt),-3*ht, wt-(8*wt),-7*ht, 'l',wt-(-19*wt),-19*ht,'c',wt-(-5*wt),-4*ht,wt-(-12*wt), 3*ht, wt-(-7*wt), 7*ht, 'z',
'M',xt+wt-(127*wt),yt+172*ht,'v', (49*ht), 'c', 0 ,6*ht,wt-(10*wt), 6*ht,wt-(10*wt), 0*ht, 'v',(-49*ht), 'c', 0 ,-7*ht,wt-(-10*wt), -7*ht,wt-(-10*wt), 0 , 'z',
'M',xt+wt-(201*wt),yt+194*ht,'l', wt-(-34*wt),35*ht,'c',wt-(-5*wt),4*ht, wt-(2*wt),12*ht, wt-(7*wt), 7*ht, 'l', wt-(34*wt),-35*ht,'c', wt-(5*wt),-4*ht, wt-(-2*wt),-11*ht, wt-(-7*wt), -7*ht, 'z',
'M',xt+wt-(195*wt),yt+239*ht,'l', wt-(-14*wt), 5*ht,'c',wt-(-2*wt),1*ht,wt-(-4*wt), 4*ht,wt-(-3*wt), 7*ht,0 , 2*ht, wt-(3*wt), 4*ht, wt-(6*wt), 3*ht,'l', wt-(14*wt), -6*ht,'c', wt-(2*wt),-1*ht, wt-(4*wt), -3*ht, wt-(3*wt), -6*ht, 0 ,-2*ht,wt-(-3*wt),-4*ht,wt-(-6*wt),-3*ht,'z',
'M',xt+wt-(154*wt),yt+206*ht,'l', wt-(-6*wt),14*ht,'c',wt-(-1*wt),3*ht, wt-(1*wt), 6*ht, wt-(4*wt), 6*ht,wt-(2*wt), 1*ht, wt-(5*wt),-1*ht, wt-(6*wt),-3*ht,'l', wt-(5*wt),-14*ht,'c', wt-(1*wt),-2*ht, wt-(-1*wt), -5*ht, wt-(-3*wt), -6*ht,wt-(-3*wt),-1*ht,wt-(-5*wt), 1*ht,wt-(-6*wt), 3*ht,'z',
'M',xt+wt-(101*wt),yt+209*ht,'l', wt-(6*wt),14*ht,'c', wt-(1*wt),2*ht, wt-(3*wt), 4*ht, wt-(6*wt), 3*ht,wt-(3*wt), 0 , wt-(5*wt),-3*ht, wt-(4*wt),-6*ht,'l', wt-(-6*wt),-14*ht,'c',wt-(-1*wt),-2*ht, wt-(-3*wt), -4*ht, wt-(-6*wt), -3*ht, 's',wt-(-5*wt),4*ht,wt-(-4*wt),6*ht,'z',
'M',xt+wt-(286*wt),yt+281*ht,'H',xt+wt-(75*wt), 'c',wt-(-7*wt),0 ,wt-(-7*wt),10*ht, 0 ,10*ht, 'h',wt-(211*wt), 'c', wt-(7*wt), 0 , wt-(7*wt),-10*ht, wt-(0*wt),-10*ht, 'z',
'M',xt+ wt-(74*wt),yt+252*ht,'l', wt-(8*wt), 4*ht,'c', wt-(2*wt),1*ht, wt-(5*wt),-1*ht, wt-(6*wt),-4*ht,0 ,-3*ht,wt-(-1*wt),-5*ht,wt-(-4*wt),-6*ht,'l', wt-(-8*wt), -3*ht,'c',wt-(-2*wt),-1*ht, wt-(-5*wt), 1*ht, wt-(-6*wt), 3*ht,wt-(-1*wt), 3*ht, wt-(1*wt), 5*ht, wt-(4*wt), 6*ht,'z',
'M',xt+ wt-(94*wt),yt+280*ht,'v', (-29*ht), 'c', 0 ,-11*ht,wt-(9*wt),-20*ht,wt-(20*wt),-20*ht, 'h', wt-(35*wt), 'c',wt-(11*wt), 0 , wt-(20*wt), 9*ht, wt-(20*wt), 20*ht, 'v',29*ht
];
};
if (Highcharts.VMLRenderer) {
Highcharts.VMLRenderer.prototype.symbols.sunrise = Highcharts.SVGRenderer.prototype.symbols.sunrise;
Highcharts.VMLRenderer.prototype.symbols.sunset = Highcharts.SVGRenderer.prototype.symbols.sunset;
}
// prepare the plot
var chart = this.element.highcharts({
title: { text: this.options.headline },
legend: false,
//time: {timezoneOffset: new Date().getTimezoneOffset()}, //to do: uncomment with next Highcharts version
series: [
{ // active
name: 'active',
id: 'active',
zIndex: 9,
className: 'uzsu-active',
},
{ // inactive
name: 'inactive',
id: 'inactive',
zIndex: 8,
className: 'uzsu-inactive',
lineWidth: 0,
type: 'scatter'
},
{ // sun min/max
id: 'range',
zIndex: 2,
className: 'uzsu-minmax',
type: 'scatter',
lineWidth: 2,
draggableY: false,
tooltip: {
headerFormat: '',
footerFormat: '<span style="font-size: 10px">{point.key}</span><br/>',
pointFormatter: function() { return '<span style="font-size: 10px">'+this.series.chart.time.dateFormat('%a, %H:%M', this.x)+'</span><br/>'; },
},
point: {
events: {
drop: function (e) {
self.justDragged = true; // used to prevent click event after drop
var time = self.element.highcharts().time.dateFormat('%H:%M', e.target.x);
if(e.target.className == 'uzsu-min')
e.target.uzsuEntry.timeMin = time;
else if(e.target.className == 'uzsu-max')
e.target.uzsuEntry.timeMax = time;
self._save();
},
click: null
}
},
},
{ // sunrise & sunset
name: 'sun',
id: 'sun',
zIndex: 1,
type: 'scatter',
tooltip: {
headerFormat: '',
footerFormat: '<span style="font-size: 10px">{point.key}</span><br/>',
pointFormatter: function() { return '<span style="font-size: 10px">'+this.series.chart.time.dateFormat('%a, %H:%M', this.x)+'</span><br/>'; },
},
marker: {
radius: 16
},
draggableY: false,
point: {
events: {
drop: function (e) {
self.sunTimes[e.target.uzsuEvent] = self.element.highcharts().time.dateFormat('%H:%M', e.target.x);
self._delay(function() { self.draw() }, 10); // redraw has to be deferred otherwise highcharts draggable plugin throws an exception
},
click: null
}
}
}
],
xAxis: {
type: 'datetime',
ordinal:false,
min: this._startTimestamp,
max: 1000*60*60*24 + this._startTimestamp,
showLastLabel: false,
crosshair: { snap: false },
dateTimeLabelFormats: { day: '%a' }
},
yAxis: {
title: false,
endOnTick: false,
startOnTick: false,
alignTicks: true,
crosshair: { snap: false },
minTickInterval: 1,
tickInterval: this.options.valuetype === 'bool' ? 1 : null,
min: min,
max: max,
type: this.options.valuetype === 'bool' ? 'category' : 'linear',
categories: this.options.valuetype === 'bool' ? [ valueParameterList[1].split(':')[0], valueParameterList[0].split(':')[0] ] : null
},
tooltip: {
xDateFormat: '%a, %H:%M',
headerFormat: '<span style="font-size: 10px;">{point.key}</span><br/>',
pointFormatter: function() {
var value = (this.series.yAxis.categories) ? this.series.yAxis.categories[this.y] : this.y;
return '<span class="highcharts-strong">' + value + '</span> (' + this.series.name + ')<br/>';
}
},
navigator: {
enabled: true,
min: this._startTimestamp,
max: 1000*60*60*24*7 + this._startTimestamp,
},
rangeSelector: {
enabled: false,
},
scrollbar: {
enabled: this.options.scrollbar,
showFull: false
},
chart: {
styledMode: true,
marginBottom: 0,
marginTop: self.options.headline == '' ? 45 : null,
exporting: {
enabled: false
},
events: {
click: function(e) { // add point
if(self.justDragged) { // prevent click event after drop
self.justDragged = false;
return;
}
if (self.element.find('.uzsu-active-toggler').hasClass('hidden'))
return;
// find timestamp of first point and round it to minimal time step
var firstX = Math.round((e.xAxis[0].value - e.xAxis[0].axis.min) % (1000*60*60*24) / (timeStep)) * timeStep + e.xAxis[0].axis.min;
// round and limit value
var yValue = e.yAxis[0].value;
if(min != undefined && yValue < min)
yValue = min;
else if(max != undefined && yValue > max)
yValue = max;
yValue = Math.round((yValue - min || 0) / step) * step + min || 0;
var uzsuEntry = { active: true, once: false, event: 'time', timeCron: self.element.highcharts().time.dateFormat('%H:%M', firstX), value: yValue };
self._uzsuRuntimePopup(uzsuEntry);
}
}
},
plotOptions: {
series: {
dragDrop : {
draggableX: this.options.editable,
dragPrecisionX: timeStep,
draggableY: this.options.editable,
dragPrecisionY: step,
dragMinY: min,
dragMaxY: max
},
cursor: this.options.editable ? 'move' : null,
marker: { enabled: true },
dataGrouping: { enabled: false },
stickyTracking: false,
findNearestPointBy: 'xy',
type: 'scatter',
lineWidth: 2,
point: {
events: {
click: function (e) {
if(self.justDragged) { // prevent click event after drop
self.justDragged = false;
return;
}
if(e.point.uzsuEntry !== undefined)
self._uzsuRuntimePopup(e.point.uzsuEntry)
},
drag: function (e) {
},
drop: function (e) {
self.justDragged = true; // used to prevent click event after drop
if(e.target.uzsuEntry !== undefined) {
e.target.uzsuEntry.value = e.target.y;
if(e.target.uzsuEntry.event == 'time')
e.target.uzsuEntry.timeCron = self.element.highcharts().time.dateFormat('%H:%M', e.target.x);
else // sunrise or sunset
e.target.uzsuEntry.timeOffset = Math.round(((e.target.x % (1000*60*60*24)) - (self._getSunTime(e.target.uzsuEntry.event, 0) % (1000*60*60*24)))/1000/60);
self._save();
}
}
}
}
}
},
},
function (chart) {
// crosshair tooltip
self.element.mousemove(function (e) {
if (!chart.lab) {
chart.lab = chart.renderer.text('', 0, 0)
.attr({ zIndex: 10 })
.addClass('highcharts-axis-labels')
.add();
}
e = chart.pointer.normalize(e);
var position = {
x: chart.xAxis[0].toValue(e.chartX),
y: chart.yAxis[0].toValue(e.chartY)
};
position.x = Math.round((position.x - self._startTimestamp) % (1000*60*60*24) / (timeStep)) * timeStep + self._startTimestamp;
if(min != undefined && position.y < min)
position.y = min;
else if(max != undefined && position.y > max)
position.y = max;
position.y = Math.round((position.y - min || 0) / step) * step + min || 0;
if(chart.yAxis[0].categories)
position.y = chart.yAxis[0].categories[position.y];
chart.lab.attr({
x: e.chartX + 5,
y: e.chartY - 22,
text: self.element.highcharts().time.dateFormat('%H:%M', position.x) + '<br>' + position.y
});
});
self.element.mouseout(function () {
if (chart && chart.lab) {
chart.lab.destroy();
chart.lab = null;
}
});
// active/inactive button
chart.renderer.button(String.fromCharCode(160)+String.fromCharCode(10004)+String.fromCharCode(160), 40, null, function(e) {
self._uzsudata.active = !self._uzsudata.active;
self._save();
this.attr('title', !self._uzsudata.active ? sv_lang.uzsu.inactive : self._uzsudata.once ? sv_lang.uzsu.once : sv_lang.uzsu.active);
}, {} , {}, {}, {}, 'callout')
.attr({
align: 'right',
title: !self._uzsudata.active ? sv_lang.uzsu.inactive : self._uzsudata.once ? sv_lang.uzsu.once : sv_lang.uzsu.active
})
.addClass('highcharts-color-0 uzsu-active-toggler')
.add()
if (self.options.designtype == 0){
// Interpolation buttons
self.interpolationButtons = [
{ interpolationType: 'none', shape: 'square', langKey: 'nointerpolation' },
{ interpolationType: 'cubic', shape: 'circle', langKey: 'cubic' },
{ interpolationType: 'linear', shape: 'triangle', langKey: 'linear' },
];
$.each(self.interpolationButtons, function(i, button) {
button.element = chart.renderer.button('', null, null, function(e) { self._uzsudata.interpolation.type = button.interpolationType; self._save(); }, {} , {}, {}, {}, button.shape)
.attr({
align: 'right',
title: sv_lang.uzsu[button.langKey],
"data-interpolation-type": button.interpolationType
})
.addClass('icon0 interpolation-button')
.css({'fill': 'transparent'})
.add()
.align({
align: 'right',
x: -16-(self.interpolationButtons.length-i-1)*20,
y: 10
}, false, null);
});
chart.renderer.text(sv_lang.uzsu.interpolation+': ', null, null)
.attr({
align: 'right'}
)
.add(
chart.renderer.createElement('g').addClass('highcharts-label').add()
)
.align({
align: 'right',
x: -16-self.interpolationButtons.length*20,
y: 22
}, false, null);
};
// Zoom buttons
self.customZoomButtons = [
{ day: 'mo', shape: 'square', xMin: 0, xMax: 24*60*60*1000 },
{ day: 'tu', shape: 'square', xMin: 24*60*60*1000, xMax: 2*24*60*60*1000 },
{ day: 'we', shape: 'square', xMin: 2*24*60*60*1000, xMax: 3*24*60*60*1000 },
{ day: 'th', shape: 'square', xMin: 3*24*60*60*1000, xMax: 4*24*60*60*1000 },
{ day: 'fr', shape: 'square', xMin: 4*24*60*60*1000, xMax: 5*24*60*60*1000 },
{ day: 'sa', shape: 'square', xMin: 5*24*60*60*1000, xMax: 6*24*60*60*1000 },
{ day: 'su', shape: 'square', xMin: 6*24*60*60*1000, xMax: 7*24*60*60*1000 },
{ day: 'w', shape: 'circle', xMin: 0, xMax: 7 *24*60*60*1000, langKey: 'week' },
];
chart.text = [];
$.each(self.customZoomButtons, function(i, button) {
chart.text[i] = chart.renderer.text(sv_lang.uzsu[button.day], null, null)
.attr({ align: 'right'} )
.add( chart.renderer.createElement('g').addClass('highcharts-label').add() )
.align({
align: 'left',
x: 118 + (i-1)*20,
y: 22
}, false, null);
button.element = chart.renderer.button('', null, null, function(e) {
chart.xAxis[0].setExtremes();
chart.xAxis[0].update({min: self._startTimestamp + button.xMin, max: self._startTimestamp + button.xMax}, false);
chart.redraw();
}, {}, {}, {}, {}, button.shape)
.attr({
align: 'right',
title: sv_lang.uzsu[button.day],
})
.addClass('icon0 zoom-button')
.css({'fill': 'transparent'})
.add()
.align({
align: 'left',
x: 120 + (i-1)*20,
y: 10
}, false, null);
});
});
},
_update: function(response) {
this._super(response);
if(this._uzsuParseAndCheckResponse(this._uzsudata))
this.draw();
},
draw: function() {
var chart = this.element.highcharts();
var interpolation = this.hasInterpolation ? this._uzsudata.interpolation : {type: 'none'};
if(this._uzsudata.active && !this._uzsudata.once){
this.element.removeClass('uzsu-all-inactive');
this.element.removeClass('uzsu-general-once');
this.element.find('.uzsu-active-toggler>text').text(String.fromCharCode(160)+ String.fromCharCode(10004) + String.fromCharCode(160));
}
else {
this.element.addClass('uzsu-all-inactive');
if (!this._uzsudata.active) {
this.element.removeClass('uzsu-general-once');
this.element.find('.uzsu-active-toggler>text').text(String.fromCharCode(160)+ String.fromCharCode(10004) + String.fromCharCode(160));
}
else {
this.element.addClass('uzsu-general-once');
this.element.find('.uzsu-active-toggler>text').text(String.fromCharCode(160)+'1 x' + String.fromCharCode(160));
}
}
this.element.find('.uzsu-active-toggler').attr('title', !this._uzsudata.active ? sv_lang.uzsu.inactive : this._uzsudata.once ? sv_lang.uzsu.once : sv_lang.uzsu.active)
.end().find('title').text(!this._uzsudata.active ? sv_lang.uzsu.inactive : this._uzsudata.once ? sv_lang.uzsu.once : sv_lang.uzsu.active);
var hasDays = false;
var hasSunrise = false;
var hasSunset = false;
var hasBurst = false;
var seriesData = { active: [], inactive: [], range: [] };
var linetype = interpolation.type == 'cubic' ? 'spline' : 'line';
var self = this;
// ******************************************************************************
// set start timestamp for the graph and texts for weekdays in zoom buttons -
// graph starts with current day
//
// weekdays: Object {'Mo': 0, ..., 'Su': 6}
// rruleDays: Object {'Mo': days from now til Monday, ..., 'Su': days from now til Sunday}
// days: Array ['Mi', 'Th', 'Sa'] active days from rrule
// rruleDaySequence: Array [2, 3, 0] Sequence of days in rrule - above example read on a Saturday
// "once" is executed on the day with the lowest number in the sequence
// ******************************************************************************
var today = new Date().getDay(); // delivers SU = 0
this._startTimestamp = (4 + today - 1) % 7 *1000*60*60*24 + new Date(0).getTimezoneOffset()*1000*60;
for (var day in this.weekDays) {
this.rruleDays[day] = this.weekDays[day] - (6 + today) % 7;
if (this.rruleDays[day] < 0)
this.rruleDays[day] += 7;
this.customZoomButtons[this.rruleDays[day]].day = day.toLowerCase();
}
$.each(this.customZoomButtons, function(idx, button) {
chart.text[idx].attr({ text: sv_lang.uzsu[button.day] });
});
// ******************************************************************************
// highlight active interpolation button
// ******************************************************************************
$.each(this.interpolationButtons, function(idx, button) {
if(button.interpolationType == self._uzsudata.interpolation.type)
button.element.addClass("icon1");
else
button.element.removeClass("icon1");
});
// ******************************************************************************
// create timeline with all events from UZSU data
// ******************************************************************************
$.each(this._uzsudata.list, function(responseEntryIdx, responseEntry) {
var x, xMin, xMax, xSeriesEndMin, xSeriesEndMax;
hasBurst = responseEntry.hasOwnProperty('series');
hasSunrise = hasSunrise || responseEntry.event == 'sunrise';
hasSunset = hasSunset ||responseEntry.event == 'sunset';
if(!hasBurst){
x = self._calculateEventTime(responseEntry);
if(responseEntry.timeMin)
xMin = self._timeToTimestamp(responseEntry.timeMin);
if(responseEntry.timeMax)
xMax = self._timeToTimestamp(responseEntry.timeMax);
}
else {
x = self._calculateEventTime(responseEntry.series.start);
if(responseEntry.series.start.timeMin)
xMin = self._timeToTimestamp(responseEntry.series.start.timeMin);
if(responseEntry.series.start.timeMax)
xMax = self._timeToTimestamp(responseEntry.series.start.timeMax);
// determine uzsu series times
var seriesEnd;
var seriesIntervall = new Date('1970-01-01T' + responseEntry.series.timeSeriesIntervall + ':00Z').getTime();
if (responseEntry.series.hasOwnProperty('timeSeriesCount')) // series end defined by count of cycles
seriesEnd = x + (parseInt(responseEntry.series.end.timeCron) - 1) * seriesIntervall;
else
seriesEnd = self._calculateEventTime(responseEntry.series.end);
if(responseEntry.series.end.timeMin)
xSeriesEndMin = self._timeToTimestamp(responseEntry.series.end.timeMin);
if(responseEntry.series.end.timeMax)
xSeriesEndMax = self._timeToTimestamp(responseEntry.series.end.timeMax);
}
var rrule = responseEntry.rrule;
if (!rrule)
rrule = 'FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR,SA,SU';
var ind = rrule.indexOf('BYDAY=');
// if RRULE BYDAY is included
if (ind > 0) {
var days = rrule.substring(ind+6).split(',');
// memorize whether at least one data point is not repeating every day so the initial display will be a week graph
if (days.length < 7 || responseEntry.once)
hasDays = true;
// get sequence of the days as an array to find out which is the next event to be executed if "once" is true
var rruleDaySequence = days.map(function(d){return self.rruleDays[d] == 0 && (hasBurst ? seriesEnd : x) < self._timeToTimestamp(new Date().transUnit('H:m')) ? self.rruleDays[d]+7 : self.rruleDays[d]});
// push all events into the timeline starting from Monday. Sorting will be done later
$.each(days, function(dayIdx, day) {
var rruleOffset = self.rruleDays[day]*1000*60*60*24;
var sunOffset = 0;
var isActive = (responseEntry.activeToday && self.rruleDays[day] == 0) || (responseEntry.active && !(responseEntry.once && self.rruleDays[day] != Math.min.apply(null, rruleDaySequence)));
//DEBUG: if (responseEntry.once) console.log(responseEntry.activeToday, responseEntry.active, self.rruleDays[day], rruleDaySequence, isActive)
if (responseEntry.event.indexOf('sun') >= 0 )
sunOffset = self._getSunTime(responseEntry.event, self.rruleDays[day]) - self._getSunTime(responseEntry.event, 0);
else if (hasBurst && responseEntry.series.start.event.indexOf('sun') >= 0 )
sunOffset = self._getSunTime(responseEntry.series.start.event, self.rruleDays[day]) - self._getSunTime(responseEntry.series.start.event, 0);
sunOffset = (x + sunOffset < xMin ? xMin - x : sunOffset);
sunOffset = (x + sunOffset > xMax ? xMax - x : sunOffset);
var xRecurring = x + rruleOffset + sunOffset;
var yValue = Number(responseEntry.value);
seriesData[isActive ? 'active' : 'inactive'].push({ x: xRecurring, y: yValue, className: 'uzsu-'+responseEntryIdx+' uzsu-event-'+responseEntry.event, entryIndex: responseEntryIdx, uzsuEntry: responseEntry });
if (hasBurst){
var sunSeriesEndOffset = 0;
if (responseEntry.series.end.event.indexOf('sun') >= 0 )
sunSeriesEndOffset = self._getSunTime(responseEntry.series.end.event, self.rruleDays[day]) - self._getSunTime(responseEntry.series.end.event, 0);
sunSeriesEndOffset = (seriesEnd + sunSeriesEndOffset < xSeriesEndMin ? xSeriesEndMin - seriesEnd : sunSeriesEndOffset);
sunSeriesEndOffset = (seriesEnd + sunSeriesEndOffset > xSeriesEndMax ? xSeriesEndMax - seriesEnd : sunSeriesEndOffset);
xBurstRecurring = xRecurring;
while (xBurstRecurring + seriesIntervall <= seriesEnd + rruleOffset + sunSeriesEndOffset) {
xBurstRecurring += seriesIntervall;
seriesData[isActive ? 'active' : 'inactive'].push({ x: xBurstRecurring, y: yValue, className: 'uzsu-'+responseEntryIdx+' uzsu-event-'+responseEntry.event, entryIndex: responseEntryIdx, uzsuEntry: responseEntry });
}
}
if(!hasBurst && (xMin !== undefined || xMax !== undefined)) {
if(xMin !== undefined)
seriesData.range.push({ x: xMin+rruleOffset, y: yValue, name: sv_lang.uzsu.earliest, uzsuEntry: responseEntry, className: 'uzsu-min' });
else
seriesData.range.push({ x: xRecurring, y: yValue, uzsuEntry: responseEntry, className: 'uzsu-min uzsu-hidden', marker: { enabled: false } });
if(xMax !== undefined)
seriesData.range.push({ x: xMax+rruleOffset, y: yValue, name: sv_lang.uzsu.latest, uzsuEntry: responseEntry, className: 'uzsu-max' });
else
seriesData.range.push({ x: xRecurring, y: yValue, uzsuEntry: responseEntry, className: 'uzsu-max uzsu-hidden', marker: { enabled: false } });
seriesData.range.push({ x: xMax+rruleOffset+1, y: null, uzsuEntry: responseEntry });
}
});
}
});
// push additional points for daywise interpolation
if (interpolation.hasOwnProperty('perday') && interpolation.perday == true) {
for (var i = 1; i < 8; i++) {
seriesData.active.push({x: this._startTimestamp + i* 24*60*60*1000 - 10, y: -999});
seriesData.active.push({x: this._startTimestamp + i* 24*60*60*1000 - 20, y: -999});
seriesData.active.push({x: this._startTimestamp + i* 24*60*60*1000 - 30, y: -999});
if (i != 7)
seriesData.active.push({x: this._startTimestamp + i* 24*60*60*1000 - 40, y: -999});
}
}
var navigatorMin = this._startTimestamp
var navigatorMax = navigatorMin + 7*24*60*60*1000;
// ******************************************************************************
// set plot data for active points
// ******************************************************************************
var data = seriesData.active;
data.sort(function(a,b) { return a.x - b.x });
// add graph points at the ends (not clickable since entryIndex and uzsuEntry are missing)
// 2 points at start in order to improve fitting of spline to UZSU interpolation
if(data.length > 0) {
if (interpolation.hasOwnProperty('perday') && interpolation.perday == true) {
var dayEnd
for (var i = 1; i< 8; i++){
dayEnd = data.findIndex(function(element){return element.y == -999});
data[dayEnd].x = data[dayEnd-1].x + 10;
data[dayEnd].y = null;
data[dayEnd+1].x = data[dayEnd-1].x + 20;
data[dayEnd+1].y = data[dayEnd-1].y;
data[dayEnd+2].x = data[dayEnd+4] ? data[dayEnd+4].x - 20 : navigatorMax;
data[dayEnd+2].y = data[dayEnd-1].y;
if (i != 7){
data[dayEnd+3].x = data[dayEnd+4].x - 10;
data[dayEnd+3].y = null;
}
}
data.unshift({x: data[0].x - 10, y: null });
data.unshift({x: data[0].x - 20, y: data[data.length - 1].y});
data.unshift({x: navigatorMin, y: data[data.length - 1].y});
} else {
navigatorMin = data[data.length-1].x-1000*60*60*24*7;
data.unshift({ x: navigatorMin, y: data[data.length-1].y, className: data[data.length-1].className });
data.unshift({ x: data[data.length-2].x-1000*60*60*24*7, y: data[data.length-2].y, className: data[data.length-2].className });
navigatorMax = data[2].x+1000*60*60*24*7;
data.push({ x: navigatorMax, y: data[2].y, className: data[2].className });
// 2nd point at end would cause highcharts to break the plot area boundaries during dragging -> omit this for now
// and wait for the solution for https://github.com/highcharts/highcharts/issues/20351
// data.push({ x: data[3].x+1000*60*60*24*7, y: data[3].y, className: data[3].className });
}
}
chart.get('active').setData(data, false, null, false);
chart.get('active').update({
type: interpolation.type == 'cubic' ? 'spline' : 'line',
step: interpolation.type != 'cubic' && interpolation.type != 'linear' ? 'left' : false,
}, false);
// ******************************************************************************
// set plot data for inactive points
// ******************************************************************************
data = seriesData.inactive;
data.sort(function(a,b) { return a.x - b.x });
chart.get('inactive').setData(data, false, null, false);
// ******************************************************************************
// set plot data for min/max times on sun events and the sun times themselves
// ******************************************************************************
chart.get('range').setData(seriesData.range, false, null, false);
plotLines = [];
sunData = [];
if(hasSunrise || hasSunset) {
for(dayIdx = 0; dayIdx < 7; dayIdx++) {
if(hasSunrise) {
plotLines.push({
value: self._getSunTime('sunrise', dayIdx)+dayIdx*1000*60*60*24,
className: 'uzsu-event-sunrise',
label: { text: sv_lang.uzsu.sunrise }
});
sunData.push({ x: self._getSunTime('sunrise', dayIdx)+dayIdx*1000*60*60*24, y: chart.yAxis[0].min, name: sv_lang.uzsu.sunrise, className: 'uzsu-event-sunrise', uzsuEvent: 'sunrise', marker: { symbol: 'sunrise' } });
}
if(hasSunset) {
plotLines.push({
value: self._getSunTime('sunset', dayIdx)+dayIdx*1000*60*60*24,
className: 'uzsu-event-sunset',
label: { text: sv_lang.uzsu.sunset }
});
sunData.push({ x: self._getSunTime('sunset', dayIdx)+dayIdx*1000*60*60*24, y: chart.yAxis[0].min, name: sv_lang.uzsu.sunset, className: 'uzsu-event-sunset', uzsuEvent: 'sunset', marker: { symbol: 'sunset' } })
}
}
}
chart.get('sun').setData(sunData, false);
// ******************************************************************************
// update the plot boundaries and draw the plot
// ******************************************************************************
chart.xAxis[0].update({
min: this._startTimestamp,
max: 1000*60*60*24 * (hasDays ? 7 : 1) + this._startTimestamp,
// floor: this._startTimestamp,
plotLines: plotLines
}, false);
chart.update({
navigator: {
xAxis: {
min: navigatorMin,
max: navigatorMax,
}
}
});
chart.xAxis[0].setExtremes(this._startTimestamp, 1000*60*60*24 * (hasDays ? 7 : 1) + this._startTimestamp)
chart.redraw();
self._plotNowLine();
},
_save: function() {
this._uzsuCollapseTimestring(this._uzsudata);
this._write(this._uzsudata);
// disable editing until dict update has been received
this.element.widget('disable');
var self = this;
$('[data-widget="device.uzsuicon"][data-item="'+ this.options.item + '"]').addClass('blink');
$('[data-widget*="device.uzsu"][data-item="'+ this.options.item + '"] .uzsu-active-toggler').hide();
setTimeout(function(){self.element.widget('enable');}, 15000); // fallback in case update is not coming
},
_timeToTimestamp: function(time) {
return new Date('1970-01-01T' + time + 'Z').getTime() + this._startTimestamp;
},
_getSunTime: function(event, dayIndex) {
if(!this.sunTimes){
this.sunTimes = { 'sunrise': [], 'sunset': [] };
for (var day in this.rruleDays){
if (this._uzsudata.hasOwnProperty('SunCalculated')){
this.sunTimes.sunrise[this.rruleDays[day]] = this._uzsudata.SunCalculated.sunrise[day];
this.sunTimes.sunset[this.rruleDays[day]] = this._uzsudata.SunCalculated.sunset[day];
} else {
this.sunTimes.sunrise[this.rruleDays[day]] = (this._uzsudata.sunrise == undefined) ? '06:00' : this._uzsudata.sunrise;
this.sunTimes.sunset[this.rruleDays[day]] = (this._uzsudata.sunset == undefined) ? '19:30' : this._uzsudata.sunset;
}
}
}
return this._timeToTimestamp(this.sunTimes[event][dayIndex]);
},
_calculateEventTime: function(timeEvent){
var result;
if(timeEvent.event == 'time')
result = this._timeToTimestamp(timeEvent.timeCron);
else{
result = this._getSunTime(timeEvent.event, 0);
if(timeEvent.timeOffsetType == 'm')
result += timeEvent.timeOffset*1000*60;
else if(timeEvent.timeOffsetType == '' && timeEvent.timeOffset != '')
result = (timeEvent.calculated == undefined) ? result : this._timeToTimestamp(timeEvent.calculated);
if(timeEvent.timeMin)
result = Math.max(this._timeToTimestamp(timeEvent.timeMin), result);
if(timeEvent.timeMax)
result = Math.min(this._timeToTimestamp(timeEvent.timeMax), result);
}
return result;
},
_plotNowLine: function(id) {
if(this._nowLinePlotDelay) {
clearTimeout(this._nowLinePlotDelay);
this._nowLinePlotDelay = null;
}
var axis = this.element.highcharts().xAxis[0];
axis.removePlotLine('now');
axis.addPlotLine({
id: 'now',
value: (this._timeToTimestamp(String(new Date().getHours()).padStart(2,'0')+':'+String(new Date().getMinutes()).padStart(2,'0')) - this._startTimestamp) + this._startTimestamp,
className: 'uzsu-now highcharts-color-0',
label: 'now'
});
this._nowLinePlotDelay = this._delay(function() {
this._plotNowLine(this._nowLinePlotDelay);
}, 60000);
},
_uzsuRuntimePopup: function(responseEntry) {
var self = this;
this.popupStartDay = new Date().getDay();
// Steuerung des Popups erst einmal wird der Leeranteil angelegt
// erst den Header, dann die Zeilen, dann den Footer
var tt = this._uzsuBuildTableHeader();
tt += this._uzsuBuildTableRow(0);
tt += this._uzsuBuildTableFooter();
// dann hängen wir das an die aktuelle Seite
var uzsuPopup = $(tt).appendTo(this.element).enhanceWithin().popup().on({
popupbeforeposition: function(ev, ui) {
var maxHeight = $(window).height() - 230;
$(this).find('.uzsuTableMain').css('max-height', maxHeight + 'px').css('overflow-y','auto').css('overflow-x','hidden');
},
popupafteropen: function(ev, ui) {
$(this).popup('reposition', {y: 30})
},
popupafterclose: function(ev, ui) {
$(this).remove();
$(window).off('resize', self._onresize);
}
});
// dann speichern wir uns für cancel die ursprünglichen im DOM gespeicherten Werte in eine Variable ab
var responseCancel = jQuery.extend(true, {}, responseEntry);
// dann die Werte eintragen.
var tableRow = $('.uzsuRow').first();
this._uzsuFillTableRow(responseEntry, tableRow);
// Verhindern negativer Zahleneingabe
$('.positivenumbers').keypress(function(evt){
var charCode = (evt.which) ? evt.which : event.keyCode;
return !(charCode > 31 && (charCode < 48 || charCode > 57));
});
// Popup schliessen mit close rechts oben in der Box oder mit Cancel in der Leiste
uzsuPopup.find('#uzsuClose, #uzsuCancel').bind('click', function(e) {
// wenn keine Änderungen gemacht werden sollen (cancel), dann auch im cache die alten Werte
uzsuPopup.popup('close');
});
// speichern mit SaveQuit
uzsuPopup.find('#uzsuSaveQuit').bind('click', function(e) {
// jetzt wird die Kopie auf das Original kopiert und geschlossen
self._uzsuSaveTableRow(responseEntry, tableRow);
// add entry if it is a new one
if(self._uzsudata.list.indexOf(responseEntry) == -1)
self._uzsudata.list.push(responseEntry);
self._save();
uzsuPopup.popup('close');
});
// Löschen mit del als Callback eintragen
uzsuPopup.delegate('.uzsuDelTableRow', 'click', function(e) {
var entryIndex = self._uzsudata.list.indexOf(responseEntry);
if(entryIndex != -1) { // don't remove if it was a new entry which is not in list yet
self._uzsudata.list.splice(entryIndex, 1);
self._save();
}
uzsuPopup.popup('close');
});
// call time series mode
uzsuPopup.delegate('.uzsuCellSeries button', 'click', function(e) {
if($(this).hasClass('ui-icon-arrow-l'))
self._uzsuHideAllSeriesLines();
else
self._uzsuShowSeriesLine(e);
});
// call Expert Mode
uzsuPopup.delegate('[class*="uzsuCellExpert"] button', 'click', function(e) {
var caller = $(this).parent()[0].className;
caller = (caller.length > 14 ? caller.substring(14) : '');
if($(this).hasClass('ui-icon-arrow-u'))
self._uzsuHideAllExpertLines();
else
self._uzsuShowExpertLine(e, caller);
});
// Handler, um den expert button Status zu setzen
uzsuPopup.delegate('input[class*="expertActive"]', 'change', function (){
self._uzsuSetExpertColor($(this));
});
// Handler, um den Status anhand des Pulldowns SUN zu setzen
uzsuPopup.delegate('.uzsuRowExpert [class*="uzsuEvent"] select, input[class*="uzsuSunActive"]', 'change', function (){
var searchClass = $(this)[0].className;
if ($(this)[0].nodeName == "SELECT")
searchClass = $(this).parents()[2].className;
var searchPos = searchClass.lastIndexOf('series');
var caller = (searchPos < 0 ? '' : searchClass.substring(searchPos));
self._uzsuSetSunActiveState($(this), caller);
});
// Handler, um den Series button Status zu setzen
uzsuPopup.delegate('input.uzsuSeriesActive', 'change', function (){
self._uzsuSetSeriesColor($(this));
});
uzsuPopup.delegate('.uzsuRowSeries .uzsuEvent select, input.uzsuSeriesActive', 'change', function (){
self._uzsuSetSeriesActiveState($(this));
});
// switch input type for series end mode
uzsuPopup.delegate('.uzsuRowSeries fieldset.uzsuSeriesEndTypeInput', 'change', function (){
self._uzsuSetSeriesEndType($(this));
});
// hier wir die aktuelle Seite danach durchsucht, wo das Popup ist und im folgenden das Popup initialisiert, geöffnet und die schliessen
// Funktion daran gebunden. Diese entfernt wieder das Popup aus dem DOM Baum nach dem Schliessen mit remove
uzsuPopup.popup('open');
},
_uzsuBuildTableFooter: function() {
var tt = "";
// Zeileneinträge abschliessen und damit die uzsuTableMain
tt += "</div>";
// Aufbau des Footers
tt += "<div class='uzsuTableFooter'>" +
"<div class='uzsuRowFooter'>" +
"<div class='uzsuCell' style='float: right'>" +
"<div data-role='controlgroup' data-type='horizontal' data-inline='true' data-mini='true'>" +
"<button id='uzsuCancel'>" + sv_lang.uzsu.cancel + "</button>" +
"<button id='uzsuSaveQuit'>" + sv_lang.uzsu.ok + "</button>" +
"</div>" +
"</div>" +
"</div>" +
"</div>";
// und der Abschluss des uzsuClear als Rahmen für den float:left und des uzsuPopup divs
tt += "</div></div>";
return tt;
},
});
// *****************************************************
// Widget for UZSU-timetable
// *****************************************************
//
// Neugestaltetes UZSU Widget zur Bedienung UZSU Plugin in Form einer Tabelle
//
// Darstellung der UZSU Einträge und Darstellung des Widgets in Form einer
// Tabelle
// Umsetzung
// (C) Andre Kohler 2020
//
// license GPL [http://www.gnu.de]
//
$.widget("sv.device_uzsutable", $.sv.device_uzsu, {
initSelector: 'div[data-widget="device.uzsutable"]',
options: {
'val-on': null,
'val-off': null,
'color-on': null,
'color-off': null,
'fill': null,
'color-on-disabled': null,
'color-off-disabled': null,
'borderstyle': null,
'granularity': null,
'showtooltip': null,
'showsun': null,
'showactualtime': null,
'designtype': null,
'valuetype': 'bool',
'valueparameterlist': null,
'headline': '',
'showzoombutton': '',
'inactivestyle': 1,
'mySvgWidth': 0
},
_create: function() {
this._super();
this._DrawGrid(this.options, this.uuid)
this._addBackgroundframe()
},
_update: function(response) {
this._super(response);
this._DrawTimeTable(this.uuid, this.options, this._uzsudata)
// Function to keep Data of TimeLine actual
this._CalcTimeLine(this)
},
_events: {
'click': function(event) {
myTarget = event.target.id
if (myTarget == "") {
myTarget = event.target.closest("svg").id
}
switch (myTarget) {
case '5m': {
this.options.granularity = "5m";
this._DrawTimeTable(this.uuid, this.options, this._uzsudata);
break
}
case '10m': {
this.options.granularity = "10m";
this._DrawTimeTable(this.uuid, this.options, this._uzsudata);
break
}
case '15m': {
this.options.granularity = "15m";
this._DrawTimeTable(this.uuid, this.options, this._uzsudata);
break
}
case '30m': {
this.options.granularity = "30m";
this._DrawTimeTable(this.uuid, this.options, this._uzsudata);
break
}
case '1h': {
this.options.granularity = "1h";
this._DrawTimeTable(this.uuid, this.options, this._uzsudata);
break
}
case 'btnActive': {
if (event.bubbles === true) {
this._uzsudata.active = !this._uzsudata.active
this._write(this._uzsudata)
event.bubbles = false
break;
}
}
default: {
// open UZSU-Popup on Click and Show Edit possibilities
var response = jQuery.extend(true, {}, this._uzsudata);
if (this._uzsuParseAndCheckResponse(response)) {
this._uzsuRuntimePopup(response);
}
}
}
}
},
// *****************************************************
// function for drawing the time-table
// *****************************************************
_DrawTimeTable: function(TableName, options, myDict) {
myOptions = $.extend(true, [], options);
var vals_on = String(myOptions['val-on']).explode()
var vals_off = String(myOptions['val-off']).explode()
var vals_on_color = String(myOptions['color-on']).explode()
var vals_off_color = String(myOptions['color-off']).explode()
var asortValues = {}
KeyValuePair = String(myOptions.valueparameterlist).explode()
KeyValuePair.forEach(function(element) {
asortValues[element.split(":")[1]] = element.split(":")[0]
});
// Ensure same length for ON/OFF values and colors - if missing colors use last color of list
while (vals_on.length > vals_on_color.length) {
vals_on_color.push(vals_on_color[vals_on_color.length - 1])
}
while (vals_off.length > vals_off_color.length) {
vals_off_color.push(vals_off_color[vals_off_color.length - 1])
}
// Draw the empty Grid
var HeadlineHeight = this._DrawGrid(myOptions, TableName)
// Keep Instance of ME because forEach will change "this" !
myInstance = this
// create day sequence to evaluate the right execution day for one-time events
var today = new Date().getDay(); // delivers SU = 0
var rruleDays = [];
var rruleDayNames = [];
for (var day in weekDays) {
rruleDays[day] = weekDays[day] - (6 + today) % 7;
if (rruleDays[day] < 0)
rruleDays[day] += 7;
rruleDayNames[rruleDays[day]] = day;
}
// now get the entries
for (myItem in myDict.list) {
myActItem = myDict.list[myItem]
myDays = []
myTimeDict = []
try {
myDays = myActItem.rrule.split(";")[1].split("=")[1].split(",")
} catch (e) {}
// build sequence of days to make sure "once" matches first entry
var rruleDaySequence = myDays.map(function(d){return rruleDays[d]});
var isActive
//// for Series
if (myActItem.hasOwnProperty("series")) {
var seriesEnd
if (myActItem.hasOwnProperty("seriesCalculated")) {
// sun times start after "now" in seriesCalculated and if a series is running todays data are repeated at the end
// since sun times differ normally over a week there is a risk of double entries in the timetable. So we need to drop the double entries
if (myActItem.seriesCalculated[myActItem.seriesCalculated.length - 1].seriesDay == myActItem.seriesCalculated[0].seriesDay){
// DEBUG: console.log(myActItem.seriesCalculated)
myActItem.seriesCalculated[0].seriesMax = myActItem.seriesCalculated[0].seriesMin;
}
for (serie in myActItem.seriesCalculated) {
myTimeDict = myInstance._CreateSerieEntriesWithSun(myActItem)
seriesEnd = myTimeDict[myTimeDict.length -1].key;
rruleDaySequence = myDays.map(function(d){return rruleDays[d] == 0 && seriesEnd < new Date().transUnit('H:m') ? rruleDays[d] + 7 : rruleDays[d]});
d = weekDays[myActItem.seriesCalculated[serie].seriesDay]
isActive = (myActItem.activeToday && myActItem.seriesCalculated[serie].seriesDay == rruleDayNames[0]) || (myActItem.active && !(myActItem.once && myActItem.seriesCalculated[serie].seriesDay != rruleDayNames[Math.min.apply(null, rruleDaySequence)]))
//DEBUG: console.log('with sun: ', myActItem.seriesCalculated[serie].seriesDay, d, rruleDayNames, myDays, rruleDaySequence, myTimeDict, isActive)
myInstance._fillCells(TableName, myTimeDict, myActItem, d, vals_on, vals_off, vals_on_color, vals_off_color, asortValues, isActive);
}
} else {
myTimeDict = myInstance._CreateSerieEntriesWithOutSun(myActItem)
myTimeDict1 = myTimeDict[0]
myTimeDict2 = myTimeDict[1]
myDays.forEach(function(element) {
d1 = weekDays[element]
seriesEnd = myTimeDict[1].length == 0 && !myTimeDict1.length == 0 ? myTimeDict1[myTimeDict1.length-1].key : '23:59';
rruleDaySequence = myDays.map(function(d){return rruleDays[d] == 0 && seriesEnd < new Date().transUnit('H:m') ? rruleDays[d] + 7 : rruleDays[d]});
isActive = (myActItem.activeToday && element == rruleDayNames[0] ) || (myActItem.active && !(myActItem.once && element != rruleDayNames[Math.min.apply(null, rruleDaySequence)]));
//DEBUG: console.log('without sun: ', element, d1, rruleDayNames, myDays, rruleDaySequence, myTimeDict1, myTimeDict2, seriesEnd, isActive)
myInstance._fillCells(TableName, myTimeDict1, myActItem, d1, vals_on, vals_off, vals_on_color, vals_off_color, asortValues, isActive);
d1 != 6 ? d2 = d1 + 1 : d2 = 0
myInstance._fillCells(TableName, myTimeDict2, myActItem, d2, vals_on, vals_off, vals_on_color, vals_off_color, asortValues, isActive);
});
}
}
//// for Standard-Entries
else {
myDays.forEach(function(element) {
d = weekDays[element]
myTimeDict = []
if (myActItem.time.search("sunrise") == -1 && myActItem.time.search("sunset") == -1 && !(myActItem.hasOwnProperty("series"))) {
myTimeDict.push(myInstance._GetTimeEntryDict(myActItem.time))
} else if (myActItem.time.search("sunrise") != -1 || myActItem.time.search("sunset") != -1) {
if (myActItem.hasOwnProperty("calculated")) {
myTimeDict.push(myInstance._GetTimeEntryDict(myActItem.calculated))
} else {
if (myActItem.time.search("sunrise") >= 0) {
myTimeDict.push(myInstance._GetTimeEntryDict(myDict.sunrise != undefined ? myDict.sunrise : "06:00"))
}
if (myActItem.time.search("sunset") >= 0) {
myTimeDict.push(myInstance._GetTimeEntryDict(myDict.sunset != undefined ? myDict.sunset : "19:30" ))
}
}
}
rruleDaySequence = myDays.map(function(d){return rruleDays[d] == 0 && myTimeDict[myTimeDict.length-1].key < new Date().transUnit('H:m') ? rruleDays[d] + 7 : rruleDays[d]});
isActive = (myActItem.activeToday && element == rruleDayNames[0] ) || (myActItem.active && !(myActItem.once && element != rruleDayNames[Math.min.apply(null, rruleDaySequence)]));
//DEBUG: console.log(element, d, rruleDayNames, myDays, rruleDaySequence, myTimeDict, isActive)
myInstance._fillCells(TableName, myTimeDict, myActItem, d, vals_on, vals_off, vals_on_color, vals_off_color, asortValues, isActive);
})
}
}
// Create the Button to activate / deactivate the UZSU
this._CreateActiveButton(myDict.active)
this._addBackgroundframe()
// Now do all selected Options
if (this.options.showtooltip == true) {
this._ShowToolTip()
}
if (myOptions.showsun == true) {
this._showSun(HeadlineHeight, myDict)
}
if (myOptions.showactualtime == true) {
this._showActualTime(HeadlineHeight)
}
if (myOptions.fill == true) {
this._fillColors(vals_on_color, vals_off_color, TableName)
}
this._setInactiveStyle(myDict)
},
// *****************************************************
// CreateSerieEntriesWithOutSun
// *****************************************************
_GetTimeEntryDict: function(actTime) {
hours = actTime.split(":")[0];
minutes = actTime.split(":")[1];
myTime = actTime;
myTimeObj = {
key: myTime,
minutes: minutes,
hours: hours
};
return myTimeObj
},
// *****************************************************
// CreateSerieEntriesWithOutSun
// *****************************************************
_CreateSerieEntriesWithOutSun: function(myActItem) {
myTimeDict1 = []
myTimeDict2 = []
// Min-Time
hours = parseInt(myActItem.series.timeSeriesMin.split(":")[0])
minutes = parseInt(myActItem.series.timeSeriesMin.split(":")[1])
// Intervall in minutes
IntervallMinutes = parseInt(myActItem.series.timeSeriesIntervall.split(":")[0]) * 60
IntervallMinutes += parseInt(myActItem.series.timeSeriesIntervall.split(":")[1])
// Max-Time
if (myActItem.series.hasOwnProperty("timeSeriesMax")) //wvhn added
{
maxHours = parseInt(myActItem.series.timeSeriesMax.split(":")[0])
maxMinutes = parseInt(myActItem.series.timeSeriesMax.split(":")[1])
} else {
var cycles = parseInt(myActItem.series.timeSeriesCount) - 1
maxMinutes = parseInt((minutes + IntervallMinutes * cycles) % 60)
maxHours = parseInt((minutes + IntervallMinutes * cycles - maxMinutes) / 60) + hours
}
// Create new date for End
var maxTime = new Date()
maxTime.setMinutes(maxMinutes)
maxTime.setHours(maxHours)
maxTime.setSeconds(0)
// Create new date for Start
var myNewTime = new Date()
myNewTime.setMinutes(minutes)
myNewTime.setHours(hours)
myNewTime.setSeconds(0)
if (maxTime < myNewTime) // day overflow, so push max Date +1
{
maxTime.setDate(myEndTime.getDate() + 1);
}
actDay = myNewTime.getDay()
while (myNewTime <= maxTime && IntervallMinutes > 0) {
myObj = {
key: String("00" + myNewTime.getHours()).slice(-2) + ':' + String("00" + myNewTime.getMinutes()).slice(-2),
minutes: String("00" + myNewTime.getMinutes()).slice(-2),
hours: String("00" + myNewTime.getHours()).slice(-2)
}
actDay == myNewTime.getDay() ? myTimeDict1.push(myObj) : myTimeDict2.push(myObj);
myNewTime.setTime(myNewTime.getTime() + IntervallMinutes * 60000);
}
return [myTimeDict1, myTimeDict2] // myTimeDict1 = Times for selected day; myTimeDict2 = Times for day after (overflow the actual day)
},
// *****************************************************
// CreateSerieEntriesWithSun
// *****************************************************
_CreateSerieEntriesWithSun: function(myActItem) {
myTimeDict = []
// Min-Time
hours = parseInt(myActItem.seriesCalculated[serie].seriesMin.split(":")[0])
minutes = parseInt(myActItem.seriesCalculated[serie].seriesMin.split(":")[1])
myStartTime = myActItem.seriesCalculated[serie].seriesMin
// Intervall in minutes
IntervallMinutes = parseInt(myActItem.series.timeSeriesIntervall.split(":")[0]) * 60
IntervallMinutes += parseInt(myActItem.series.timeSeriesIntervall.split(":")[1])
// Max-Time
maxHours = parseInt(myActItem.seriesCalculated[serie].seriesMax.split(":")[0])
maxMinutes = parseInt(myActItem.seriesCalculated[serie].seriesMax.split(":")[1])
myEndTime = myActItem.seriesCalculated[serie].seriesMax
// Create new date for End
var maxTime = new Date()
maxTime.setMinutes(maxMinutes)
maxTime.setHours(maxHours)
maxTime.setSeconds(0)
// Create new date for Start
var myNewTime = new Date()
myNewTime.setMinutes(minutes)
myNewTime.setHours(hours)
myNewTime.setSeconds(0)
while (myNewTime <= maxTime && IntervallMinutes > 0) {
myTimeDict.push({
key: String("00" + myNewTime.getHours()).slice(-2) + ':' + String("00" + myNewTime.getMinutes()).slice(-2),
minutes: String("00" + myNewTime.getMinutes()).slice(-2),
hours: String("00" + myNewTime.getHours()).slice(-2)
});
myNewTime.setTime(myNewTime.getTime() + IntervallMinutes * 60000);
}
return myTimeDict
},
// *****************************************************
// fillColors
// *****************************************************
_fillColors: function(vals_on_color, vals_off_color, preFix) {
// Fill the times from ON to OFF
actMode = ''
actColor = ''
for (d = 0; d <= 6; d++) {
for (h = 0; h <= 23; h++) {
minutes = 0
for (m = 0; m <= myColSpan - 1; m++) {
minutes = m * nextValue
mySvg = 'svg-' + preFix +
"-" +
String("00" + d).slice(-2) +
"-" +
String("00" + h).slice(-2) +
"-" +
String("00" + minutes).slice(-2)
mySvgCell = $('#' + mySvg)[0]
if (mySvgCell.classList.contains('ON')) {
actMode = 'ON';
actColor = mySvgCell.style.getPropertyValue("fill")
}
if (mySvgCell.classList.contains('OFF')) {
actMode = 'OFF';
actColor = mySvgCell.style.getPropertyValue("fill")
}
if (actMode == 'ON') {
mySvgCell.setAttributeNS(null, 'style', "stroke:black;stroke-width:" + myBorder + "px;fill:" + actColor + "; fill-opacity:1.0; pointer-events:auto;");
}
}
}
}
// Check if 'ON' after end of the week => if yes search next 'OFF'
if (vals_on_color.includes(actColor)) {
actMode = 'ON'
for (d = 0; d <= 6; d++) {
if (actMode == 'OFF') {
break
}
for (h = 0; h <= 23; h++) {
minutes = 0
for (m = 0; m <= myColSpan - 1; m++) {
minutes = m * nextValue
mySvg = 'svg-' + preFix +
"-" +
String("00" + d).slice(-2) +
"-" +
String("00" + h).slice(-2) +
"-" +
String("00" + minutes).slice(-2)
mySvgCell = $('#' + mySvg)[0]
if (mySvgCell.classList.contains('ON')) {
actMode = 'OFF';
break
}
if (mySvgCell.classList.contains('OFF')) {
actMode = 'OFF';
break
}
if (actMode == 'ON') {
mySvgCell.setAttributeNS(null, 'style', "stroke:black;stroke-width:" + myBorder + "px;fill:" + actColor + "; fill-opacity:1.0; pointer-events:auto;");
}
}
}
}
}
},
// *****************************************************
// create svg-rect with text
// *****************************************************
_CreateSvgBox1: function(Borderwidth) {
var newSvg = document.createElementNS("http://www.w3.org/2000/svg", 'svg'); // Create a svg in SVG namespace
// set width and height
newSvg.setAttribute("width", 400 / 25);
newSvg.setAttribute("height", "12");
newRect = document.createElementNS("http://www.w3.org/2000/svg", 'rect'); // Create a rect in SVG namespace
newRect.setAttribute("width", 400 / 25);
newRect.setAttribute("height", "12");
newRect.setAttribute("style", "fill:white; stroke:black;stroke-width:" + Borderwidth + "px; fill-opacity:0.0");
newSvg.appendChild(newRect)
var newElement = document.createElementNS("http://www.w3.org/2000/svg", 'text'); // Create a text in SVG namespace
newElement.setAttribute("style", 'text-anchor:middle; font-size:0.5em;')
newElement.setAttribute("y", 9);
newElement.setAttribute("x", offset / 2);
var txt = document.createTextNode("");
newElement.appendChild(txt);
newSvg.appendChild(newElement)
return newSvg
},
// *****************************************************
// add the background-frame
// *****************************************************
_addBackgroundframe: function() {
svgBackGround = this._CreateSvgBox2('0.5', 0)
svgBackGround.setAttributeNS(null, 'x', 0);
svgBackGround.setAttributeNS(null, 'y', 40);
svgBackGround.setAttributeNS(null, 'width', this.options.mySvgWidth - 0);
svgBackGround.setAttributeNS(null, 'height', '120px');
svgBackGround.setAttributeNS(null, 'style', "stroke:black;stroke-width:" + "2.0" + "px; fill-opacity: 0.0; pointer-events:none;");
this.element.find("svg")[0].appendChild(svgBackGround)
},
// *****************************************************
// create svg-rect without text
// *****************************************************
_CreateSvgBox2: function(StrokeWidth, myOverlay) {
newRect = document.createElementNS("http://www.w3.org/2000/svg", 'rect'); // Create a rect in SVG namespace
newRect.setAttribute("width", 400 / 25 / myColSpan + myOverlay);
newRect.setAttribute("height", "12");
newRect.setAttribute("style", "stroke:black;stroke-width:" + StrokeWidth + "px;fill:yellow; fill-opacity:0.0; pointer-events:visiblePainted");
return newRect
},
// *****************************************************
// create svg-line
// *****************************************************
_CreateSvgLine: function(StrokeWidth) {
newLine = document.createElementNS("http://www.w3.org/2000/svg", 'line'); // Create// Create a line in SVG namespace
newLine.setAttribute("style", "stroke:black;stroke-width:" + StrokeWidth + "px; fill-opacity:0.0");
return newLine
},
// *****************************************************
// create svg-image
// *****************************************************
_CreateSvgImage: function(ImageUrl) {
newImage = document.createElementNS("http://www.w3.org/2000/svg", 'image'); // Create a image in SVG namespace
newImage.setAttribute("href", ImageUrl);
return newImage
},
// *****************************************************
// create svg-text
// *****************************************************
_CreateSvgText: function(Text) {
newText = document.createElementNS("http://www.w3.org/2000/svg", 'text'); // Create a text in SVG namespace
var txt = document.createTextNode(Text);
newText.appendChild(txt);
return newText
},
// *****************************************************
// DrawGrid
// *****************************************************
_DrawGrid: function(myOptions, TableName) {
var myJson = {}
var myBorderStyle = ''
var preFix = TableName
if (myOptions.borderstyle.explode().length == 1) {
myBorderStyle = myOptions.borderstyle.explode()[0]
} else {
for (key in myOptions.borderstyle.explode()) {
myJson[myOptions.borderstyle.explode()[key].split(":")[0]] = myOptions.borderstyle.explode()[key].split(":")[1]
}
if (myJson[myOptions['granularity']] != undefined) {
myBorderStyle = myJson[myOptions['granularity']]
} else {
myBorderStyle = 'solid'
}
}
switch (myBorderStyle) {
case 'hourly': {
myBorder = 0.0
overlayFill = 0.2
break;
}
case 'solid': {
myBorder = 0.2
overlayFill = 0
break;
}
case 'horizontal': {
myBorder = 0
overlayFill = 0.2
break;
}
case 'none': {
myBorder = 0
overlayFill = 0
break;
}
default: {
myBorder = 0.2
overlayFill = 0
break;
}
}
switch (myOptions['granularity']) {
case '5m': {
round = 2.5
nextValue = 5
myColSpan = 12
break;
}
case '10m': {
round = 5
nextValue = 10
myColSpan = 6
break;
}
case '15m': {
round = 7.5
nextValue = 15
myColSpan = 4
break;
}
case '30m': {
round = 15
nextValue = 30
myColSpan = 2
break;
}
case '1h': {
round = 30
nextValue = 60
myColSpan = 1
break;
}
default: {
round = 30
nextValue = 60
myColSpan = 1
break;
}
}
var myDays = [sv_lang.uzsu.mo, sv_lang.uzsu.tu, sv_lang.uzsu.we, sv_lang.uzsu.th, sv_lang.uzsu.fr, sv_lang.uzsu.sa, sv_lang.uzsu.su]
txtActive = sv_lang.uzsu.inactive
var preFix = TableName
var HeadlineHeight = 40
weekDays = {
'MO': "0",
'TU': "1",
'WE': "2",
'TH': "3",
'FR': "4",
'SA': "5",
'SU': "6"
}
d = 0
// Clear the SVG
this.element.find("svg")[0].innerHTML=""
this.options.mySvgWidth = 400
this.element.find("svg")[0].width.baseVal.value = this.options.mySvgWidth
offset = this.options.mySvgWidth / 25
// Set the viewbox
this.element.find("svg")[0].viewBox.baseVal.width = this.options.mySvgWidth + 2 // viewWidth
this.element.find("svg")[0].setAttribute('width', '100%')
// add the Headline to Svg
if (this.options.headline != '') {
var tblHeadline = document.createElementNS("http://www.w3.org/2000/svg", 'text'); // Create a text in SVG namespace
tblHeadline.setAttribute("style", 'text-anchor:middle; font-size:14px')
tblHeadline.setAttributeNS(null, 'x', this.options.mySvgWidth / 2);
tblHeadline.setAttributeNS(null, 'y', 29);
tblHeadline.setAttribute("id", "tblHeadline");
tblHeadline.setAttributeNS(null, 'width', this.options.mySvgWidth - 40);
tblHeadline.classList.add("highcharts-title")
tblHeadline.innerHTML = this.options.headline
this.element.find("svg")[0].appendChild(tblHeadline)
}
tblHeader = []
h = 0
while (h <= 23) {
tblHeader[h] = this._CreateSvgBox1("0.5")
tblHeader[h].setAttributeNS(null, 'x', offset + offset * h);
tblHeader[h].setAttributeNS(null, 'y', 0 + HeadlineHeight);
tblHeader[h].setAttribute("id", "tblHeader-" + h);
tblHeader[h].childNodes[1].classList.add("highcharts-title")
tblHeader[h].childNodes[1].innerHTML = String("00" + h).slice(-2)
this.element.find("svg")[0].appendChild(tblHeader[h])
h += 1
}
if (myOptions.showsun == true) {
// add the sun-Header to Svg
tblHeader[99] = this._CreateSvgBox1("0.5")
tblHeader[99].setAttributeNS(null, 'x', offset);
tblHeader[99].setAttributeNS(null, 'y', 12 + HeadlineHeight);
tblHeader[99].setAttributeNS(null, 'width', this.options.mySvgWidth / 25 * 24);
tblHeader[99].childNodes[0].setAttributeNS(null, 'width', this.options.mySvgWidth / 25 * 24);
tblHeader[99].setAttribute("id", "tblHeader-99");
this.element.find("svg")[0].appendChild(tblHeader[99])
}
while (d <= 6) {
tblHeader[d + 50] = this._CreateSvgBox1("0.5")
tblHeader[d + 50].setAttributeNS(null, 'x', 0);
tblHeader[d + 50].setAttributeNS(null, 'y', (12 * myOptions.showsun) + 12 + d * 12 + HeadlineHeight);
tblHeader[d + 50].setAttribute("id", "tblHeader-" + (50 + d));
tblHeader[d + 50].childNodes[1].classList.add("highcharts-title")
tblHeader[d + 50].childNodes[1].innerHTML = myDays[d]
this.element.find("svg")[0].appendChild(tblHeader[d + 50])
h = 0
while (h <= 23) {
m = 0
minutes = 0
while (m <= myColSpan - 1) {
minutes = m * nextValue
// *******************************
tblHeader[(d + 1) * h + m] = this._CreateSvgBox2(myBorder, overlayFill)
tblHeader[(d + 1) * h + m].setAttributeNS(null, 'x', offset + h * offset + m * offset / myColSpan);
tblHeader[(d + 1) * h + m].setAttributeNS(null, 'y', (12 * myOptions.showsun) + 12 + d * 12 + HeadlineHeight);
tblHeader[(d + 1) * h + m].colspan = myColSpan;
tblHeader[(d + 1) * h + m].setAttribute("id", 'svg-' + preFix +
"-" +
String("00" + d).slice(-2) +
"-" +
String("00" + h).slice(-2) +
"-" +
String("00" + minutes).slice(-2));
this.element.find("svg")[0].appendChild(tblHeader[(d + 1) * h + m])
// *********************************
m++
}
h += 1
}
d += 1
}
// Set Lines if horizontal
if (myBorderStyle == 'horizontal' || myBorderStyle == 'hourly') {
myLines = []
l = 0
while (l <= 6) {
myLines[l] = this._CreateSvgLine('0.5')
myLines[l].setAttributeNS(null, 'x1', offset);
myLines[l].setAttributeNS(null, 'x2', offset * 25);
myLines[l].setAttributeNS(null, 'y1', (12 * myOptions.showsun) + 12 + 12 + l * 12 + HeadlineHeight);
myLines[l].setAttributeNS(null, 'y2', (12 * myOptions.showsun) + 12 + 12 + l * 12 + HeadlineHeight);
this.element.find("svg")[0].appendChild(myLines[l])
l++
}
}
// Set Lines if 1h
if (myBorderStyle == 'hourly') {
myVertLines = []
l = 1
while (l <= 23) {
myVertLines[l] = this._CreateSvgLine('0.5')
myVertLines[l].setAttributeNS(null, 'x1', offset * l + offset);
myVertLines[l].setAttributeNS(null, 'x2', offset * l + offset);
myVertLines[l].setAttributeNS(null, 'y1', (12 * myOptions.showsun) + 12 + HeadlineHeight);
myVertLines[l].setAttributeNS(null, 'y2', (12 * myOptions.showsun) + 12 + 12 + 72 + HeadlineHeight);
this.element.find("svg")[0].appendChild(myVertLines[l])
l++
}
}
return HeadlineHeight
},
// *****************************************************
// setInactiveStyle
// *****************************************************
_setInactiveStyle: function (myDict) {
// Set the different modes for inactive UZSU
// Set the opacity based on active or not active
if (this.options.inactivestyle & 1) {
myDict.active == false ? this.element.find("svg")[0].style.opacity = 0.5 : this.element.find("svg")[0].style.opacity = 1.0
}
if (myDict.active == false && this.options.inactivestyle & 2) {
myInActiveText = this._CreateSvgText(txtActive)
myInActiveText.setAttributeNS(null, 'y', '40px')
myInActiveText.classList.add("highcharts-title")
myInActiveText.setAttributeNS(null, 'style', 'transform: rotate(20deg); font-weight:bold; fill:lightgrey; font-size:3em; stroke-width:0.5; stroke:black; font-family:Monospace; text-anchor:middle')
myInActiveText.setAttributeNS(null, 'x', this.options.mySvgWidth / 2)
myInActiveText.setAttributeNS(null, 'id', 'txtInactive')
this.element.find("svg")[0].appendChild(myInActiveText)
}
},
// *****************************************************
// fillCells
// *****************************************************
_fillCells: function(preFix, myTimeDict, myActItem, d, vals_on, vals_off, vals_on_color, vals_off_color, asortValues, isActive) {
for (entry in myTimeDict) {
myTime = myTimeDict[entry].key
hours = myTimeDict[entry].hours
minutes = myTimeDict[entry].minutes
round_up_down = -1
myOptions['val-on'] == myActItem.value ? round_up_down = -1 : round_up_down = -1
m = (Math.round((parseInt(minutes) + (round * round_up_down)) / nextValue) * nextValue) % 60;
h = (m === 0 && minutes > round) ? (hours === 23 ? 0 : hours) : hours;
//DEBUG: console.log("gerundete Zeit :" + String("00"+h).slice(-2) + ':'+String("00"+m).slice(-2))
mySvg = 'svg-' + preFix +
"-" +
String("00" + d).slice(-2) +
"-" +
String("00" + h).slice(-2) +
"-" +
String("00" + m).slice(-2)
mySvgCell = $('#' + mySvg)[0]
myValue = ''
if (isActive) {
if (vals_on.includes(myActItem.value)) {
if (asortValues.hasOwnProperty(myActItem.value)) {
myValue = asortValues[myActItem.value]
} else {
myValue = 'ON'
}
myColor = vals_on_color[vals_on.indexOf(myActItem.value)]
// Now the SVG
try {
mySvgCell.setAttributeNS(null, 'style', "stroke:black;stroke-width:" + myBorder + "px;fill:" + myColor + "; fill-opacity:1.0; pointer-events:auto;");
mySvgCell.classList.add("ON")
} catch (e) {
console.log('Error on updating Cell')
}
}
if (vals_off.includes(myActItem.value)) {
if (asortValues.hasOwnProperty(myActItem.value)) {
myValue = asortValues[myActItem.value]
} else {
myValue = 'OFF'
}
myColor = vals_off_color[vals_off.indexOf(myActItem.value)]
// Now the SVG
mySvgCell.setAttributeNS(null, 'style', "stroke:black;stroke-width:" + myBorder + "px;fill:" + myColor + "; fill-opacity:1.0; pointer-events:auto;");
mySvgCell.classList.add("OFF")
}
} else {
if (vals_on.includes(myActItem.value)) {
if (asortValues.hasOwnProperty(myActItem.value)) {
myValue = asortValues[myActItem.value]
} else {
myValue = 'ON'
}
// Now the SVG
mySvgCell.setAttributeNS(null, 'style', "stroke:black;stroke-width:" + myBorder + "px;fill:" + myOptions['color-on-disabled'] + "; fill-opacity:1.0");
mySvgCell.classList.add("DISABLED_ON")
}
if (vals_off.includes(myActItem.value)) {
if (asortValues.hasOwnProperty(myActItem.value)) {
myValue = asortValues[myActItem.value]
} else {
myValue = 'OFF'
}
mySvgCell.setAttributeNS(null, 'style', "stroke:black;stroke-width:" + myBorder + "px;fill:" + myOptions['color-off-disabled'] + "; fill-opacity:1.0");
mySvgCell.classList.add("DISABLED_OFF")
}
}
if (myOptions.showtooltip == true) {
mySvgCell.ToolTip = '' + myTime + '- Value :' + myValue
}
}
},
// *****************************************************
// _ShowToolTip
// *****************************************************
_ShowToolTip: function() {
$(".ON, .OFF, .DISABLED_OFF, .DISABLED_ON ").hover(function(event) {
if (event.type == 'mouseenter') {
//DEBUG: console.log("General mouseover ON")
/** ************************* */
myObject = event.target
myWidth = this.parentNode.width.animVal.value;
offset = myWidth / 25
var newSvg = document.createElementNS("http://www.w3.org/2000/svg", 'svg'); // Create a svg in SVG namespace
// set width and height
newSvg.setAttribute("height", "12");
newRect = document.createElementNS("http://www.w3.org/2000/svg", 'rect'); // Create a rect in SVG namespace
newRect.setAttribute("height", "12");
newSvg.appendChild(newRect)
var newElement = document.createElementNS("http://www.w3.org/2000/svg", 'text'); // Create a text in SVG namespace
newElement.setAttribute("height", "12");
newElement.setAttribute("style", 'fill:white; text-anchor:middle; font-size:0.5em;')
newElement.setAttribute("y", 9);
newElement.setAttribute("x", offset / 2);
var txt = document.createTextNode("");
newElement.appendChild(txt);
newSvg.appendChild(newElement)
/** ************************* */
mySvgToolTip = newSvg
mySvgToolTip.setAttributeNS(null, 'width', '45');
mySvgToolTip.childNodes[0].setAttributeNS(null, 'width', '45');
mySvgToolTip.childNodes[1].innerHTML = this.ToolTip
mySvgToolTip.childNodes[1].setAttribute("style", 'fill:white; text-anchor:middle; font-size:0.4em;')
mySvgToolTip.childNodes[1].setAttributeNS(null, 'x', '22.5');
mySvgToolTip.childNodes[0].setAttributeNS(null, 'style', 'fill:#555; fill-opacity:1.0;');
mySvgToolTip.childNodes[0].setAttributeNS(null, 'ry', '5');
mySvgToolTip.childNodes[0].setAttributeNS(null, 'rx', '5');
xPos = myObject.x.animVal.value - 10
yPos = myObject.y.animVal.value - 10
xPos > myWidth - 70 ? xPos = myWidth - 60 : xPos = xPos
xPos < 10 ? xPos = 10 : xPos = xPos
mySvgToolTip.setAttributeNS(null, 'x', xPos);
mySvgToolTip.setAttributeNS(null, 'y', yPos);
mySvgToolTip.setAttributeNS(null, 'id', 'mySvgToolTip');
this.parentNode.appendChild(mySvgToolTip)
}
if (event.type == 'mouseleave') {
$("#mySvgToolTip").remove()
}
})
},
// *****************************************************
// showSun
// *****************************************************
_showSun: function(HeadlineHeight,myDict) {
// get sunrise and sunset
if (myDict.hasOwnProperty('SunCalculated')) {
sunrise = myDict.SunCalculated.sunrise["SU,MO,TU,WE,TH,FR,SA".split(",")[new Date().getDay()]]
sunset = myDict.SunCalculated.sunset["SU,MO,TU,WE,TH,FR,SA".split(",")[new Date().getDay()]]
}
else {
if (myDict.hasOwnProperty('sunrise')) {
sunrise = myDict.sunrise
sunset = myDict.sunset
}
else { // dummys for docu page
sunrise = "04.54"
sunset = "21:30"
}
}
// SunRise-SVG
h = sunrise.split(":")[0]
m = sunrise.split(":")[1]
mySvgSunRise = this._CreateSvgImage("lib/weather/pics/sun_up.png")
mySvgSunRise.setAttributeNS(null, 'height', '12px')
mySvgSunRise.setAttributeNS(null, 'y', 13 + HeadlineHeight)
xSunrise = (parseInt(h) * 60 + parseInt(m))
maxSpan = (24 * offset)
mySvgSunRise.setAttributeNS(null, 'id', "SunRiseImg")
modWidth = 320 * (12 / 206) / 2
mySvgSunRise.setAttributeNS(null, 'x', (offset + xSunrise * maxSpan / (24 * 60) - modWidth))
this.element.find("svg")[0].appendChild(mySvgSunRise)
myText = []
myText[0] = this._CreateSvgText(sunrise)
myText[0].classList.add("highcharts-title")
myText[0].setAttributeNS(null, 'height', '12px')
myText[0].setAttributeNS(null, 'y', 22 + HeadlineHeight)
myText[0].setAttributeNS(null, 'style', 'font-size:8px')
myText[0].setAttributeNS(null, 'x', (offset + xSunrise * maxSpan / (24 * 60) + modWidth))
this.element.find("svg")[0].appendChild(myText[0])
mySunLines = []
mySunLines[1] = this._CreateSvgLine('1.0')
mySunLines[1].setAttributeNS(null, 'x1', offset + xSunrise * maxSpan / (24 * 60));
mySunLines[1].setAttributeNS(null, 'x2', offset + xSunrise * maxSpan / (24 * 60));
mySunLines[1].setAttributeNS(null, 'y1', 22 + HeadlineHeight);
mySunLines[1].setAttributeNS(null, 'y2', 22 + 7 * 12 + HeadlineHeight);
mySunLines[1].setAttributeNS(null, 'style', 'stroke:yellow;')
this.element.find("svg")[0].appendChild(mySunLines[1])
// Sunset-SVG
h = sunset.split(":")[0]
m = sunset.split(":")[1]
mySvgSunSet = this._CreateSvgImage("lib/weather/pics/sun_down.png")
mySvgSunSet.setAttributeNS(null, 'height', '12px')
mySvgSunSet.setAttributeNS(null, 'y', 13 + HeadlineHeight)
xSunSet = (parseInt(h) * 60 + parseInt(m))
maxSpan = (24 * offset)
mySvgSunSet.setAttributeNS(null, 'id', "SunRiseImg")
modWidth = 320 * (12 / 206) / 2
mySvgSunSet.setAttributeNS(null, 'x', (offset + xSunSet * maxSpan / (24 * 60) - modWidth))
this.element.find("svg")[0].appendChild(mySvgSunSet)
myText[1] = this._CreateSvgText(sunset)
myText[1].classList.add("highcharts-title")
myText[1].setAttributeNS(null, 'height', '12px')
myText[1].setAttributeNS(null, 'y', 22 + HeadlineHeight)
myText[1].setAttributeNS(null, 'style', 'font-size:8px')
myText[1].setAttributeNS(null, 'x', (offset + xSunSet * maxSpan / (24 * 60) - 25))
this.element.find("svg")[0].appendChild(myText[1])
mySunLines[2] = this._CreateSvgLine('1.0')
mySunLines[2].setAttributeNS(null, 'x1', offset + xSunSet * maxSpan / (24 * 60));
mySunLines[2].setAttributeNS(null, 'x2', offset + xSunSet * maxSpan / (24 * 60));
mySunLines[2].setAttributeNS(null, 'y1', 22 + HeadlineHeight);
mySunLines[2].setAttributeNS(null, 'y2', 22 + 7 * 12 + HeadlineHeight);
mySunLines[2].setAttributeNS(null, 'style', 'stroke:yellow;')
this.element.find("svg")[0].appendChild(mySunLines[2])
},
// *****************************************************
// showActualTime
// *****************************************************
_showActualTime: function(HeadlineHeight) {
myDate = new Date()
myactHour = myDate.getHours()
myactMin = myDate.getMinutes()
myactTime = ('00' + myactHour).slice(-2) + ':' + ('00' + myactMin).slice(-2)
// Add the actual TimeLine to the SVG
maxSpan = (24 * offset)
actPos = maxSpan / (24 * 60) * (myactHour * 60 + myactMin)
myActTimeLine = this._CreateSvgLine('1.0')
myActTimeLine.setAttributeNS(null, 'x1', actPos + offset);
myActTimeLine.setAttributeNS(null, 'x2', actPos + offset);
myActTimeLine.setAttributeNS(null, 'y1', (12 * myOptions.showsun) + 10 + HeadlineHeight);
myActTimeLine.setAttributeNS(null, 'y2', 30 + (7 - !myOptions.showsun) * 12 + HeadlineHeight);
myActTimeLine.setAttributeNS(null, 'style', 'stroke:blue;')
myActTimeLine.setAttributeNS(null, 'id', 'actTimeLine-' + this.uuid);
this.element.find("svg")[0].appendChild(myActTimeLine)
if (myactHour < 12) {
modifier = 1
} else {
modifier = -43
}
myActTime = this._CreateSvgBox1("0.5")
myActTime.setAttributeNS(null, 'width', '35');
myActTime.childNodes[0].setAttributeNS(null, 'width', '35');
myActTime.childNodes[1].innerHTML = myactTime
myActTime.childNodes[1].setAttributeNS(null, 'x', '17.5');
myActTime.childNodes[1].setAttributeNS(null, 'style', 'fill:white;font-size: 0.5em;text-anchor: middle;');
myActTime.childNodes[0].style.fill = 'blue'
myActTime.childNodes[0].setAttributeNS(null, 'style', 'fill:blue; fill-opacity:1.0;');
myActTime.childNodes[0].setAttributeNS(null, 'ry', '5');
myActTime.childNodes[0].setAttributeNS(null, 'rx', '5');
myActTime.setAttributeNS(null, 'x', actPos + offset + modifier);
myActTime.setAttributeNS(null, 'y', 20 + (7 - !myOptions.showsun) * 12 + HeadlineHeight);
myActTime.setAttributeNS(null, 'id', 'actTime-' + this.uuid);
this.element.find("svg")[0].appendChild(myActTime)
},
// *****************************************************
// CreateActiveButton
// *****************************************************
_CreateActiveButton: function(active) {
// add Checkbox with Active/InActive
btnActive = this._CreateSvgBox1("0.5")
btnActive.setAttributeNS(null, 'x', 0);
btnActive.setAttributeNS(null, 'y', 15);
btnActive.setAttributeNS(null, "width", "15px");
btnActive.setAttributeNS(null, "height", "15px");
btnActive.setAttribute("id", "btnActive");
btnActive.setAttribute("class", "uzsu-active-toggler");
btnActive.childNodes[0].setAttributeNS(null, "x", "0px");
btnActive.childNodes[0].setAttributeNS(null, "y", "0px");
btnActive.childNodes[0].setAttributeNS(null, "rx", "1");
btnActive.childNodes[0].setAttributeNS(null, "ry", "1");
btnActive.childNodes[0].setAttributeNS(null, "width", "14.5px");
btnActive.childNodes[0].setAttributeNS(null, "height", "14px");
btnActive.childNodes[1].setAttributeNS(null, "width", "19px");
btnActive.childNodes[1].setAttributeNS(null, "height", "19px");
btnActive.childNodes[1].setAttributeNS(null, "style", 'fill:black; text-anchor:middle; font-size:9.5px;')
btnActive.childNodes[1].setAttributeNS(null, "x", "7.5");
btnActive.childNodes[1].setAttributeNS(null, "y", "11");
btnActive.setAttributeNS(null, 'id', 'btnActive');
btnActive.childNodes[0].setAttributeNS(null, 'style', ' fill-opacity:1.0;'); // fill:lightgray;
active == false ? btnActive.classList.add("icon0") : btnActive.classList.add("icon1")
active == false ? btnActive.childNodes[1].innerHTML = String("✘") : btnActive.childNodes[1].innerHTML = String("✔");
this.element.find("svg")[0].appendChild(btnActive)
},
// *****************************************************
// CalcTimeLine
// *****************************************************
_CalcTimeLine: function(myInstance) {
myDate = new Date()
// Calc the delay
var delay = (60 - myDate.getSeconds()) * 1000 + 1000;
setTimeout(function() {
offset = myInstance.options.mySvgWidth / 25
if (myactHour < 12) {
modifier = 1
} else {
modifier = -43
}
maxSpan = (24 * offset)
actPos = maxSpan / (24 * 60) * (myactHour * 60 + myactMin)
myActTimeLine = myInstance.element.find('#actTimeLine-' + myInstance.uuid)[0]
myActTime = myInstance.element.find('#actTime-' + myInstance.uuid)[0]
try {
myActTimeLine.setAttributeNS(null, 'x1', actPos + offset);
myActTimeLine.setAttributeNS(null, 'x2', actPos + offset);
myActTime.setAttributeNS(null, 'x', actPos + offset + modifier);
} catch (e) {
console.log('problem while updating TimeLine for ' + myInstance.uuid + ' Error : ' + e)
}
myDate = new Date()
myactHour = myDate.getHours()
myactMin = myDate.getMinutes()
myactTime = ('00' + myactHour).slice(-2) + ':' + ('00' + myactMin).slice(-2)
myActTime.childNodes[1].innerHTML = myactTime
TimeStamp = new Date()
//DEBUG: console.log(TimeStamp + '-' + myactTime + '- Seconds: '+ TimeStamp.getSeconds()+ ' - Offset: '+ offset +'-Instance: '+myInstance.uuid )
myInstance._CalcTimeLine(myInstance)
}, delay)
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment