diff --git a/README.md b/README.md
index dc65d7f1..404a8be5 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,8 @@ fhem-tablet-ui
========
Just another dashboard for FHEM http://fhem.de/fhem.html
+But with a clear intention: Keep it short and simple!
+
![](http://knowthelist.github.io/fhem-tablet-ui/fhem-tablet-ui-example.png)
@@ -29,28 +31,28 @@ Change the wiget container according your rooms
KUECHE
-
-
+
+
-
-
HerdLicht
-
+
+
HerdLicht
+
```
Change the widgets you have and want to see on the dashboard
```html
-
+
```
All widgets have individual parameter settings. Set following attributes according your needs.
####All widgets
-- **type** : widget type
-- **device** : FHEM device name (call FHEM's 'list' command to get names)
-- **class** : css classes for look and formatting of the widget
+- **data-type** : widget type
+- **data-device** : FHEM device name (call FHEM's 'list' command to get names)
+- **class** : css classes for look and formatting of the widget
####Switch widgets
- **data-get** : name of the reading to get from FHEM (default 'STATE')
@@ -58,7 +60,7 @@ All widgets have individual parameter settings. Set following attributes accordi
- **data-get-off** : value for OFF status to get. (default 'off')
- **data-set-on** : value for ON status to set. (default: value of data-get-on)
- **data-set-off** : value for OFF status to set. (default: value of data-get-off)
-- **data-icon** : name of the font-awesome icon.
+- **data-icon** : name of the font-awesome icon. (default: fa-lightbulb-o)
####Contact widgets
- **data-get** : name of the reading to get from FHEM (default 'STATE')
@@ -68,9 +70,12 @@ All widgets have individual parameter settings. Set following attributes accordi
####Label widgets
- **data-get** : name of the reading to get from FHEM
-- **data-fix** : keeping a specified number of decimals. (default '1')
+- **data-fix** : keeping a specified number of decimals. (default '-1' -> non-numeric)
- **data-icon** : name of the font-awesome icon.
- **data-part** : split position of the space separated value to show.
+- **data-colors** : a array of color values to affect the colour of the label according to the limit value
+- **data-limits** : a array of numeric values to affect the colour of the label
+- **data-unit** : add a unit after a numeric value. use encoded strings e.g. "%B0C%0A"
####Push widgets
- **data-set** : command to send to FHEM (set \ \)
@@ -92,7 +97,23 @@ All widgets have individual parameter settings. Set following attributes accordi
- **data-get** : name of the reading to get from FHEM (default 'STATE')
- **data-set** : command to send to FHEM (set \ \ \) (default '')
4 states are valid: 1,2,3 or 4 (1=home,2=night,3=away,4=holiday)
-
+
+####Slider widgets (currently vertical only)
+- **data-get** : name of the reading to get from FHEM (default 'STATE')
+- **data-set** : command to send to FHEM (set \ \ \) (default '')
+- **data-min** : minimal value to set (default 0)
+- **data-max** : maximal value to set (default 100)
+
+####Dimmer widgets
+- **data-get** : name of the reading to get from FHEM (default 'STATE')
+- **data-get-on** : value for ON status to get. (default 'on')
+- **data-get-off** : value for OFF status to get. (default 'off')
+- **data-set-off** : value for OFF status to set. (default: value of data-get-off)
+- **data-set** : command to send to FHEM (set \ \ \) (default '')
+- **data-icon** : name of the font-awesome icon. (default: fa-lightbulb-o)
+
+data-get-on and data-get-off accept RegEx values. e.g. data-get-on="[0-9]{1,3}|on" means set switch on if STATE is a numeric value or 'on'.
+
Select one of over 500 icons from http://fortawesome.github.io/Font-Awesome/icons. Just enter the icon name (with suffix "fa-"), all icons are available. e.g. data-icon="fa-volume-up"
To disable longpoll, set an other value then 1
@@ -110,11 +131,13 @@ Currently there are 7 types of widgets.
- **push** : e.g. up / down
- **volume** : dial to set a single value (e.g. 0-60)
- **homestatus** : selector for 4 states (1=home,2=night,3=away,4=holiday)
+- **dimmer** : toogle button with a setter for on value
+- **slider** : vertical slider to select between min/max value
By default the ui gets/sets the fhem parameter 'STATE' (not 'state').
####Thermostat
-Configure as device='...' that item which delivers temp and desired-temp as reading.
+Configure as data-device='...' that item which delivers temp and desired-temp as reading.
Default parameters are:
```
@@ -122,11 +145,11 @@ data-get="desired-temp" data-temp="measured-temp" data-set="desired-temp"
```
Therefor for HomaMatic HM-CC-RT-DN this is sufficient.
```html
-
+
```
The long format looks like this:
```html
-
+
```
The wigets will show the valve value only in case of a valid data-valve attribute.
The default for data-valve ist null. That means, a empty data-valve attribute hides the valve label for the widget.
@@ -137,10 +160,10 @@ Example for HM-WDS40-TH-I Funk-Temperatur-/Feuchtesensor innen
STATE T: 20.0 H: 61
```
```html
-
-
Temperatur
-
-
Luftfeuchte
+
+
Temperatur
+
+
Luftfeuchte
```
But the same result can reached by getting single readings:
```
@@ -148,27 +171,41 @@ humidity 58
temperature 20.1
```
```html
-
-
Temperatur
-
-
Luftfeuchte
+
+
Temperatur
+
+
Luftfeuchte
+```
+
+Example for how to influence the color of the label according to value limits
+```html
+
```
Example for how to create a widget for shutter via push: show state and set up/down
```html
-
-
Rollo
+
+
Rollo
```
####Switch
Example for how to create a widget for MILIGHT via toggle button. Usage of RegEx pattern for state request:
```html
-
```
+####Dimmer
+Example for how to create a widget for a dimmer via toggle button incl. dimmer. Usage of RegEx pattern get all values for state on:
+```html
+
+```
+
License
-------
This project is licensed under [MIT](http://www.opensource.org/licenses/mit-license.php).
diff --git a/css/fhem-tablet-ui.css b/css/fhem-tablet-ui.css
index 51c497b3..f7dda056 100755
--- a/css/fhem-tablet-ui.css
+++ b/css/fhem-tablet-ui.css
@@ -44,7 +44,7 @@ text-align: center;
margin-bottom: -5px !important;
}
- .small {
+.small {
font-size: 80%;
}
@@ -57,6 +57,12 @@ input {
visibility:hidden;
}
+.slider_vertical{
+height: 100px !important;
+max-height: 100px !important;
+margin:10px 0px 0px 27% !important;
+}
+
.jq-toast-wrap{
z-index:100;
}
diff --git a/index.html b/index.html
index 9b7b8928..c423163a 100755
--- a/index.html
+++ b/index.html
@@ -6,7 +6,7 @@
/*
* Just another dashboard for FHEM
*
- * Version: 1.2.1
+ * Version: 1.3.0
* Requires: jQuery v1.7+, font-awesome, jquery.gridster, jquery.toast
* URL: https://github.com/knowthelist/fhem-tablet-ui
*
@@ -20,32 +20,40 @@
*/
-->
-
-
-
+
+
+
+
-
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
@@ -53,112 +61,118 @@
GARTEN
-
-
Licht
-
-
Rollo
-
-
Tür
-
-
Temperatur
+
+
Licht
+
+
Rollo
+
+
Tür
+
+
Außen
+
+
Temperatur
BAD
-
+
-
-
Badradio
+
+
Badradio
SCHLAFZIMMER
-
+
-
-
Fenster
+
+
Fenster
KINDERZIMMER
-
+
-
-
Fenster
+
+
Fenster
GALERIE
-
-
Licht
+
+
Licht
KUECHE
-
-
+
+
-
-
HerdLicht
-
+
+
HerdLicht
+
HOMESTATUS
-
+
WOHNZIMMER
-
-
+
+
-
-
SchrankLicht
+
+
SchrankLicht
-
-
Terassentür
+
+
Terassentür
-
-
Temperatur
-
-
Luftfeuchte
+
+
Temperatur
+
+
Luftfeuchte
-
-
MULTIMEDIA
-
-
TV
+
+
TV
-
-
Radio
+
+
Radio
-
-
-
radio
+
+
+
radio
-
-
Volume
+
+
Volume
-
+
+ LIGHTS
+
+
Light1
+
+
Light2
+
diff --git a/js/fhem-tablet-ui.js b/js/fhem-tablet-ui.js
index 56f4eba5..ce353461 100755
--- a/js/fhem-tablet-ui.js
+++ b/js/fhem-tablet-ui.js
@@ -2,7 +2,7 @@
/**
* Just another dashboard for FHEM
*
-* Version: 1.2.3
+* Version: 1.3.0
* Requires: jQuery v1.7+, font-awesome, jquery.gridster, jquery.toast
*
* Copyright (c) 2015 Mario Stephan
@@ -12,19 +12,60 @@
var deviceStates={};
var readings = {"STATE":true};
var devices = {};
+var types = {};
var ready = true;
var reading_cntr;
+var DEBUG = false;
var doLongPoll = false;
var timer;
+var dir;
var shortpollInterval = 30 * 1000; // 30 seconds
var devs=Array();
+var plugins = {
+ modules: [],
+ addModule: function (module) {
+ this.modules.push(module);
+ },
+ load: function (name) {
+ $.ajax({
+ url: dir+'/'+name+'.js',
+ dataType: "script",
+ cache: true,
+ async: false,
+ context:{name: name},
+ success: function () {
+ DEBUG && console.log('Loaded plugin: '+this.name);
+ var module = eval(this.name);
+ plugins.addModule(module);
+ module.init();
+ },
+ });
+ },
+ update: function (dev,par) {
+ $.each(this.modules, function (index, module) {
+ //Iterate each module and run update function
+ module.update(dev,par);
+ });
+ }
+}
+
+
+
$( document ).ready(function() {
- wx = 1 * $("meta[name='widget_base_width']").attr("content");
- wy = 1 * $("meta[name='widget_base_height']").attr("content");
+ wx = parseInt( $("meta[name='widget_base_width']").attr("content") );
+ wy = parseInt( $("meta[name='widget_base_height']").attr("content") );
doLongPoll = ($("meta[name='longpoll']").attr("content") == '1');
+ DEBUG = ($("meta[name='debug']").attr("content") == '1');
+ //self path
+ dir = $('script[src$="fhem-tablet-ui.js"]').attr('src');
+ var name = dir.split('/').pop();
+ dir = dir.replace('/'+name,"");
+ DEBUG && console.log('Plugin dir: '+dir);
+
+ //init gridster
gridster = $(".gridster > ul").gridster({
widget_base_dimensions: [wx, wy],
widget_margins: [5, 5],
@@ -33,216 +74,32 @@ $( document ).ready(function() {
}
}).data('gridster');
- $('div[type=label]').each(function(index) {
- $(this).data('get', $(this).data('get') || 'STATE');
+ //make it HTML conform (remove this after migration)
+ $('div[type]').each(function() {
+ $(this).attr({
+ 'data-type' : $(this).attr('type'),
+ })
+ .removeAttr('type');
});
-
- //init widgets
- $('div[type="homestatus"]').each(function( index ) {
- var clientX=0;
- var clientY=0;
- var knob_elem = jQuery('', {
- type: 'text',
- }).data($(this).data())
- .data('curval', 10)
- .appendTo($(this));
-
- $(this).bind('mousemove', function(e) {
-
- knob_elem.data('pageX',e.pageX);
- knob_elem.data('pageY',e.pageY);
- e.preventDefault();
- });
-
- var device = $(this).attr('device');
- $(this).data('get', $(this).data('get') || 'STATE');
-
- knob_elem.knob({
- 'min': 0,
- 'max': 2 * Math.PI,
- 'step': 0.01,
- 'height':210,
- 'width':210,
- 'bgColor': $(this).data('bgcolor') || '#aaaaaa',
- 'fgColor': $(this).data('fgcolor') || '#aa6900',
- 'tkColor': $(this).data('tkcolor') || '#696969',
- 'minColor': '#2A2A2A',
- 'maxColor': '#696969',
- 'thickness': 0.4,
- 'displayInput': false,
- 'angleOffset' : 0,
- 'reading': $(this).data('set') || '',
- 'draw' : drawHomeSelector,
- 'change' : function (v) {
- startInterval();
- },
- 'release' : function (v) {
- if (ready){
- setFhemStatus(device, this.o.reading + ' ' + this.o.status);
- this.$.data('curval', v);
- }
- }
- });
- });
-
- $('div[type="volume"]').each(function( index ) {
- var knob_elem = jQuery('', {
- type: 'text',
- value: '10',
- }).appendTo($(this));
-
- var device = $(this).attr('device');
- $(this).data('get', $(this).data('get') || 'STATE');
-
- knob_elem.knob({
- 'min': $(this).data('min') || 0,
- 'max': $(this).data('max') || 70,
- 'height':150,
- 'width':150,
- //'step':.5,
- 'angleOffset': $(this).data('angleoffset') || -120,
- 'angleArc': $(this).data('anglearc') || 240,
- 'bgColor': $(this).data('bgcolor') || 'transparent',
- 'fgColor': $(this).data('fgcolor') || '#cccccc',
- 'tkColor': $(this).data('tkcolor') || '#696969',
- 'minColor': '#aa6900',
- 'maxColor': '#aa6900',
- 'thickness': .25,
- 'tickdistance': 20,
- 'cursor': 6,
- 'reading': $(this).data('set') || '',
- 'draw' : drawDial,
- 'change' : function (v) {
- startInterval();
- },
- 'release' : function (v) {
- if (ready){
- setFhemStatus(device, this.o.reading + ' ' + v);
- this.$.data('curval', v);
- }
- }
- });
-
+ $('div[device]').each(function() {
+ $(this).attr({
+ 'data-device' : $(this).attr('device'),
+ })
+ .removeAttr('device');
});
+ //end **** (remove this after migration)
- $('div[type="thermostat"]').each(function( index ) {
- var knob_elem = jQuery('', {
- type: 'text',
- value: '10',
- }).appendTo($(this));
-
- var device = $(this).attr('device');
- //default reading parameter name
- $(this).data('get', $(this).data('get') || 'desired-temp');
- $(this).data('temp', $(this).data('temp') || 'measured-temp');
-
- knob_elem.knob({
- 'min':10,
- 'max':30,
- 'height':100,
- 'width':100,
- //'step':.5,
- 'angleOffset': $(this).data('angleoffset') || -120,
- 'angleArc': $(this).data('anglearc') || 240,
- 'bgColor': $(this).data('bgcolor') || 'transparent',
- 'fgColor': $(this).data('fgcolor') || '#cccccc',
- 'tkColor': $(this).data('tkcolor') || '#696969',
- 'minColor': '#4477ff',
- 'maxColor': '#ff0000',
- 'thickness': .25,
- 'cursor': 6,
- 'reading': $(this).data('cmd') || 'desired-temp',
- 'draw' : drawDial,
- 'change' : function (v) {
- //reset poll timer to avoid jump back
- startInterval();
- },
- 'release' : function (v) {
- if (ready){
- setFhemStatus(device, this.o.reading + ' ' + v);
- $.toast('Set '+ device + ' ' + this.o.reading + ' ' + v );
- this.$.data('curval', v);
- }
- }
- });
-
-
+ //collect required widgets types
+ $('div[data-type]').each(function(index){
+ var t = $(this).data("type");
+ if(!types[t])
+ types[t] = true;
});
-
- $('div[type="switch"]').each(function(index) {
-
- var device = $(this).attr('device');
- $(this).data('get', $(this).data('get') || 'STATE');
- $(this).data('get-on', $(this).attr('data-get-on') || $(this).attr('data-on') || 'on');
- $(this).data('get-off', $(this).attr('data-get-off') || $(this).attr('data-off') || 'off');
- $(this).data('set-on', $(this).attr('data-set-on') || $(this).data('get-on'));
- $(this).data('set-off', $(this).attr('data-set-off') || $(this).data('get-off'));
- var elem = $(this).famultibutton({
- icon: 'fa-lightbulb-o',
- backgroundIcon: 'fa-circle',
- offColor: '#2A2A2A',
- onColor: '#2A2A2A',
-
- // Called in toggle on state.
- toggleOn: function( ) {
- setFhemStatus(device,$(this).data('set-on'));
- },
- toggleOff: function( ) {
- setFhemStatus(device,$(this).data('set-off'));
- },
- });
- elem.data('famultibutton',elem);
-
- });
- $('div[type="push"]').each(function(index) {
-
- var device = $(this).attr('device');
- var elem = $(this).famultibutton({
- backgroundIcon: 'fa-circle-thin',
- offColor: '#505050',
- onColor: '#aa6900',
- mode: 'push',
-
- // Called in toggle on state.
- toggleOn: function( ) {
- setFhemStatus(device,$(this).data('set'));
- },
- });
- elem.data('famultibutton',elem);
- });
-
- $('div[type="contact"]').each(function(index) {
-
- var elem = $(this).famultibutton({
- icon: 'fa-windows',
- backgroundIcon: null,
- onColor: '#aa6900',
- onBackgroundColor: '#aa6900',
- offColor: '#505050',
- offBackgroundColor: '#505050',
- mode: 'signal', //toggle, push, ,
- });
- elem.data('famultibutton',elem);
- //default reading parameter name
- $(this).data('get', $(this).data('get') || 'STATE');
- $(this).data('get-on', $(this).attr('data-get-on') || $(this).attr('data-on') || 'open');
- $(this).data('get-off', $(this).attr('data-get-off') || $(this).attr('data-off') || 'closed');
-
- });
-
- $("*").focus(function(){
- $(this).blur();
- });
-
- $('input').css({visibility:'visible'});
-
- //collect required devices
- $('div[device]').each(function(index){
- var device = $(this).attr("device");
- if(!devices[device])
- devices[device] = true; devs.push(device);
- });
+ //init widgets
+ for (var widget_type in types) {
+ plugins.load('widget_'+widget_type);
+ }
//collect required readings
$('[data-get]').each(function(index){
@@ -250,15 +107,12 @@ $( document ).ready(function() {
if(!readings[reading])
readings[reading] = true;
});
- $('[data-temp]').each(function(index){
- var reading = $(this).data("temp");
- if(!readings[reading])
- readings[reading] = true;
- });
- $('[data-valve]').each(function(index){
- var reading = $(this).data("valve");
- if(!readings[reading])
- readings[reading] = true;
+
+ //collect required devices
+ $('div[data-device]').each(function(index){
+ var device = $(this).data("device");
+ if(!devices[device])
+ devices[device] = true; devs.push(device);
});
//get current values of readings
@@ -273,6 +127,10 @@ $( document ).ready(function() {
}, 1000);
shortpollInterval = 15 * 60 * 1000; // 15 minutes
}
+
+ $("*").focus(function(){
+ $(this).blur();
+ });
// refresh every x secs
startInterval();
@@ -289,105 +147,16 @@ function startInterval() {
}, shortpollInterval);
}
-function update(filter) {
- ready = false;
- var deviceElements;
- var deviceType;
-
- if ( filter == '*' )
- deviceElements= $('div[device]');
- else
- deviceElements= $('div[device="'+filter+'"]');
-
- deviceElements.each(function(index) {
-
- deviceType = $(this).attr('type');
-
- if (deviceType == 'label'){
-
- var value = getDeviceValue( $(this), 'get' );
- if (value){
- var part = $(this).data('part') || -1;
- var unit = ($(this).data('unit')) ? unescape($(this).data('unit')) : '';
- var fix = $(this).data('fix');
- fix = ( $.isNumeric(fix) ) ? fix : 1;
- var val = getPart(value,part);
- val = ( $.isNumeric(val) ) ? Number(val).toFixed(fix) : val;
- $(this).html( val + ""
- +unit+"" );
- }
- }
- else if (deviceType == 'thermostat'){
-
- var clima = getClimaValues( $(this) );
- if ( clima.desired && clima.temp ){
- var knob_elem = $(this).find('input');
-
- if ( clima.desired > 0 && knob_elem.val() != clima.desired ){
- knob_elem.val( clima.desired ).trigger('change');
- }
- if ( clima.temp > 0 && knob_elem.data('curval') != clima.temp ){
- knob_elem.trigger(
- 'configure', { "isValue": clima.temp, "valveValue": clima.valve }
- );
- knob_elem.data('curval', clima.temp);
- }
- }
- }
- else if (deviceType == 'volume'){
-
- var val = getDeviceValue( $(this), 'get' );
- if (val){
- var knob_elem = $(this).find('input');
- if ( knob_elem.val() != val )
- knob_elem.val( val ).trigger('change');
- }
- }
- else if (deviceType == 'homestatus'){
-
- var value = getDeviceValue( $(this), 'get' );
- if (value && value > -1){
- var knob_elem = $(this).find('input');
- var val=0;
- switch( value ) {
- case '3':
- val=Math.PI;
- break;
- case '4':
- val=Math.PI*0.25;
- break;
- case '2':
- val=Math.PI*1.75;
- break;
- default:
- val=0;
- }
- if ( knob_elem.data('curval') != val )
- knob_elem.val( val ).trigger('change');
- }
- }
- else if (deviceType == 'switch' || deviceType == 'contact'){
-
- var state = getDeviceValue( $(this), 'get' );
- if (state) {
- if ( state == $(this).data('get-on') )
- $(this).data('famultibutton').setOn();
- else if ( state == $(this).data('get-off') )
- $(this).data('famultibutton').setOff();
- else if ( state.match(RegExp('^' + $(this).data('get-on') + '$')) )
- $(this).data('famultibutton').setOn();
- else if ( state.match(RegExp('^' + $(this).data('get-off') + '$')) )
- $(this).data('famultibutton').setOff();
- }
- }
- });
+function update(dev,par) {
+ ready = false;
+ plugins.update(dev,par);
ready = true;
- console.log('update done (filter:'+filter+')');
+ DEBUG && console.log('update done for device:'+dev+' parameter:'+par);
}
function setFhemStatus(device,status) {
startInterval();
- console.log("set "+device+" "+status);
+ DEBUG && console.log("set "+device+" "+status);
$.ajax({
async: true,
url: $("meta[name='fhemweb_url']").attr("content") || "../fhem/",
@@ -417,7 +186,7 @@ function longPoll(roomName) {
- no separat node for parameter name
- multiple nodes with the same data (2xdate)
*/
- console.log('start longpoll');
+ DEBUG && console.log('start longpoll');
if (xhr)
xhr.abort();
@@ -483,9 +252,8 @@ function longPoll(roomName) {
};
params[paraname]=value;
deviceStates[key]=params;
- update(key);
-
- //console.log(date + ' / ' + key+' / '+paraname+' / '+val);
+ DEBUG && console.log(date + ' / ' + key+' / '+paraname+' / '+val);
+ update(key,paraname);
}
//console.log(date + ' / ' + key+' / '+paraname+' / '+val);
}
@@ -541,7 +309,7 @@ function requestFhem(paraname) {
}
reading_cntr--;
if ( reading_cntr < 1 ) {
- update('*');
+ update('*','*');
reading_cntr = Object.keys(readings).length;
}
});
@@ -549,12 +317,12 @@ function requestFhem(paraname) {
}
this.getPart = function (s,p) {
- var c = (typeof s != "undefined") ? s.split(" ") : '';
+ var c = (s && typeof s != "undefined") ? s.split(" ") : '';
return (c.length >= p && p>0 ) ? c[p-1] : s;
};
this.getDeviceValue = function (device, src) {
- var devname = device.attr('device');
+ var devname = device.data('device');
var paraname = (src && src != '') ? device.data(src) : Object.keys(readings)[0];
if (devname && devname.length>0){
var params = deviceStates[devname];
@@ -563,236 +331,3 @@ this.getDeviceValue = function (device, src) {
return null;
}
-this.getClimaValues = function (device) {
-
- var state = getDeviceValue( device, '');
- var desi = getDeviceValue( device, 'get');
- return {
- temp: getDeviceValue( device, 'temp'),
- desired: ( state && state.indexOf('set_') < 0 ) ? desi : getPart(state,2),
- valve: getDeviceValue( device, 'valve')
- };
-};
-
-this.getGradientColor = function(start_color, end_color, percent) {
- // strip the leading # if it's there
- start_color = start_color.replace(/^\s*#|\s*$/g, '');
- end_color = end_color.replace(/^\s*#|\s*$/g, '');
-
- // convert 3 char codes --> 6, e.g. `E0F` --> `EE00FF`
- if(start_color.length == 3){
- start_color = start_color.replace(/(.)/g, '$1$1');
- }
-
- if(end_color.length == 3){
- end_color = end_color.replace(/(.)/g, '$1$1');
- }
-
- // get colors
- var start_red = parseInt(start_color.substr(0, 2), 16),
- start_green = parseInt(start_color.substr(2, 2), 16),
- start_blue = parseInt(start_color.substr(4, 2), 16);
-
- var end_red = parseInt(end_color.substr(0, 2), 16),
- end_green = parseInt(end_color.substr(2, 2), 16),
- end_blue = parseInt(end_color.substr(4, 2), 16);
-
- // calculate new color
- var diff_red = end_red - start_red;
- var diff_green = end_green - start_green;
- var diff_blue = end_blue - start_blue;
-
- diff_red = ( (diff_red * percent) + start_red ).toString(16).split('.')[0];
- diff_green = ( (diff_green * percent) + start_green ).toString(16).split('.')[0];
- diff_blue = ( (diff_blue * percent) + start_blue ).toString(16).split('.')[0];
-
- // ensure 2 digits by color
- if( diff_red.length == 1 )
- diff_red = '0' + diff_red
-
- if( diff_green.length == 1 )
- diff_green = '0' + diff_green
-
- if( diff_blue.length == 1 )
- diff_blue = '0' + diff_blue
-
- return '#' + diff_red + diff_green + diff_blue;
- };
-
-var drawDial = function () {
- var c = this.g, // context
- a = this.arc(this.cv), // Arc
- pa, // Previous arc
- r = 1;
-
- c.lineWidth = this.lineWidth;
- c.lineCap = this.lineCap;
- if (this.o.bgColor !== "none") {
- c.beginPath();
- c.strokeStyle = this.o.bgColor;
- c.arc(this.xy, this.xy, this.radius, this.endAngle - 0.00001, this.startAngle + 0.00001, true);
- c.stroke();
- }
-
- var tick_w = (2 * Math.PI) / 360;
- var step = (this.o.max - this.o.min) / this.angleArc;
- var acAngle = ((this.o.isValue - this.o.min) / step) + this.startAngle;
- var dist = this.o.tickdistance || 4;
- var mincolor = this.o.minColor || '#ff0000';
- var maxcolor = this.o.maxColor || '#4477ff';
-
- // draw ticks
- for (tick = this.startAngle; tick < this.endAngle + 0.00001; tick+=tick_w*dist) {
- i = step * (tick-this.startAngle)+this.o.min;
-
- c.beginPath();
-
- if ((tick > acAngle && tick < a.s) || (tick-tick_w*4 <= acAngle && tick+tick_w*4 >= a.s)){
- // draw diff range in gradient color
- c.strokeStyle = getGradientColor(maxcolor, mincolor, (this.endAngle-tick)/this.angleArc);
- }
- else {
- // draw normal ticks
- c.strokeStyle = this.o.tkColor;//'#4477ff';
- }
-
- // thicker lines every 5 ticks
- if ( Math.round(i*10)/10 % 5 == 0 ){
- w = tick_w*2;
- w *= (c.strokeStyle != this.o.tkColor) ? 1.5 : 1;
- }
- else {
- w = tick_w;
- w *= (c.strokeStyle != this.o.tkColor) ? 2 : 1;
- }
- // thicker lines every at current value
- if (acAngle > tick-tick_w && acAngle < tick+tick_w)
- w *= 1.9;
-
- c.arc( this.xy, this.xy, this.radius, tick, tick+w , false);
- c.stroke();
- }
-
- // draw target temp cursor
- c.beginPath();
- this.o.fgColor= getGradientColor(maxcolor, mincolor, (this.endAngle-a.e)/(this.endAngle-this.startAngle));
- c.strokeStyle = r ? this.o.fgColor : this.fgColor;
- c.lineWidth = this.lineWidth * 2;
- c.arc(this.xy, this.xy, this.radius-this.lineWidth/2, a.s, a.e, a.d);
- c.stroke();
-
- //draw current value as text
- var x = this.radius*0.7*Math.cos(acAngle);
- var y = this.radius*0.7*Math.sin(acAngle);
- c.fillStyle = this.o.tkColor;
- c.font="10px sans-serif";
- c.fillText(this.o.isValue ,this.xy+x-5,this.xy+y+5);
-
- //draw valve value as text
- if ( this.o.valveValue ) {
- var x = -5;
- var y = this.radius*0.55;
- c.fillStyle = this.o.tkColor;
- c.font="10px sans-serif";
- c.fillText(this.o.valveValue+'%',this.xy+x,this.xy+y+5);
- }
- return false;
-};
-
-var drawHomeSelector = function (event) {
- var sector=0;
- var c = this.g; // context
- var x=this.$.data('pageX');
- var y=this.$.data('pageY');
- var mx=this.x+this.w2;
- var my=this.y+this.w2;
- var r=this.radius*0.4;
-
- //Assign sector 1 for center pressed or set value 0
- if ( Math.pow((mx-x),2) + Math.pow((my-y),2) < Math.pow(r,2)
- || this.cv == 0 )
- sector=1;
-
- if (sector==1){
- // inner circle
- c.lineWidth = this.radius*0.4;
- c.strokeStyle = this.o.fgColor ;
- c.beginPath();
- c.arc( this.xy, this.xy, this.radius*0.2, 0, 2 * Math.PI);
- c.stroke();
- }
- else{
- // outer section
- var start=0;
- var end = 0;
-
- if (this.cv > Math.PI*0.5 && this.cv <= Math.PI*1.5){
- start=0; end=Math.PI; sector=3;
- }
- else if (this.cv > Math.PI*1.5 && this.cv <= Math.PI*2){
- start=Math.PI; end=Math.PI*1.5; sector=2;
- }
- else if (this.cv > 0 && this.cv <= Math.PI*0.5){
- start=Math.PI*1.5; end=Math.PI*2; sector=4;
- }
-
- c.lineWidth = this.radius*0.6;
- c.beginPath();
- c.strokeStyle = this.o.fgColor;
- c.arc(this.xy, this.xy, this.radius*0.7, start, end);
- c.stroke();
- }
-
- // sections
- c.strokeStyle = this.o.tkColor;
- c.lineWidth = this.radius*0.6;
- c.beginPath();
- c.arc(this.xy, this.xy, this.radius*0.7, 0, 0.02);
- c.stroke();
- c.beginPath();
- c.arc(this.xy, this.xy, this.radius*0.7, Math.PI -0.02, Math.PI);
- c.stroke();
- c.beginPath();
- c.arc(this.xy, this.xy, this.radius*0.7, 1.5 * Math.PI-0.02, 1.5 * Math.PI);
- c.stroke();
-
- // inner circle line
- c.lineWidth = 2;
- c.strokeStyle = this.o.tkColor;
- c.beginPath();
- c.arc( this.xy, this.xy, this.radius*0.4, 0, 2 * Math.PI);
- c.stroke();
-
- // outer circle line
- c.lineWidth = 2;
- c.beginPath();
- c.arc( this.xy, this.xy, this.radius, 0, 2 * Math.PI, false);
- c.stroke();
-
- c.fillStyle = (sector==1)?this.o.minColor:this.o.maxColor;
- c.font = "100 11px sans-serif";
- c.fillText("Home", this.xy-14, this.xy+15);
- c.font = "22px FontAwesome";
- c.fillText("\uf015", this.xy-12, this.xy+2);
-
- c.fillStyle = (sector==2)?this.o.minColor:this.o.maxColor;
- c.font = "22px FontAwesome";
- c.fillText("\uf236", this.xy-this.radius*0.7, this.xy-this.radius*0.4);
- c.font = "100 11px sans-serif";
- c.fillText("Night", this.xy-this.radius*0.9, this.xy-10);
-
- c.fillStyle = (sector==3)?this.o.minColor:this.o.maxColor;
- c.font = "22px FontAwesome";
- c.fillText("\uf1b9", this.xy-12, this.xy+this.radius*0.67);
- c.font = "100 11px sans-serif";
- c.fillText("Away", this.xy-12, this.xy+this.radius*0.65+15);
-
- c.fillStyle = (sector==4)?this.o.minColor:this.o.maxColor;
- c.font = "22px FontAwesome";
- c.fillText("\uf0f2", this.xy+this.radius*0.4, this.xy-this.radius*0.4);
- c.font = "100 11px sans-serif";
- c.fillText("Holiday", this.xy+this.radius*0.42, this.xy-10);
-
- this.o.status = sector;
- return false;
-};
diff --git a/js/widget_contact.js b/js/widget_contact.js
new file mode 100755
index 00000000..fc622e41
--- /dev/null
+++ b/js/widget_contact.js
@@ -0,0 +1,51 @@
+var widget_contact = {
+ _contact: null,
+ elements: null,
+ init: function () {
+ _contact=this;
+ _contact.elements = $('div[data-type="contact"]');
+ _contact.elements.each(function(index) {
+
+ var elem = $(this).famultibutton({
+ icon: 'fa-windows',
+ backgroundIcon: null,
+ onColor: '#aa6900',
+ onBackgroundColor: '#aa6900',
+ offColor: '#505050',
+ offBackgroundColor: '#505050',
+ mode: 'signal', //toggle, push, ,
+ });
+ elem.data('famultibutton',elem);
+ //default reading parameter name
+ $(this).data('get', $(this).data('get') || 'STATE');
+ $(this).data('get-on', $(this).attr('data-get-on') || $(this).attr('data-on') || 'open');
+ $(this).data('get-off', $(this).attr('data-get-off') || $(this).attr('data-off') || 'closed');
+
+ });
+ },
+ update: function (dev,par) {
+
+ var deviceElements;
+ if ( dev == '*' )
+ deviceElements= _contact.elements;
+ else
+ deviceElements= _contact.elements.filter('div[data-device="'+dev+'"]');
+
+ deviceElements.each(function(index,elem) {
+ if ( $(this).data('get')==par || par =='*'){
+ var state = getDeviceValue( $(this), 'get' );
+ if (state) {
+ if ( state == $(this).data('get-on') )
+ $(this).data('famultibutton').setOn();
+ else if ( state == $(this).data('get-off') )
+ $(this).data('famultibutton').setOff();
+ else if ( state.match(RegExp('^' + $(this).data('get-on') + '$')) )
+ $(this).data('famultibutton').setOn();
+ else if ( state.match(RegExp('^' + $(this).data('get-off') + '$')) )
+ $(this).data('famultibutton').setOff();
+ }
+ }
+ });
+ }
+
+};
\ No newline at end of file
diff --git a/js/widget_dimmer.js b/js/widget_dimmer.js
new file mode 100755
index 00000000..d347af02
--- /dev/null
+++ b/js/widget_dimmer.js
@@ -0,0 +1,69 @@
+var widget_dimmer = {
+ _dimmer: null,
+ elements: null,
+ init: function () {
+ _dimmer=this;
+ _dimmer.elements = $('div[data-type="dimmer"]');
+ _dimmer.elements.each(function(index) {
+
+ var device = $(this).data('device');
+ $(this).data('get', $(this).data('get') || 'STATE');
+ $(this).data('set', $(this).data('set') || '');
+ $(this).data('get-on', $(this).attr('data-get-on') || $(this).attr('data-on') || 'on');
+ $(this).data('get-off', $(this).attr('data-get-off') || $(this).attr('data-off') || 'off');
+ $(this).data('set-off', $(this).attr('data-set-off') || $(this).data('get-off'));
+ var elem = $(this).famultibutton({
+ icon: 'fa-lightbulb-o',
+ backgroundIcon: 'fa-circle',
+ offColor: '#2A2A2A',
+ onColor: '#2A2A2A',
+ mode: 'dimmer',
+
+ // Called in toggle on state.
+ toggleOn: function( ) {
+ var v = $(this).data('famultibutton').getValue();
+ setFhemStatus(device,$(this).data('set')+' '+v);
+ },
+ toggleOff: function( ) {
+ setFhemStatus(device,$(this).data('set-off'));
+ },
+ valueChanged: function(v) {
+ localStorage.setItem("dimmer_"+device, v);
+ if ($(this).data('famultibutton').getState())
+ setFhemStatus(device,$(this).data('set')+' '+v);
+ },
+ });
+ var val = localStorage.getItem("dimmer_"+device);
+ if ( val )
+ elem.setValue( parseInt(val));
+ });
+ },
+ update: function (dev,par) {
+
+ var deviceElements;
+ if ( dev == '*' )
+ deviceElements= _dimmer.elements;
+ else
+ deviceElements= _dimmer.elements.filter('div[data-device="'+dev+'"]');
+
+ deviceElements.each(function(index) {
+
+ if ( $(this).data('get')==par || par =='*'){
+
+ var state = getDeviceValue( $(this), 'get' );
+ if (state) {
+ if ($.isNumeric(state)) $(this).data('famultibutton').setValue( parseInt(state));
+ if ( state == $(this).data('get-on') )
+ $(this).data('famultibutton').setOn();
+ else if ( state == $(this).data('get-off') )
+ $(this).data('famultibutton').setOff();
+ else if ( state.match(RegExp('^' + $(this).data('get-on') + '$')) )
+ $(this).data('famultibutton').setOn();
+ else if ( state.match(RegExp('^' + $(this).data('get-off') + '$')) )
+ $(this).data('famultibutton').setOff();
+ }
+ }
+ });
+ }
+
+};
\ No newline at end of file
diff --git a/js/widget_homestatus.js b/js/widget_homestatus.js
new file mode 100755
index 00000000..0187ea23
--- /dev/null
+++ b/js/widget_homestatus.js
@@ -0,0 +1,185 @@
+var widget_homestatus = {
+ _homestatus: null,
+ elements: null,
+ drawSelector: function () {
+ var sector=0;
+ var c = this.g; // context
+ var x=this.$.data('pageX');
+ var y=this.$.data('pageY');
+ var mx=this.x+this.w2;
+ var my=this.y+this.w2;
+ var r=this.radius*0.4;
+
+ //Assign sector 1 for center pressed or set value 0
+ if ( Math.pow((mx-x),2) + Math.pow((my-y),2) < Math.pow(r,2)
+ || this.cv == 0 )
+ sector=1;
+
+ if (sector==1){
+ // inner circle
+ c.lineWidth = this.radius*0.4;
+ c.strokeStyle = this.o.fgColor ;
+ c.beginPath();
+ c.arc( this.xy, this.xy, this.radius*0.2, 0, 2 * Math.PI);
+ c.stroke();
+ }
+ else{
+ // outer section
+ var start=0;
+ var end = 0;
+
+ if (this.cv > Math.PI*0.5 && this.cv <= Math.PI*1.5){
+ start=0; end=Math.PI; sector=3;
+ }
+ else if (this.cv > Math.PI*1.5 && this.cv <= Math.PI*2){
+ start=Math.PI; end=Math.PI*1.5; sector=2;
+ }
+ else if (this.cv > 0 && this.cv <= Math.PI*0.5){
+ start=Math.PI*1.5; end=Math.PI*2; sector=4;
+ }
+
+ c.lineWidth = this.radius*0.6;
+ c.beginPath();
+ c.strokeStyle = this.o.fgColor;
+ c.arc(this.xy, this.xy, this.radius*0.7, start, end);
+ c.stroke();
+ }
+
+ // sections
+ c.strokeStyle = this.o.tkColor;
+ c.lineWidth = this.radius*0.6;
+ c.beginPath();
+ c.arc(this.xy, this.xy, this.radius*0.7, 0, 0.02);
+ c.stroke();
+ c.beginPath();
+ c.arc(this.xy, this.xy, this.radius*0.7, Math.PI -0.02, Math.PI);
+ c.stroke();
+ c.beginPath();
+ c.arc(this.xy, this.xy, this.radius*0.7, 1.5 * Math.PI-0.02, 1.5 * Math.PI);
+ c.stroke();
+
+ // inner circle line
+ c.lineWidth = 2;
+ c.strokeStyle = this.o.tkColor;
+ c.beginPath();
+ c.arc( this.xy, this.xy, this.radius*0.4, 0, 2 * Math.PI);
+ c.stroke();
+
+ // outer circle line
+ c.lineWidth = 2;
+ c.beginPath();
+ c.arc( this.xy, this.xy, this.radius, 0, 2 * Math.PI, false);
+ c.stroke();
+
+ c.fillStyle = (sector==1)?this.o.minColor:this.o.maxColor;
+ c.font = "100 11px sans-serif";
+ c.fillText("Home", this.xy-14, this.xy+15);
+ c.font = "22px FontAwesome";
+ c.fillText("\uf015", this.xy-12, this.xy+2);
+
+ c.fillStyle = (sector==2)?this.o.minColor:this.o.maxColor;
+ c.font = "22px FontAwesome";
+ c.fillText("\uf236", this.xy-this.radius*0.7, this.xy-this.radius*0.4);
+ c.font = "100 11px sans-serif";
+ c.fillText("Night", this.xy-this.radius*0.9, this.xy-10);
+
+ c.fillStyle = (sector==3)?this.o.minColor:this.o.maxColor;
+ c.font = "22px FontAwesome";
+ c.fillText("\uf1b9", this.xy-12, this.xy+this.radius*0.67);
+ c.font = "100 11px sans-serif";
+ c.fillText("Away", this.xy-12, this.xy+this.radius*0.65+15);
+
+ c.fillStyle = (sector==4)?this.o.minColor:this.o.maxColor;
+ c.font = "22px FontAwesome";
+ c.fillText("\uf0f2", this.xy+this.radius*0.4, this.xy-this.radius*0.4);
+ c.font = "100 11px sans-serif";
+ c.fillText("Holiday", this.xy+this.radius*0.42, this.xy-10);
+
+ this.o.status = sector;
+ return false;
+},
+ init: function () {
+ _homestatus=this;
+ _homestatus.elements = $('div[data-type="homestatus"]');
+ _homestatus.elements.each(function(index) {
+
+ var clientX=0;
+ var clientY=0;
+ var knob_elem = jQuery('', {
+ type: 'text',
+ }).data($(this).data())
+ .data('curval', 10)
+ .appendTo($(this));
+
+ $(this).bind('mousemove', function(e) {
+
+ knob_elem.data('pageX',e.pageX);
+ knob_elem.data('pageY',e.pageY);
+ e.preventDefault();
+ });
+
+ var device = $(this).data('device');
+ $(this).data('get', $(this).data('get') || 'STATE');
+
+ knob_elem.knob({
+ 'min': 0,
+ 'max': 2 * Math.PI,
+ 'step': 0.01,
+ 'height':210,
+ 'width':210,
+ 'bgColor': $(this).data('bgcolor') || '#aaaaaa',
+ 'fgColor': $(this).data('fgcolor') || '#aa6900',
+ 'tkColor': $(this).data('tkcolor') || '#696969',
+ 'minColor': '#2A2A2A',
+ 'maxColor': '#696969',
+ 'thickness': 0.4,
+ 'displayInput': false,
+ 'angleOffset' : 0,
+ 'reading': $(this).data('set') || '',
+ 'draw' : _homestatus.drawSelector,
+ 'change' : function (v) {
+ startInterval();
+ },
+ 'release' : function (v) {
+ if (ready){
+ setFhemStatus(device, this.o.reading + ' ' + this.o.status);
+ this.$.data('curval', v);
+ }
+ }
+ });
+ });
+ },
+ update: function (dev,par) {
+
+ var deviceElements;
+ if ( dev == '*' )
+ deviceElements= _homestatus.elements;
+ else
+ deviceElements= _homestatus.elements.filter('div[data-device="'+dev+'"]');
+
+ deviceElements.each(function(index,elem) {
+
+ var value = getDeviceValue( $(this), 'get' );
+ if (value && value > -1){
+ var knob_elem = $(this).find('input');
+ var val=0;
+ switch( value ) {
+ case '3':
+ val=Math.PI;
+ break;
+ case '4':
+ val=Math.PI*0.25;
+ break;
+ case '2':
+ val=Math.PI*1.75;
+ break;
+ default:
+ val=0;
+ }
+ if ( knob_elem.data('curval') != val )
+ knob_elem.val( val ).trigger('change');
+ }
+ });
+ },
+
+};
\ No newline at end of file
diff --git a/js/widget_label.js b/js/widget_label.js
new file mode 100755
index 00000000..520c2798
--- /dev/null
+++ b/js/widget_label.js
@@ -0,0 +1,47 @@
+var widget_label = {
+ _label: null,
+ elements: null,
+ init: function () {
+ _label=this;
+ _label.elements = $('div[data-type="label"]');
+ _label.elements.each(function(index) {
+ $(this).data('get', $(this).data('get') || 'STATE');
+ });
+ },
+ update: function (dev,par) {
+
+ var deviceElements;
+ if ( dev == '*' )
+ deviceElements= _label.elements;
+ else
+ deviceElements= _label.elements.filter('div[data-device="'+dev+'"]');
+
+ deviceElements.each(function(index,elem) {
+ if ( $(this).data('get')==par || par =='*'){
+ var value = getDeviceValue( $(this), 'get' );
+ if (value){
+ var part = $(this).data('part') || -1;
+ var unit = ($(this).data('unit')) ? unescape($(this).data('unit')) : '';
+ var fix = $(this).data('fix');
+ fix = ( $.isNumeric(fix) ) ? fix : -1;
+ var val = getPart(value,part);
+ val = ( $.isNumeric(val) && fix>=0 ) ? Number(val).toFixed(fix) : val;
+ $(this).html( val + ""+unit+"" );
+ //set colors according limits for numeric values
+ if ($.isNumeric(val)){
+ var limits=$(this).data('limits');
+ var colors=$(this).data('colors');
+ if (limits && colors && limits.length == colors.length){
+ for (var i=0;ilimits[i])
+ $(this).css("color", colors[i]);
+ }
+ }
+ }
+ }
+ }
+
+ });
+ }
+
+};
\ No newline at end of file
diff --git a/js/widget_push.js b/js/widget_push.js
new file mode 100755
index 00000000..7883fafe
--- /dev/null
+++ b/js/widget_push.js
@@ -0,0 +1,49 @@
+var widget_push = {
+ _push: null,
+ elements: null,
+ init: function () {
+ _push=this;
+ _push.elements = $('div[data-type="push"]');
+ _push.elements.each(function(index) {
+
+ var device = $(this).data('device');
+ var elem = $(this).famultibutton({
+ backgroundIcon: 'fa-circle-thin',
+ offColor: '#505050',
+ onColor: '#aa6900',
+ mode: 'push',
+
+ // Called in toggle on state.
+ toggleOn: function( ) {
+ setFhemStatus(device,$(this).data('set'));
+ },
+
+ });
+ });
+ },
+ update: function (dev,par) {
+
+ var deviceElements;
+ if ( dev == '*' )
+ deviceElements= _push.elements;
+ else
+ deviceElements= _push.elements.filter('div[data-device="'+dev+'"]');
+
+ deviceElements.each(function(index,elem) {
+ if ( $(this).data('get')==par || par =='*'){
+ var value = getDeviceValue( $(this), 'get' );
+ if (value){
+ var part = $(this).data('part') || -1;
+ var unit = ($(this).data('unit')) ? unescape($(this).data('unit')) : '';
+ var fix = $(this).data('fix');
+ fix = ( $.isNumeric(fix) ) ? fix : 1;
+ var val = getPart(value,part);
+ val = ( $.isNumeric(val) ) ? Number(val).toFixed(fix) : val;
+ $(this).html( val + ""
+ +unit+"" );
+ }
+ }
+ });
+ }
+
+};
\ No newline at end of file
diff --git a/js/widget_slider.js b/js/widget_slider.js
new file mode 100755
index 00000000..abe30dd8
--- /dev/null
+++ b/js/widget_slider.js
@@ -0,0 +1,70 @@
+var widget_slider = {
+ _slider: null,
+ elements: null,
+ init: function () {
+ _slider=this;
+ _slider.elements = $('div[data-type="slider"]');
+ _slider.elements.each(function(index) {
+
+ var device = $(this).data('device');
+ $(this).data('get', $(this).data('get') || 'STATE');
+ $(this).data('set', $(this).data('set') || '');
+ //ToDo: more data parameter: color etc.
+
+ var storeval = localStorage.getItem("slider_"+device);
+ var elem = jQuery('', {
+ type: 'text',
+ }).appendTo($(this));
+
+ var pwrng = new Powerange(elem[0], {
+ vertical: true,
+ hideRange: true,
+ 'min': $(this).data('min') || 0,
+ 'max': $(this).data('max') || 100,
+ klass: 'slider_vertical',
+ start: (storeval)?storeval:'5',
+ });
+ $(this).data('Powerange',pwrng);
+
+ var releaseEventType=((document.ontouchend!==null)?'mouseup':'touchend');
+
+ $(this).bind(releaseEventType, function(e) {
+ var val = $(this).find('input').val();
+ localStorage.setItem("slider_"+device, val);
+ setFhemStatus(device,$(this).data('set')+' '+val);
+ $.toast('Set '+ device + ' ' + $(this).data('set')+' '+val);
+ e.preventDefault();
+ });
+
+ //ToDo: make fit for horizontal
+ $(this).addClass('slider_vertical');
+
+ });
+ },
+ update: function (dev,par) {
+
+ var deviceElements;
+ if ( dev == '*' )
+ deviceElements= _slider.elements;
+ else
+ deviceElements= _slider.elements.filter('div[data-device="'+dev+'"]');
+
+ deviceElements.each(function(index) {
+
+ if ( $(this).data('get')==par || par =='*'){
+
+ var state = getDeviceValue( $(this), 'get' );
+ if (state) {
+ var elem = $(this).find('input');
+ if ($.isNumeric(state) && elem) {
+ var pwrng = $(this).data('Powerange');
+ pwrng.setStart(parseInt(state));
+ DEBUG && console.log( 'slider dev:'+dev+' par:'+par+' changed to:'+state );
+ }
+ }
+ }
+ elem.css({visibility:'visible'});
+ });
+ }
+
+};
\ No newline at end of file
diff --git a/js/widget_switch.js b/js/widget_switch.js
new file mode 100755
index 00000000..712d2594
--- /dev/null
+++ b/js/widget_switch.js
@@ -0,0 +1,56 @@
+var widget_switch = {
+ _switch: null,
+ elements: null,
+ init: function () {
+ _switch=this;
+ _switch.elements = $('div[data-type="switch"]');
+ _switch.elements.each(function(index) {
+
+ var device = $(this).data('device');
+ $(this).data('get', $(this).data('get') || 'STATE');
+ $(this).data('get-on', $(this).attr('data-get-on') || $(this).attr('data-on') || 'on');
+ $(this).data('get-off', $(this).attr('data-get-off') || $(this).attr('data-off') || 'off');
+ $(this).data('set-on', $(this).attr('data-set-on') || $(this).data('get-on'));
+ $(this).data('set-off', $(this).attr('data-set-off') || $(this).data('get-off'));
+ var elem = $(this).famultibutton({
+ icon: 'fa-lightbulb-o',
+ backgroundIcon: 'fa-circle',
+ offColor: '#2A2A2A',
+ onColor: '#2A2A2A',
+
+ // Called in toggle on state.
+ toggleOn: function( ) {
+ setFhemStatus(device,$(this).data('set-on'));
+ },
+ toggleOff: function( ) {
+ setFhemStatus(device,$(this).data('set-off'));
+ },
+ });
+ });
+ },
+ update: function (dev,par) {
+
+ var deviceElements;
+ if ( dev == '*' )
+ deviceElements= _switch.elements;
+ else
+ deviceElements= _switch.elements.filter('div[data-device="'+dev+'"]');
+
+ deviceElements.each(function(index,elem) {
+ if ( $(this).data('get')==par || par =='*'){
+ var state = getDeviceValue( $(this), 'get' );
+ if (state) {
+ if ( state == $(this).data('get-on') )
+ $(this).data('famultibutton').setOn();
+ else if ( state == $(this).data('get-off') )
+ $(this).data('famultibutton').setOff();
+ else if ( state.match(RegExp('^' + $(this).data('get-on') + '$')) )
+ $(this).data('famultibutton').setOn();
+ else if ( state.match(RegExp('^' + $(this).data('get-off') + '$')) )
+ $(this).data('famultibutton').setOff();
+ }
+ }
+ });
+ }
+
+};
\ No newline at end of file
diff --git a/js/widget_template.js b/js/widget_template.js
new file mode 100755
index 00000000..80e27529
--- /dev/null
+++ b/js/widget_template.js
@@ -0,0 +1,31 @@
+var widget_template = {
+ _template: null,
+ elements: null,
+ init: function () {
+ _template=this;
+ _template.elements = $('div[data-type="template"]');
+ _template.elements.each(function(index) {
+ var device = $(this).data('device');
+ // Init the widget here
+ });
+ },
+ update: function (dev,par) {
+
+ var deviceElements;
+ if ( dev == '*' )
+ deviceElements= _template.elements;
+ else
+ deviceElements= _template.elements.filter('div[data-device="'+dev+'"]');
+
+ deviceElements.each(function(index,elem) {
+ if ( $(this).data('get')==par || par =='*'){
+
+ var value = getDeviceValue( $(this), 'get' );
+ if (value){
+ // Update the widget here
+ }
+ }
+ });
+ }
+
+};
\ No newline at end of file
diff --git a/js/widget_thermostat.js b/js/widget_thermostat.js
new file mode 100755
index 00000000..2361fab2
--- /dev/null
+++ b/js/widget_thermostat.js
@@ -0,0 +1,229 @@
+var widget_thermostat = {
+ elements: null,
+ _thermostat:null,
+ getnix: function() { return 'nix';},
+ getClimaValues: function (device) {
+
+ var state = getDeviceValue( device, '');
+ var desi = getDeviceValue( device, 'get');
+ return {
+ temp: getDeviceValue( device, 'temp'),
+ desired: ( state && state.indexOf('set_') < 0 ) ? desi : getPart(state,2),
+ valve: getDeviceValue( device, 'valve')
+ };
+},
+ getGradientColor: function(start_color, end_color, percent) {
+ // strip the leading # if it's there
+ start_color = start_color.replace(/^\s*#|\s*$/g, '');
+ end_color = end_color.replace(/^\s*#|\s*$/g, '');
+
+ // convert 3 char codes --> 6, e.g. `E0F` --> `EE00FF`
+ if(start_color.length == 3){
+ start_color = start_color.replace(/(.)/g, '$1$1');
+ }
+
+ if(end_color.length == 3){
+ end_color = end_color.replace(/(.)/g, '$1$1');
+ }
+
+ // get colors
+ var start_red = parseInt(start_color.substr(0, 2), 16),
+ start_green = parseInt(start_color.substr(2, 2), 16),
+ start_blue = parseInt(start_color.substr(4, 2), 16);
+
+ var end_red = parseInt(end_color.substr(0, 2), 16),
+ end_green = parseInt(end_color.substr(2, 2), 16),
+ end_blue = parseInt(end_color.substr(4, 2), 16);
+
+ // calculate new color
+ var diff_red = end_red - start_red;
+ var diff_green = end_green - start_green;
+ var diff_blue = end_blue - start_blue;
+
+ diff_red = ( (diff_red * percent) + start_red ).toString(16).split('.')[0];
+ diff_green = ( (diff_green * percent) + start_green ).toString(16).split('.')[0];
+ diff_blue = ( (diff_blue * percent) + start_blue ).toString(16).split('.')[0];
+
+ // ensure 2 digits by color
+ if( diff_red.length == 1 )
+ diff_red = '0' + diff_red
+
+ if( diff_green.length == 1 )
+ diff_green = '0' + diff_green
+
+ if( diff_blue.length == 1 )
+ diff_blue = '0' + diff_blue
+
+ return '#' + diff_red + diff_green + diff_blue;
+ },
+ drawDial: function () {
+ var c = this.g, // context
+ a = this.arc(this.cv), // Arc
+ pa, // Previous arc
+ r = 1;
+
+ c.lineWidth = this.lineWidth;
+ c.lineCap = this.lineCap;
+ if (this.o.bgColor !== "none") {
+ c.beginPath();
+ c.strokeStyle = this.o.bgColor;
+ c.arc(this.xy, this.xy, this.radius, this.endAngle - 0.00001, this.startAngle + 0.00001, true);
+ c.stroke();
+ }
+
+ var tick_w = (2 * Math.PI) / 360;
+ var step = (this.o.max - this.o.min) / this.angleArc;
+ var acAngle = ((this.o.isValue - this.o.min) / step) + this.startAngle;
+ var dist = this.o.tickdistance || 4;
+ var mincolor = this.o.minColor || '#ff0000';
+ var maxcolor = this.o.maxColor || '#4477ff';
+
+ // draw ticks
+ for (tick = this.startAngle; tick < this.endAngle + 0.00001; tick+=tick_w*dist) {
+ i = step * (tick-this.startAngle)+this.o.min;
+
+ c.beginPath();
+
+ if ((tick > acAngle && tick < a.s) || (tick-tick_w*4 <= acAngle && tick+tick_w*4 >= a.s)){
+ // draw diff range in gradient color
+ c.strokeStyle = getGradientColor(maxcolor, mincolor, (this.endAngle-tick)/this.angleArc);
+ }
+ else {
+ // draw normal ticks
+ c.strokeStyle = this.o.tkColor;//'#4477ff';
+ }
+
+ // thicker lines every 5 ticks
+ if ( Math.round(i*10)/10 % 5 == 0 ){
+ w = tick_w*2;
+ w *= (c.strokeStyle != this.o.tkColor) ? 1.5 : 1;
+ }
+ else {
+ w = tick_w;
+ w *= (c.strokeStyle != this.o.tkColor) ? 2 : 1;
+ }
+ // thicker lines every at current value
+ if (acAngle > tick-tick_w && acAngle < tick+tick_w)
+ w *= 1.9;
+
+ c.arc( this.xy, this.xy, this.radius, tick, tick+w , false);
+ c.stroke();
+ }
+
+ // draw target temp cursor
+ c.beginPath();
+ this.o.fgColor= getGradientColor(maxcolor, mincolor, (this.endAngle-a.e)/(this.endAngle-this.startAngle));
+ c.strokeStyle = r ? this.o.fgColor : this.fgColor;
+ c.lineWidth = this.lineWidth * 2;
+ c.arc(this.xy, this.xy, this.radius-this.lineWidth/2, a.s, a.e, a.d);
+ c.stroke();
+
+ //draw current value as text
+ var x = this.radius*0.7*Math.cos(acAngle);
+ var y = this.radius*0.7*Math.sin(acAngle);
+ c.fillStyle = this.o.tkColor;
+ c.font="10px sans-serif";
+ c.fillText(this.o.isValue ,this.xy+x-5,this.xy+y+5);
+
+ //draw valve value as text
+ if ( this.o.valveValue ) {
+ var x = -5;
+ var y = this.radius*0.55;
+ c.fillStyle = this.o.tkColor;
+ c.font="10px sans-serif";
+ c.fillText(this.o.valveValue+'%',this.xy+x,this.xy+y+5);
+ }
+ return false;
+},
+ init: function () {
+ _thermostat=this;
+ _thermostat.elements=$('div[data-type="thermostat"]');
+ _thermostat.elements.each(function( index ) {
+ var knob_elem = jQuery('', {
+ type: 'text',
+ value: '10',
+ }).appendTo($(this));
+
+ var device = $(this).data('device');
+ //default reading parameter name
+ $(this).data('get', $(this).data('get') || 'desired-temp');
+ $(this).data('temp', $(this).data('temp') || 'measured-temp');
+
+ knob_elem.knob({
+ 'min':10,
+ 'max':30,
+ 'height':100,
+ 'width':100,
+ 'step': 1*$(this).data('step') || 1,
+ 'angleOffset': $(this).data('angleoffset') || -120,
+ 'angleArc': $(this).data('anglearc') || 240,
+ 'bgColor': $(this).data('bgcolor') || 'transparent',
+ 'fgColor': $(this).data('fgcolor') || '#cccccc',
+ 'tkColor': $(this).data('tkcolor') || '#696969',
+ 'minColor': '#4477ff',
+ 'maxColor': '#ff0000',
+ 'thickness': .25,
+ 'cursor': 6,
+ 'reading': $(this).data('set') || 'desired-temp',
+ 'draw' : _thermostat.drawDial,
+ 'change' : function (v) {
+ //reset poll timer to avoid jump back
+ startInterval();
+ },
+ 'release' : function (v) {
+ if (ready){
+ setFhemStatus(device, this.o.reading + ' ' + v);
+ $.toast('Set '+ device + ' ' + this.o.reading + ' ' + v );
+ }
+ }
+ });
+ });
+ $('[data-temp]').each(function(index){
+ var reading = $(this).data("temp");
+ if(!readings[reading])
+ readings[reading] = true;
+ });
+ $('[data-valve]').each(function(index){
+ var reading = $(this).data("valve");
+ if(!readings[reading])
+ readings[reading] = true;
+ });
+ },
+ update: function (dev,par) {
+
+ var deviceElements;
+ if ( dev == '*' )
+ deviceElements= _thermostat.elements;
+ else
+ deviceElements= _thermostat.elements.filter('div[data-device="'+dev+'"]');
+
+ deviceElements.each(function(index) {
+
+ var clima = _thermostat.getClimaValues( $(this) );
+ var knob_elem = $(this).find('input');
+
+ if ( ($(this).data('get')==par || par =='*') &&
+ clima.desired && clima.desired > 0 && knob_elem.data('desvalue') != clima.desired ){
+ knob_elem.val( clima.desired ).trigger('change');
+ knob_elem.data('desvalue', clima.desired);
+ DEBUG && console.log( 'thermo dev:'+dev+' par:'+par+' change:clima.desired' );
+ }
+ if ( clima.temp && clima.temp > 0 && knob_elem.data('curvalue') != clima.temp ){
+ knob_elem.trigger(
+ 'configure', { "isValue": clima.temp }
+ );
+ knob_elem.data('curvalue', clima.temp);
+ DEBUG && console.log( 'thermo dev:'+dev+' par:'+par+' change:clima.temp' );
+ }
+ if ( clima.valve && knob_elem.data('curvalve') != clima.valve ){
+ knob_elem.trigger(
+ 'configure', { "valveValue": clima.valve }
+ );
+ knob_elem.data('curvalve', clima.valve);
+ DEBUG && console.log( 'thermo dev:'+dev+' par:'+par+' change:clima.valve' );
+ }
+ knob_elem.css({visibility:'visible'});
+ });
+},
+};
+
diff --git a/js/widget_volume.js b/js/widget_volume.js
new file mode 100755
index 00000000..927dfa6b
--- /dev/null
+++ b/js/widget_volume.js
@@ -0,0 +1,145 @@
+var widget_volume = {
+ _volume: null,
+ elements: null,
+ drawDial: function () {
+ var c = this.g, // context
+ a = this.arc(this.cv), // Arc
+ pa, // Previous arc
+ r = 1;
+
+ c.lineWidth = this.lineWidth;
+ c.lineCap = this.lineCap;
+ if (this.o.bgColor !== "none") {
+ c.beginPath();
+ c.strokeStyle = this.o.bgColor;
+ c.arc(this.xy, this.xy, this.radius, this.endAngle - 0.00001, this.startAngle + 0.00001, true);
+ c.stroke();
+ }
+
+ var tick_w = (2 * Math.PI) / 360;
+ var step = (this.o.max - this.o.min) / this.angleArc;
+ var acAngle = ((this.o.isValue - this.o.min) / step) + this.startAngle;
+ var dist = this.o.tickdistance || 4;
+ var mincolor = this.o.minColor || '#ff0000';
+ var maxcolor = this.o.maxColor || '#4477ff';
+
+ // draw ticks
+ for (tick = this.startAngle; tick < this.endAngle + 0.00001; tick+=tick_w*dist) {
+ i = step * (tick-this.startAngle)+this.o.min;
+
+ c.beginPath();
+
+ if ((tick > acAngle && tick < a.s) || (tick-tick_w*4 <= acAngle && tick+tick_w*4 >= a.s)){
+ // draw diff range in gradient color
+ c.strokeStyle = getGradientColor(maxcolor, mincolor, (this.endAngle-tick)/this.angleArc);
+ }
+ else {
+ // draw normal ticks
+ c.strokeStyle = this.o.tkColor;//'#4477ff';
+ }
+
+ // thicker lines every 5 ticks
+ if ( Math.round(i*10)/10 % 5 == 0 ){
+ w = tick_w*2;
+ w *= (c.strokeStyle != this.o.tkColor) ? 1.5 : 1;
+ }
+ else {
+ w = tick_w;
+ w *= (c.strokeStyle != this.o.tkColor) ? 2 : 1;
+ }
+ // thicker lines every at current value
+ if (acAngle > tick-tick_w && acAngle < tick+tick_w)
+ w *= 1.9;
+
+ c.arc( this.xy, this.xy, this.radius, tick, tick+w , false);
+ c.stroke();
+ }
+
+ // draw target temp cursor
+ c.beginPath();
+ this.o.fgColor= getGradientColor(maxcolor, mincolor, (this.endAngle-a.e)/(this.endAngle-this.startAngle));
+ c.strokeStyle = r ? this.o.fgColor : this.fgColor;
+ c.lineWidth = this.lineWidth * 2;
+ c.arc(this.xy, this.xy, this.radius-this.lineWidth/2, a.s, a.e, a.d);
+ c.stroke();
+
+ //draw current value as text
+ var x = this.radius*0.7*Math.cos(acAngle);
+ var y = this.radius*0.7*Math.sin(acAngle);
+ c.fillStyle = this.o.tkColor;
+ c.font="10px sans-serif";
+ c.fillText(this.o.isValue ,this.xy+x-5,this.xy+y+5);
+
+ //draw valve value as text
+ if ( this.o.valveValue ) {
+ var x = -5;
+ var y = this.radius*0.55;
+ c.fillStyle = this.o.tkColor;
+ c.font="10px sans-serif";
+ c.fillText(this.o.valveValue+'%',this.xy+x,this.xy+y+5);
+ }
+ return false;
+},
+ init: function () {
+ _volume=this;
+ _volume.elements = $('div[data-type="volume"]');
+ _volume.elements.each(function(index) {
+ var knob_elem = jQuery('', {
+ type: 'text',
+ value: '10',
+ }).appendTo($(this));
+
+ var device = $(this).data('device');
+ $(this).data('get', $(this).data('get') || 'STATE');
+
+ knob_elem.knob({
+ 'min': $(this).data('min') || 0,
+ 'max': $(this).data('max') || 70,
+ 'height':150,
+ 'width':150,
+ 'angleOffset': $(this).data('angleoffset') || -120,
+ 'angleArc': $(this).data('anglearc') || 240,
+ 'bgColor': $(this).data('bgcolor') || 'transparent',
+ 'fgColor': $(this).data('fgcolor') || '#cccccc',
+ 'tkColor': $(this).data('tkcolor') || '#696969',
+ 'minColor': '#aa6900',
+ 'maxColor': '#aa6900',
+ 'thickness': .25,
+ 'tickdistance': 20,
+ 'cursor': 6,
+ 'reading': $(this).data('set') || '',
+ 'draw' : _volume.drawDial,
+ 'change' : function (v) {
+ startInterval();
+ },
+ 'release' : function (v) {
+ if (ready){
+ setFhemStatus(device, this.o.reading + ' ' + v);
+ this.$.data('curval', v);
+ }
+ }
+ });
+ });
+ },
+ update: function (dev,par) {
+
+ var deviceElements;
+ if ( dev == '*' )
+ deviceElements= _volume.elements;
+ else
+ deviceElements= _volume.elements.filter('div[data-device="'+dev+'"]');
+
+ deviceElements.each(function(index,elem) {
+
+ var val = getDeviceValue( $(this), 'get' );
+ if (val){
+ var knob_elem = $(this).find('input');
+ if ( knob_elem.val() != val )
+ knob_elem.val( val ).trigger('change');
+ knob_elem.css({visibility:'visible'});
+ }
+
+ });
+ }
+
+};
\ No newline at end of file
diff --git a/lib/fa-multi-button.min.js b/lib/fa-multi-button.min.js
index 73b6f8be..7c9b56fa 100755
--- a/lib/fa-multi-button.min.js
+++ b/lib/fa-multi-button.min.js
@@ -1 +1 @@
-!function(t){t.fn.famultibutton=function(o){function n(){r=!0,l.children().first().css("color",c.onBackgroundColor),l.children().last().css("color",c.onColor)}function e(){r=!1,l.children().first().css("color",c.offBackgroundColor),l.children().last().css("color",c.offColor)}function s(){r&&(r=!1,t("").animate({width:100},{duration:700,easing:"swing",step:function(t,o){var n=(t-o.start)/(o.end-o.start);l.children().first().css("color",getGradientColor(c.onBackgroundColor,c.offBackgroundColor,n)),l.children().last().css("color",getGradientColor(c.onColor,c.offColor,n))}}))}if(this.length>1)return this.each(function(){t(this).famultibutton(o)}),this;var l=this,r=!1,a={backgroundIcon:"fa-circle",classes:["fa-2x"],icon:"fa-power-off",offColor:"#2A2A2A",offBackgroundColor:"#505050",onColor:"#2A2A2A",onBackgroundColor:"#aa6900",mode:"toggle",toggleOn:null,toggleOff:null},c=t.extend({},a,o),i=function(){if(c=t.extend({},c,l.data()),l.addClass("fa-stack"),jQuery("",{id:"bg","class":"fa fa-stack-2x"}).addClass(c.backgroundIcon).appendTo(l),jQuery("",{id:"fg","class":"fa fa-stack-1x"}).addClass(c.icon).appendTo(l),c.classes&&c.classes.length>0)for(var o=0;o").animate({width:100},{duration:700,easing:"swing",step:function(t,o){var n=(t-o.start)/(o.end-o.start);c.children().first().css("color",getGradientColor(O.onBackgroundColor,O.offBackgroundColor,n)),c.children().last().css("color",getGradientColor(O.onColor,O.offColor,n))}}))}function i(){clearTimeout(u),p=m>0?p-=1:p+=1,p>100&&(p=100),0>p&&(p=0),s();var t=d[Math.abs(m)];u=setTimeout(function(){i()},500-t)}function s(){var t=r[0];if(t.height=c.innerHeight(),t.width=c.innerWidth(),t.getContext){var o=t.getContext("2d");o.strokeStyle=O.offBackgroundColor;for(var n=t.height-Math.round(t.height*p/100),e=0;e1)return this.each(function(){t(this).famultibutton(o)}),this;var r,u,c=this,f=!1,h=!1,d=[0,10,40,80,120,140,150,160,180,200,240,260,280,300,320,420,430,440,450,460,470],g=0,p=0,m=0,v=0,C=0,b=!1,k=!1,x={backgroundIcon:"fa-circle",classes:["fa-2x"],icon:"fa-power-off",offColor:"#2A2A2A",offBackgroundColor:"#505050",onColor:"#2A2A2A",onBackgroundColor:"#aa6900",mode:"toggle",toggleOn:null,toggleOff:null,valueChanged:null},O=t.extend({},x,o),y=function(){if(O=t.extend({},O,c.data()),c.addClass("fa-stack"),jQuery("",{id:"bg","class":"fa fa-stack-2x"}).addClass(O.backgroundIcon).appendTo(c),jQuery("",{id:"fg","class":"fa fa-stack-1x"}).addClass(O.icon).appendTo(c),O.classes&&O.classes.length>0)for(var o=0;o").attr({id:"scale"}).appendTo(c),r=c.find("canvas#scale"),r.css({height:c.innerHeight()+4}),v=parseInt(r.offset().top)-parseInt(c.offset().top),s(),a()),c.data("famultibutton",c),c};if(getGradientColor=function(t,o,n){t=t.replace(/^\s*#|\s*$/g,""),o=o.replace(/^\s*#|\s*$/g,""),3==t.length&&(t=t.replace(/(.)/g,"$1$1")),3==o.length&&(o=o.replace(/(.)/g,"$1$1"));var e=parseInt(t.substr(0,2),16),l=parseInt(t.substr(2,2),16),i=parseInt(t.substr(4,2),16),s=parseInt(o.substr(0,2),16),a=parseInt(o.substr(2,2),16),r=parseInt(o.substr(4,2),16),u=s-e,c=a-l,f=r-i;return u=(u*n+e).toString(16).split(".")[0],c=(c*n+l).toString(16).split(".")[0],f=(f*n+i).toString(16).split(".")[0],1==u.length&&(u="0"+u),1==c.length&&(c="0"+c),1==f.length&&(f="0"+f),"#"+u+c+f},"push"==O.mode){var I=null!==document.ontouchstart?"mousedown":"touchstart",T=null!==document.ontouchend?"mouseup":"touchend",B=null!==document.ontouchleave?"mouseout":"touchleave";this.bind(I,function(t){n(),"function"==typeof O.toggleOn&&O.toggleOn.call(this),t.preventDefault()}),this.bind(T,function(t){l(),t.preventDefault()}),this.bind(B,function(t){l(),t.preventDefault()})}else if("toggle"==O.mode){var I=null!==document.ontouchstart?"click":"touchstart";this.bind(I,function(t){f?(e(),"function"==typeof O.toggleOff&&O.toggleOff.call(this)):(n(),"function"==typeof O.toggleOn&&O.toggleOn.call(this)),t.preventDefault()})}else if("dimmer"==O.mode){var I=null!==document.ontouchstart?"mousedown":"touchstart",D=null!==document.ontouchmove?"mousemove":"touchmove",T=null!==document.ontouchend?"mouseup":"touchend",B=null!==document.ontouchleave?"mouseout":"touchleave";this.bind(I,function(t){var o=t.originalEvent;g=o.touches?o.touches[0].clientY:t.pageY,m=0,k=!0,t.preventDefault()}),this.bind(B,function(t){b&&(b=!1,c.animate({top:0}),clearInterval(u),h=!1,a()),k=!1,t.preventDefault()}),this.bind(T,function(t){b?(b=!1,c.animate({top:0}),clearTimeout(u),h=!1,"function"==typeof O.valueChanged&&O.valueChanged.call(this,p)):f?(e(),"function"==typeof O.toggleOff&&O.toggleOff.call(this)):(n(),"function"==typeof O.toggleOn&&O.toggleOn.call(this)),b=!1,k=!1,a(),s(),t.preventDefault()}),this.bind(D,function(t){k&&(b=!0);var o=t.originalEvent;C=o.touches?o.touches[0].clientY:t.pageY,m=C-g,m>20&&(m=20),-20>m&&(m=-20),b&&(this.style.top=m+"px",h||(a(),i(),h=!0),r.css({top:-m+"px"})),t.preventDefault()})}return this.setOn=function(){n()},this.setOff=function(){e()},this.getState=function(){return f},this.getValue=function(){return p},this.setValue=function(t){p=t,s()},y()}}(jQuery);
\ No newline at end of file
diff --git a/lib/powerange.min.css b/lib/powerange.min.css
new file mode 100755
index 00000000..7ea50b29
--- /dev/null
+++ b/lib/powerange.min.css
@@ -0,0 +1 @@
+.range-bar{background-color:#404040;border-radius:15px;display:block;height:4px;position:relative;width:100%}.range-quantity{background-color:#aa6900;border-radius:15px;display:block;height:100%;width:0}.range-handle{background-color:#bcbcbc;border-radius:100%;height:20px;left:20px;top:-13px;position:absolute;width:20px;-webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);}.range-min,.range-max{color:#181819;font-size:12px;height:20px;padding-top:4px;position:absolute;text-align:center;top:-9px;width:24px}.range-min{left:-30px}.range-max{right:-30px}.vertical{height:100%;width:3px}.vertical .range-quantity{bottom:0;height:0;position:absolute;width:100%}.vertical .range-handle{bottom:0;left:-9px;top:auto}.vertical .range-min,.vertical .range-max{left:-10px;right:auto;top:auto}.vertical .range-min{bottom:-30px}.vertical .range-max{top:-30px}.unselectable{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.range-disabled{cursor:default}
\ No newline at end of file
diff --git a/lib/powerange.min.js b/lib/powerange.min.js
new file mode 100755
index 00000000..31cb9fde
--- /dev/null
+++ b/lib/powerange.min.js
@@ -0,0 +1 @@
+(function(){function e(t,s,n){var i=e.resolve(t);if(null==i){n=n||t,s=s||"root";var o=Error('Failed to require "'+n+'" from "'+s+'"');throw o.path=n,o.parent=s,o.require=!0,o}var r=e.modules[i];if(!r._resolving&&!r.exports){var a={};a.exports={},a.client=a.component=!0,r._resolving=!0,r.call(this,a.exports,e.relative(i),a),delete r._resolving,r.exports=a.exports}return r.exports}e.modules={},e.aliases={},e.resolve=function(t){"/"===t.charAt(0)&&(t=t.slice(1));for(var s=[t,t+".js",t+".json",t+"/index.js",t+"/index.json"],n=0;s.length>n;n++){var t=s[n];if(e.modules.hasOwnProperty(t))return t;if(e.aliases.hasOwnProperty(t))return e.aliases[t]}},e.normalize=function(e,t){var s=[];if("."!=t.charAt(0))return t;e=e.split("/"),t=t.split("/");for(var n=0;t.length>n;++n)".."==t[n]?e.pop():"."!=t[n]&&""!=t[n]&&s.push(t[n]);return e.concat(s).join("/")},e.register=function(t,s){e.modules[t]=s},e.alias=function(t,s){if(!e.modules.hasOwnProperty(t))throw Error('Failed to alias "'+t+'", it does not exist');e.aliases[s]=t},e.relative=function(t){function s(e,t){for(var s=e.length;s--;)if(e[s]===t)return s;return-1}function n(s){var i=n.resolve(s);return e(i,t,s)}var i=e.normalize(t,"..");return n.resolve=function(n){var o=n.charAt(0);if("/"==o)return n.slice(1);if("."==o)return e.normalize(i,n);var r=t.split("/"),a=s(r,"deps")+1;return a||(a=0),n=r.slice(0,a+1).join("/")+"/deps/"+n},n.exists=function(t){return e.modules.hasOwnProperty(n.resolve(t))},n},e.register("component-event/index.js",function(e){var t=window.addEventListener?"addEventListener":"attachEvent",s=window.removeEventListener?"removeEventListener":"detachEvent",n="addEventListener"!==t?"on":"";e.bind=function(e,s,i,o){return e[t](n+s,i,o||!1),i},e.unbind=function(e,t,i,o){return e[s](n+t,i,o||!1),i}}),e.register("component-query/index.js",function(e,t,s){function n(e,t){return t.querySelector(e)}e=s.exports=function(e,t){return t=t||document,n(e,t)},e.all=function(e,t){return t=t||document,t.querySelectorAll(e)},e.engine=function(t){if(!t.one)throw Error(".one callback required");if(!t.all)throw Error(".all callback required");return n=t.one,e.all=t.all,e}}),e.register("component-matches-selector/index.js",function(e,t,s){function n(e,t){if(r)return r.call(e,t);for(var s=i.all(t,e.parentNode),n=0;s.length>n;++n)if(s[n]==e)return!0;return!1}var i=t("query"),o=Element.prototype,r=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.msMatchesSelector||o.oMatchesSelector;s.exports=n}),e.register("discore-closest/index.js",function(e,t,s){var n=t("matches-selector");s.exports=function(e,t,s,i){for(e=s?{parentNode:e}:e,i=i||document;(e=e.parentNode)&&e!==document;){if(n(e,t))return e;if(e===i)return}}}),e.register("component-delegate/index.js",function(e,t){var s=t("closest"),n=t("event");e.bind=function(e,t,i,o,r){return n.bind(e,i,function(n){var i=n.target||n.srcElement;n.delegateTarget=s(i,t,!0,e),n.delegateTarget&&o.call(e,n)},r)},e.unbind=function(e,t,s,i){n.unbind(e,t,s,i)}}),e.register("component-events/index.js",function(e,t,s){function n(e,t){if(!(this instanceof n))return new n(e,t);if(!e)throw Error("element required");if(!t)throw Error("object required");this.el=e,this.obj=t,this._events={}}function i(e){var t=e.split(/ +/);return{name:t.shift(),selector:t.join(" ")}}var o=t("event"),r=t("delegate");s.exports=n,n.prototype.sub=function(e,t,s){this._events[e]=this._events[e]||{},this._events[e][t]=s},n.prototype.bind=function(e,t){function s(){var e=[].slice.call(arguments).concat(h);l[t].apply(l,e)}var n=i(e),a=this.el,l=this.obj,c=n.name,t=t||"on"+c,h=[].slice.call(arguments,2);return n.selector?s=r.bind(a,n.selector,c,s):o.bind(a,c,s),this.sub(c,t,s),s},n.prototype.unbind=function(e,t){if(0==arguments.length)return this.unbindAll();if(1==arguments.length)return this.unbindAllOf(e);var s=this._events[e];if(s){var n=s[t];n&&o.unbind(this.el,e,n)}},n.prototype.unbindAll=function(){for(var e in this._events)this.unbindAllOf(e)},n.prototype.unbindAllOf=function(e){var t=this._events[e];if(t)for(var s in t)this.unbind(e,s)}}),e.register("component-indexof/index.js",function(e,t,s){s.exports=function(e,t){if(e.indexOf)return e.indexOf(t);for(var s=0;e.length>s;++s)if(e[s]===t)return s;return-1}}),e.register("component-classes/index.js",function(e,t,s){function n(e){if(!e)throw Error("A DOM element reference is required");this.el=e,this.list=e.classList}var i=t("indexof"),o=/\s+/,r=Object.prototype.toString;s.exports=function(e){return new n(e)},n.prototype.add=function(e){if(this.list)return this.list.add(e),this;var t=this.array(),s=i(t,e);return~s||t.push(e),this.el.className=t.join(" "),this},n.prototype.remove=function(e){if("[object RegExp]"==r.call(e))return this.removeMatching(e);if(this.list)return this.list.remove(e),this;var t=this.array(),s=i(t,e);return~s&&t.splice(s,1),this.el.className=t.join(" "),this},n.prototype.removeMatching=function(e){for(var t=this.array(),s=0;t.length>s;s++)e.test(t[s])&&this.remove(t[s]);return this},n.prototype.toggle=function(e,t){return this.list?(t!==void 0?t!==this.list.toggle(e,t)&&this.list.toggle(e):this.list.toggle(e),this):(t!==void 0?t?this.add(e):this.remove(e):this.has(e)?this.remove(e):this.add(e),this)},n.prototype.array=function(){var e=this.el.className.replace(/^\s+|\s+$/g,""),t=e.split(o);return""===t[0]&&t.shift(),t},n.prototype.has=n.prototype.contains=function(e){return this.list?this.list.contains(e):!!~i(this.array(),e)}}),e.register("component-emitter/index.js",function(e,t,s){function n(e){return e?i(e):void 0}function i(e){for(var t in n.prototype)e[t]=n.prototype[t];return e}s.exports=n,n.prototype.on=n.prototype.addEventListener=function(e,t){return this._callbacks=this._callbacks||{},(this._callbacks[e]=this._callbacks[e]||[]).push(t),this},n.prototype.once=function(e,t){function s(){n.off(e,s),t.apply(this,arguments)}var n=this;return this._callbacks=this._callbacks||{},s.fn=t,this.on(e,s),this},n.prototype.off=n.prototype.removeListener=n.prototype.removeAllListeners=n.prototype.removeEventListener=function(e,t){if(this._callbacks=this._callbacks||{},0==arguments.length)return this._callbacks={},this;var s=this._callbacks[e];if(!s)return this;if(1==arguments.length)return delete this._callbacks[e],this;for(var n,i=0;s.length>i;i++)if(n=s[i],n===t||n.fn===t){s.splice(i,1);break}return this},n.prototype.emit=function(e){this._callbacks=this._callbacks||{};var t=[].slice.call(arguments,1),s=this._callbacks[e];if(s){s=s.slice(0);for(var n=0,i=s.length;i>n;++n)s[n].apply(this,t)}return this},n.prototype.listeners=function(e){return this._callbacks=this._callbacks||{},this._callbacks[e]||[]},n.prototype.hasListeners=function(e){return!!this.listeners(e).length}}),e.register("ui-component-mouse/index.js",function(e,t,s){function n(e,t){this.obj=t||{},this.el=e}var i=t("emitter"),o=t("event");s.exports=function(e,t){return new n(e,t)},i(n.prototype),n.prototype.bind=function(){function e(i){s.onmouseup&&s.onmouseup(i),o.unbind(document,"mousemove",t),o.unbind(document,"mouseup",e),n.emit("up",i)}function t(e){s.onmousemove&&s.onmousemove(e),n.emit("move",e)}var s=this.obj,n=this;return n.down=function(i){s.onmousedown&&s.onmousedown(i),o.bind(document,"mouseup",e),o.bind(document,"mousemove",t),n.emit("down",i)},o.bind(this.el,"mousedown",n.down),this},n.prototype.unbind=function(){o.unbind(this.el,"mousedown",this.down),this.down=null}}),e.register("abpetkov-percentage-calc/percentage-calc.js",function(e){e.isNumber=function(e){return"number"==typeof e?!0:!1},e.of=function(t,s){return e.isNumber(t)&&e.isNumber(s)?t/100*s:void 0},e.from=function(t,s){return e.isNumber(t)&&e.isNumber(s)?100*(t/s):void 0}}),e.register("abpetkov-closest-num/closest-num.js",function(e){e.find=function(e,t){var s=null,n=null,o=t[0];for(i=0;t.length>i;i++)s=Math.abs(e-o),n=Math.abs(e-t[i]),s>n&&(o=t[i]);return o}}),e.register("vesln-super/lib/super.js",function(e,t,s){function n(){var t=i.call(arguments);if(t.length)return"function"!=typeof t[0]?e.merge(t):(e.inherits.apply(null,t),void 0)}var i=Array.prototype.slice,e=s.exports=n;e.extend=function(t,s){var n=this,i=function(){return n.apply(this,arguments)};return e.merge([i,this]),e.inherits(i,this),t&&e.merge([i.prototype,t]),s&&e.merge([i,s]),i.extend=this.extend,i},e.inherits=function(e,t){e.super_=t,Object.create?e.prototype=Object.create(t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}):(e.prototype=new t,e.prototype.constructor=e)},e.merge=function(e){for(var t=2===e.length?e.shift():{},s=null,n=0,i=e.length;i>n;n++){s=e[n];for(var o in s)s.hasOwnProperty(o)&&(t[o]=s[o])}return t}}),e.register("powerange/lib/powerange.js",function(e,t,s){var n=(t("./main"),t("./horizontal")),i=t("./vertical"),o={callback:function(){},decimal:!1,disable:!1,disableOpacity:.5,hideRange:!1,klass:"",min:0,max:100,start:null,step:null,vertical:!1};s.exports=function(e,t){t=t||{};for(var s in o)null==t[s]&&(t[s]=o[s]);return t.vertical?new i(e,t):new n(e,t)}}),e.register("powerange/lib/main.js",function(e,t,s){function n(e,t){return this instanceof n?(this.element=e,this.options=t||{},this.slider=this.create("span","range-bar"),null!==this.element&&"text"===this.element.type&&this.init(),void 0):new n(e,t)}var o=t("mouse"),r=t("events"),a=t("classes"),l=t("percentage-calc");s.exports=n,n.prototype.bindEvents=function(){this.handle=this.slider.querySelector(".range-handle"),this.touch=r(this.handle,this),this.touch.bind("touchstart","onmousedown"),this.touch.bind("touchmove","onmousemove"),this.touch.bind("touchend","onmouseup"),this.mouse=o(this.handle,this),this.mouse.bind()},n.prototype.hide=function(){this.element.style.display="none"},n.prototype.append=function(){var e=this.generate();this.insertAfter(this.element,e)},n.prototype.generate=function(){var e={handle:{type:"span",selector:"range-handle"},min:{type:"span",selector:"range-min"},max:{type:"span",selector:"range-max"},quantity:{type:"span",selector:"range-quantity"}};for(var t in e)if(e.hasOwnProperty(t)){var s=this.create(e[t].type,e[t].selector);this.slider.appendChild(s)}return this.slider},n.prototype.create=function(e,t){var s=document.createElement(e);return s.className=t,s},n.prototype.insertAfter=function(e,t){e.parentNode.insertBefore(t,e.nextSibling)},n.prototype.extraClass=function(e){this.options.klass&&a(this.slider).add(e)},n.prototype.setRange=function(e,t){"number"!=typeof e||"number"!=typeof t||this.options.hideRange||(this.slider.querySelector(".range-min").innerHTML=e,this.slider.querySelector(".range-max").innerHTML=t)},n.prototype.setValue=function(e,t){var s=l.from(parseFloat(e),t),n=l.of(s,this.options.max-this.options.min)+this.options.min,i=!1;n=this.options.decimal?Math.round(100*n)/100:Math.round(n),i=this.element.value!=n?!0:!1,this.element.value=n,this.options.callback(),i&&this.changeEvent()},n.prototype.step=function(e,t){var s=e-t,n=l.from(this.checkStep(this.options.step),this.options.max-this.options.min),o=l.of(n,s),r=[];for(i=0;s>=i;i+=o)r.push(i);return this.steps=r,this.steps},n.prototype.checkValues=function(e){this.options.min>e&&(this.options.start=this.options.min),e>this.options.max&&(this.options.start=this.options.max),this.options.min>=this.options.max&&(this.options.min=this.options.max)},n.prototype.checkStep=function(e){return 0>e&&(e=Math.abs(e)),this.options.step=e,this.options.step},n.prototype.disable=function(){(this.options.min==this.options.max||this.options.min>this.options.max||this.options.disable)&&(this.mouse.unbind(),this.touch.unbind(),this.slider.style.opacity=this.options.disableOpacity,a(this.handle).add("range-disabled"))},n.prototype.unselectable=function(e,t){a(this.slider).has("unselectable")||t!==!0?a(this.slider).remove("unselectable"):a(this.slider).add("unselectable")},n.prototype.changeEvent=function(){if("function"!=typeof Event&&document.fireEvent)this.element.fireEvent("onchange");else{var e=document.createEvent("HTMLEvents");e.initEvent("change",!1,!0),this.element.dispatchEvent(e)}},n.prototype.init=function(){this.hide(),this.append(),this.bindEvents(),this.extraClass(this.options.klass),this.checkValues(this.options.start),this.setRange(this.options.min,this.options.max),this.disable()}}),e.register("powerange/lib/horizontal.js",function(e,t,s){function n(){a.apply(this,arguments),this.options.step&&this.step(this.slider.offsetWidth,this.handle.offsetWidth),this.setStart(this.options.start)}var i=t("super"),o=t("closest-num"),r=t("percentage-calc"),a=t("./main");s.exports=n,i(n,a),n.prototype.setStart=function(e){var t=null===e?this.options.min:e,s=r.from(t-this.options.min,this.options.max-this.options.min)||0,n=r.of(s,this.slider.offsetWidth-this.handle.offsetWidth),i=this.options.step?o.find(n,this.steps):n;this.setPosition(i),this.setValue(this.handle.style.left,this.slider.offsetWidth-this.handle.offsetWidth)},n.prototype.setPosition=function(e){this.handle.style.left=e+"px",this.slider.querySelector(".range-quantity").style.width=e+"px"},n.prototype.onmousedown=function(e){e.touches&&(e=e.touches[0]),this.startX=e.clientX,this.handleOffsetX=this.handle.offsetLeft,this.restrictHandleX=this.slider.offsetWidth-this.handle.offsetWidth,this.unselectable(this.slider,!0)},n.prototype.onmousemove=function(e){e.preventDefault(),e.touches&&(e=e.touches[0]);var t=this.handleOffsetX+e.clientX-this.startX,s=this.steps?o.find(t,this.steps):t;0>=t?this.setPosition(0):t>=this.restrictHandleX?this.setPosition(this.restrictHandleX):this.setPosition(s),this.setValue(this.handle.style.left,this.slider.offsetWidth-this.handle.offsetWidth)},n.prototype.onmouseup=function(){this.unselectable(this.slider,!1)}}),e.register("powerange/lib/vertical.js",function(e,t,s){function n(){l.apply(this,arguments),o(this.slider).add("vertical"),this.options.step&&this.step(this.slider.offsetHeight,this.handle.offsetHeight),this.setStart(this.options.start)}var i=t("super"),o=t("classes"),r=t("closest-num"),a=t("percentage-calc"),l=t("./main");s.exports=n,i(n,l),n.prototype.setStart=function(e){var t=null===e?this.options.min:e,s=a.from(t-this.options.min,this.options.max-this.options.min)||0,n=a.of(s,this.slider.offsetHeight-this.handle.offsetHeight),i=this.options.step?r.find(n,this.steps):n;this.setPosition(i),this.setValue(this.handle.style.bottom,this.slider.offsetHeight-this.handle.offsetHeight)},n.prototype.setPosition=function(e){this.handle.style.bottom=e+"px",this.slider.querySelector(".range-quantity").style.height=e+"px"},n.prototype.onmousedown=function(e){e.touches&&(e=e.touches[0]),this.startY=e.clientY,this.handleOffsetY=this.slider.offsetHeight-this.handle.offsetHeight-this.handle.offsetTop,this.restrictHandleY=this.slider.offsetHeight-this.handle.offsetHeight,this.unselectable(this.slider,!0)},n.prototype.onmousemove=function(e){e.preventDefault(),e.touches&&(e=e.touches[0]);var t=this.handleOffsetY+this.startY-e.clientY,s=this.steps?r.find(t,this.steps):t;0>=t?this.setPosition(0):t>=this.restrictHandleY?this.setPosition(this.restrictHandleY):this.setPosition(s),this.setValue(this.handle.style.bottom,this.slider.offsetHeight-this.handle.offsetHeight)},n.prototype.onmouseup=function(){this.unselectable(this.slider,!1)}}),e.alias("component-events/index.js","powerange/deps/events/index.js"),e.alias("component-events/index.js","events/index.js"),e.alias("component-event/index.js","component-events/deps/event/index.js"),e.alias("component-delegate/index.js","component-events/deps/delegate/index.js"),e.alias("discore-closest/index.js","component-delegate/deps/closest/index.js"),e.alias("discore-closest/index.js","component-delegate/deps/closest/index.js"),e.alias("component-matches-selector/index.js","discore-closest/deps/matches-selector/index.js"),e.alias("component-query/index.js","component-matches-selector/deps/query/index.js"),e.alias("discore-closest/index.js","discore-closest/index.js"),e.alias("component-event/index.js","component-delegate/deps/event/index.js"),e.alias("component-classes/index.js","powerange/deps/classes/index.js"),e.alias("component-classes/index.js","classes/index.js"),e.alias("component-indexof/index.js","component-classes/deps/indexof/index.js"),e.alias("ui-component-mouse/index.js","powerange/deps/mouse/index.js"),e.alias("ui-component-mouse/index.js","mouse/index.js"),e.alias("component-emitter/index.js","ui-component-mouse/deps/emitter/index.js"),e.alias("component-event/index.js","ui-component-mouse/deps/event/index.js"),e.alias("abpetkov-percentage-calc/percentage-calc.js","powerange/deps/percentage-calc/percentage-calc.js"),e.alias("abpetkov-percentage-calc/percentage-calc.js","powerange/deps/percentage-calc/index.js"),e.alias("abpetkov-percentage-calc/percentage-calc.js","percentage-calc/index.js"),e.alias("abpetkov-percentage-calc/percentage-calc.js","abpetkov-percentage-calc/index.js"),e.alias("abpetkov-closest-num/closest-num.js","powerange/deps/closest-num/closest-num.js"),e.alias("abpetkov-closest-num/closest-num.js","powerange/deps/closest-num/index.js"),e.alias("abpetkov-closest-num/closest-num.js","closest-num/index.js"),e.alias("abpetkov-closest-num/closest-num.js","abpetkov-closest-num/index.js"),e.alias("vesln-super/lib/super.js","powerange/deps/super/lib/super.js"),e.alias("vesln-super/lib/super.js","powerange/deps/super/index.js"),e.alias("vesln-super/lib/super.js","super/index.js"),e.alias("vesln-super/lib/super.js","vesln-super/index.js"),e.alias("powerange/lib/powerange.js","powerange/index.js"),"object"==typeof exports?module.exports=e("powerange"):"function"==typeof define&&define.amd?define([],function(){return e("powerange")}):this.Powerange=e("powerange")})();
\ No newline at end of file