Merge + fix side effects of making warning instead of errors in case of missing HMI variable svghmi
authorEdouard Tisserant
Thu, 17 Sep 2020 11:30:22 +0200
branchsvghmi
changeset 3058 6ea4b7e1a9ed
parent 3055 6dd617cc9c05 (current diff)
parent 3057 696301e869d5 (diff)
child 3059 e0db3f6a5f39
child 3060 4d20b92282e3
child 3063 466c3df67835
Merge + fix side effects of making warning instead of errors in case of missing HMI variable
svghmi/gen_index_xhtml.xslt
svghmi/widget_button.ysl2
svghmi/widgets_common.ysl2
tests/svghmi_v2/svghmi_0@svghmi/svghmi.svg
--- a/svghmi/gen_index_xhtml.xslt	Tue Sep 15 13:57:06 2020 +0200
+++ b/svghmi/gen_index_xhtml.xslt	Thu Sep 17 11:30:22 2020 +0200
@@ -915,7 +915,7 @@
           <xsl:when test="not(@index)">
             <xsl:choose>
               <xsl:when test="not(@type)">
-                <xsl:message terminate="yes">
+                <xsl:message terminate="no">
                   <xsl:text>Widget </xsl:text>
                   <xsl:value-of select="$widget/@type"/>
                   <xsl:text> id="</xsl:text>
@@ -924,6 +924,10 @@
                   <xsl:value-of select="@value"/>
                   <xsl:text>" in HMI tree</xsl:text>
                 </xsl:message>
+                <xsl:text>undefined</xsl:text>
+                <xsl:if test="position()!=last()">
+                  <xsl:text>,</xsl:text>
+                </xsl:if>
               </xsl:when>
               <xsl:when test="@type = 'PAGE_LOCAL'">
                 <xsl:text>"</xsl:text>
@@ -1179,6 +1183,8 @@
 </xsl:text>
     <xsl:text>                let index = this.get_variable_index(i);
 </xsl:text>
+    <xsl:text>                if(index == undefined) continue;
+</xsl:text>
     <xsl:text>                subscribers(index).add(this);
 </xsl:text>
     <xsl:text>            }
@@ -1197,6 +1203,8 @@
 </xsl:text>
     <xsl:text>            let realindex = this.get_variable_index(index);
 </xsl:text>
+    <xsl:text>            if(realindex == undefined) continue;
+</xsl:text>
     <xsl:text>            let cached_val = cache[realindex];
 </xsl:text>
     <xsl:text>            if(cached_val != undefined)
@@ -1231,9 +1239,13 @@
 </xsl:text>
     <xsl:text>    }
 </xsl:text>
-    <xsl:text>    change_hmi_value(index,opstr) {
-</xsl:text>
-    <xsl:text>        return change_hmi_value(this.get_variable_index(index), opstr);
+    <xsl:text>    change_hmi_value(index, opstr) {
+</xsl:text>
+    <xsl:text>        let realindex = this.get_variable_index(index);
+</xsl:text>
+    <xsl:text>        if(realindex == undefined) return undefined;
+</xsl:text>
+    <xsl:text>        return change_hmi_value(realindex, opstr);
 </xsl:text>
     <xsl:text>    }
 </xsl:text>
@@ -1241,7 +1253,11 @@
 </xsl:text>
     <xsl:text>    apply_hmi_value(index, new_val) {
 </xsl:text>
-    <xsl:text>        return apply_hmi_value(this.get_variable_index(index), new_val);
+    <xsl:text>        let realindex = this.get_variable_index(index);
+</xsl:text>
+    <xsl:text>        if(realindex == undefined) return undefined;
+</xsl:text>
+    <xsl:text>        return apply_hmi_value(realindex, new_val);
 </xsl:text>
     <xsl:text>    }
 </xsl:text>
@@ -1255,6 +1271,8 @@
 </xsl:text>
     <xsl:text>            let refindex = this.get_variable_index(i);
 </xsl:text>
+    <xsl:text>            if(refindex == undefined) continue;
+</xsl:text>
     <xsl:text>
 </xsl:text>
     <xsl:text>            if(index == refindex) {
@@ -1490,83 +1508,101 @@
 </xsl:text>
     <xsl:text>    state = 0;
 </xsl:text>
+    <xsl:text>    plc_lock = false;
+</xsl:text>
     <xsl:text>    active_style = undefined;
 </xsl:text>
     <xsl:text>    inactive_style = undefined;
 </xsl:text>
     <xsl:text>
 </xsl:text>
-    <xsl:text>    // TODO decouple update of DOM from event (i.e use animate())
-</xsl:text>
-    <xsl:text>
-</xsl:text>
-    <xsl:text>
-</xsl:text>
-    <xsl:text>    // TODO State of the button should distinguish UI feedbak from current PLC value
-</xsl:text>
-    <xsl:text>
-</xsl:text>
-    <xsl:text>    on_mouse_down(evt) {
+    <xsl:text>    dispatch(value) {
+</xsl:text>
+    <xsl:text>        if(value){
+</xsl:text>
+    <xsl:text>            this.button_release();
+</xsl:text>
+    <xsl:text>        }
+</xsl:text>
+    <xsl:text>    }
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>     on_mouse_down(evt) {
+</xsl:text>
+    <xsl:text>         if (this.active_style &amp;&amp; this.inactive_style) {
+</xsl:text>
+    <xsl:text>             this.active_elt.setAttribute("style", this.active_style);
+</xsl:text>
+    <xsl:text>             this.inactive_elt.setAttribute("style", "display:none");
+</xsl:text>
+    <xsl:text>         }
+</xsl:text>
+    <xsl:text>         this.apply_hmi_value(0, 1);
+</xsl:text>
+    <xsl:text>         this.plc_lock = false;
+</xsl:text>
+    <xsl:text>     }
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>     on_mouse_up(evt) {
+</xsl:text>
+    <xsl:text>         this.button_release();
+</xsl:text>
+    <xsl:text>     }
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>     button_release(){
+</xsl:text>
+    <xsl:text>        if(!this.plc_lock){
+</xsl:text>
+    <xsl:text>            this.plc_lock = true;
+</xsl:text>
+    <xsl:text>        }
+</xsl:text>
+    <xsl:text>        else{
+</xsl:text>
+    <xsl:text>            if (this.active_style &amp;&amp; this.inactive_style) {
+</xsl:text>
+    <xsl:text>                 this.active_elt.setAttribute("style", "display:none");
+</xsl:text>
+    <xsl:text>                 this.inactive_elt.setAttribute("style", this.inactive_style);
+</xsl:text>
+    <xsl:text>             }
+</xsl:text>
+    <xsl:text>             this.apply_hmi_value(0, 0);
+</xsl:text>
+    <xsl:text>        }
+</xsl:text>
+    <xsl:text>     }
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>     init() {
+</xsl:text>
+    <xsl:text>        this.active_style = this.active_elt ? this.active_elt.style.cssText : undefined;
+</xsl:text>
+    <xsl:text>        this.inactive_style = this.inactive_elt ? this.inactive_elt.style.cssText : undefined;
+</xsl:text>
+    <xsl:text>
 </xsl:text>
     <xsl:text>        if (this.active_style &amp;&amp; this.inactive_style) {
 </xsl:text>
-    <xsl:text>            this.active_elt.setAttribute("style", this.active_style);
-</xsl:text>
-    <xsl:text>            this.inactive_elt.setAttribute("style", "display:none");
+    <xsl:text>            this.active_elt.setAttribute("style", "display:none");
+</xsl:text>
+    <xsl:text>            this.inactive_elt.setAttribute("style", this.inactive_style);
 </xsl:text>
     <xsl:text>        }
 </xsl:text>
-    <xsl:text>        this.apply_hmi_value(0, 1);
-</xsl:text>
-    <xsl:text>        // TODO inhibit all mouse/touch events except mouse up (in other word grab cursor)
-</xsl:text>
-    <xsl:text>    }
-</xsl:text>
-    <xsl:text>
-</xsl:text>
-    <xsl:text>    on_mouse_up(evt) {
-</xsl:text>
-    <xsl:text>        if (this.active_style &amp;&amp; this.inactive_style) {
-</xsl:text>
-    <xsl:text>            this.active_elt.setAttribute("style", "display:none");
-</xsl:text>
-    <xsl:text>            this.inactive_elt.setAttribute("style", this.inactive_style);
-</xsl:text>
-    <xsl:text>        }
-</xsl:text>
-    <xsl:text>        this.apply_hmi_value(0, 0);
-</xsl:text>
-    <xsl:text>        // TODO release inhibited events 
-</xsl:text>
-    <xsl:text>    }
-</xsl:text>
-    <xsl:text>
-</xsl:text>
-    <xsl:text>    init() {
-</xsl:text>
-    <xsl:text>       // TODO : move to widget_defs so that we can have generated string literals directly
-</xsl:text>
-    <xsl:text>       this.active_style = this.active_elt ? this.active_elt.style.cssText : undefined;
-</xsl:text>
-    <xsl:text>       this.inactive_style = this.inactive_elt ? this.inactive_elt.style.cssText : undefined;
-</xsl:text>
-    <xsl:text>
-</xsl:text>
-    <xsl:text>       if (this.active_style &amp;&amp; this.inactive_style) {
-</xsl:text>
-    <xsl:text>           this.active_elt.setAttribute("style", "display:none");
-</xsl:text>
-    <xsl:text>           this.inactive_elt.setAttribute("style", this.inactive_style);
-</xsl:text>
-    <xsl:text>       }
-</xsl:text>
-    <xsl:text>
-</xsl:text>
-    <xsl:text>       this.element.setAttribute("onmousedown", "hmi_widgets[\""+this.element_id+"\"].on_mouse_down(evt)");
-</xsl:text>
-    <xsl:text>       this.element.setAttribute("onmouseup", "hmi_widgets[\""+this.element_id+"\"].on_mouse_up(evt)");
-</xsl:text>
-    <xsl:text>    }
+    <xsl:text>
+</xsl:text>
+    <xsl:text>        this.element.setAttribute("onmousedown", "hmi_widgets["+this.element_id+"].on_mouse_down(evt)");
+</xsl:text>
+    <xsl:text>        this.element.setAttribute("onmouseup", "hmi_widgets["+this.element_id+"].on_mouse_up(evt)");
+</xsl:text>
+    <xsl:text>     }
 </xsl:text>
     <xsl:text>}
 </xsl:text>
@@ -4718,47 +4754,163 @@
 </xsl:text>
     <xsl:text>    range = undefined;
 </xsl:text>
+    <xsl:text>    handle_orig = undefined;
+</xsl:text>
+    <xsl:text>    scroll_size = 10;
+</xsl:text>
+    <xsl:text>    min_size = 0.07;
+</xsl:text>
     <xsl:text>    fi = undefined;
 </xsl:text>
-    <xsl:text>    svg_dist = undefined;
+    <xsl:text>    curr_value = 0;
 </xsl:text>
     <xsl:text>    drag = false;
 </xsl:text>
     <xsl:text>    enTimer = false;
 </xsl:text>
+    <xsl:text>    handle_click = undefined;
+</xsl:text>
+    <xsl:text>    last_drag = false;
+</xsl:text>
     <xsl:text>
 </xsl:text>
     <xsl:text>    dispatch(value) {
 </xsl:text>
+    <xsl:text>        //save current value inside widget
+</xsl:text>
+    <xsl:text>        this.curr_value = value;
+</xsl:text>
+    <xsl:text>
+</xsl:text>
     <xsl:text>        if(this.value_elt)
 </xsl:text>
     <xsl:text>            this.value_elt.textContent = String(value);
 </xsl:text>
     <xsl:text>
 </xsl:text>
-    <xsl:text>        this.update_DOM(value, this.handle_elt);
-</xsl:text>
-    <xsl:text>
+    <xsl:text>        //don't update if draging and setpoint ghost doesn't exist
+</xsl:text>
+    <xsl:text>        if(!this.drag || (this.setpoint_elt != undefined)){
+</xsl:text>
+    <xsl:text>            this.update_DOM(value, this.handle_elt);
+</xsl:text>
+    <xsl:text>        }
 </xsl:text>
     <xsl:text>    }
 </xsl:text>
     <xsl:text>
 </xsl:text>
-    <xsl:text>    last_drag = false;
-</xsl:text>
-    <xsl:text>
-</xsl:text>
     <xsl:text>    update_DOM(value, elt){
 </xsl:text>
     <xsl:text>        let [min,max,start,totallength] = this.range;
 </xsl:text>
-    <xsl:text>        let length = Math.max(0,Math.min(totallength,(Number(value)-min)*totallength/(max-min)));
-</xsl:text>
-    <xsl:text>        let tip = this.range_elt.getPointAtLength(length);
-</xsl:text>
-    <xsl:text>        elt.setAttribute('transform',"translate("+(tip.x-start.x)+","+(tip.y-start.y)+")");
-</xsl:text>
-    <xsl:text>
+    <xsl:text>        // check if handle is resizeable
+</xsl:text>
+    <xsl:text>        if (this.scroll_size != undefined){ //size changes
+</xsl:text>
+    <xsl:text>            //get parameters
+</xsl:text>
+    <xsl:text>            let length = Math.max(min,Math.min(max,(Number(value)-min)*max/(max-min)));
+</xsl:text>
+    <xsl:text>            let tip = this.range_elt.getPointAtLength(length);
+</xsl:text>
+    <xsl:text>            let handle_min = totallength*this.min_size;
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>            let step = 1;
+</xsl:text>
+    <xsl:text>            //check if range is bigger than  max displayed and recalculate step
+</xsl:text>
+    <xsl:text>            if ((totallength/handle_min) &lt; (max-min+1)){
+</xsl:text>
+    <xsl:text>                step = (max-min+1)/(totallength/handle_min-1);
+</xsl:text>
+    <xsl:text>            }
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>            let kx,ky,offseY,offseX = undefined;
+</xsl:text>
+    <xsl:text>            //scale on x or y axes
+</xsl:text>
+    <xsl:text>            if (this.fi &gt; 0.75){
+</xsl:text>
+    <xsl:text>                //get scale factor
+</xsl:text>
+    <xsl:text>                if(step &gt; 1){
+</xsl:text>
+    <xsl:text>                    ky = handle_min/this.handle_orig.height;
+</xsl:text>
+    <xsl:text>                }
+</xsl:text>
+    <xsl:text>                else{
+</xsl:text>
+    <xsl:text>                    ky = (totallength-handle_min*(max-min))/this.handle_orig.height;
+</xsl:text>
+    <xsl:text>                }
+</xsl:text>
+    <xsl:text>                kx = 1;
+</xsl:text>
+    <xsl:text>                //get 0 offset to stay inside range
+</xsl:text>
+    <xsl:text>                offseY = start.y - (this.handle_orig.height + this.handle_orig.y) * ky;
+</xsl:text>
+    <xsl:text>                offseX = 0;
+</xsl:text>
+    <xsl:text>                //get distance from value
+</xsl:text>
+    <xsl:text>                tip.y =this.range_elt.getPointAtLength(0).y - length/step *handle_min;
+</xsl:text>
+    <xsl:text>            }
+</xsl:text>
+    <xsl:text>            else{
+</xsl:text>
+    <xsl:text>                //get scale factor
+</xsl:text>
+    <xsl:text>                if(step &gt; 1){
+</xsl:text>
+    <xsl:text>                    kx = handle_min/this.handle_orig.width;
+</xsl:text>
+    <xsl:text>                }
+</xsl:text>
+    <xsl:text>                else{
+</xsl:text>
+    <xsl:text>                    kx = (totallength-handle_min*(max-min))/this.handle_orig.width;
+</xsl:text>
+    <xsl:text>                }
+</xsl:text>
+    <xsl:text>                ky = 1;
+</xsl:text>
+    <xsl:text>                //get 0 offset to stay inside range
+</xsl:text>
+    <xsl:text>                offseX = start.x - (this.handle_orig.x * kx);
+</xsl:text>
+    <xsl:text>                offseY = 0;
+</xsl:text>
+    <xsl:text>                //get distance from value
+</xsl:text>
+    <xsl:text>                tip.x =this.range_elt.getPointAtLength(0).x + length/step *handle_min;
+</xsl:text>
+    <xsl:text>            }
+</xsl:text>
+    <xsl:text>            elt.setAttribute('transform',"matrix("+(kx)+" 0 0 "+(ky)+" "+(tip.x-start.x+offseX)+" "+(tip.y-start.y+offseY)+")");
+</xsl:text>
+    <xsl:text>        }
+</xsl:text>
+    <xsl:text>        else{ //size stays the same
+</xsl:text>
+    <xsl:text>            let length = Math.max(0,Math.min(totallength,(Number(value)-min)*totallength/(max-min)));
+</xsl:text>
+    <xsl:text>            let tip = this.range_elt.getPointAtLength(length);
+</xsl:text>
+    <xsl:text>            elt.setAttribute('transform',"translate("+(tip.x-start.x)+","+(tip.y-start.y)+")");
+</xsl:text>
+    <xsl:text>        }
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>        // show or hide ghost if exists
 </xsl:text>
     <xsl:text>        if(this.setpoint_elt != undefined){
 </xsl:text>
@@ -4786,6 +4938,8 @@
 </xsl:text>
     <xsl:text>    on_release(evt) {
 </xsl:text>
+    <xsl:text>        //unbind events
+</xsl:text>
     <xsl:text>        window.removeEventListener("touchmove", this.on_bound_drag, true);
 </xsl:text>
     <xsl:text>        window.removeEventListener("mousemove", this.on_bound_drag, true);
@@ -4798,26 +4952,38 @@
 </xsl:text>
     <xsl:text>        window.removeEventListener("touchcancel", this.bound_on_release, true);
 </xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>        //reset drag flag
+</xsl:text>
     <xsl:text>        if(this.drag){
 </xsl:text>
     <xsl:text>            this.drag = false;
 </xsl:text>
     <xsl:text>        }
 </xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>        // get final position
+</xsl:text>
     <xsl:text>        this.update_position(evt);
 </xsl:text>
+    <xsl:text>
+</xsl:text>
     <xsl:text>    }
 </xsl:text>
     <xsl:text>
 </xsl:text>
-    <xsl:text>
-</xsl:text>
     <xsl:text>    on_drag(evt){
 </xsl:text>
+    <xsl:text>        //ignore drag event for X amount of time and if not selected
+</xsl:text>
     <xsl:text>        if(this.enTimer &amp;&amp; this.drag){
 </xsl:text>
     <xsl:text>            this.update_position(evt);
 </xsl:text>
+    <xsl:text>
+</xsl:text>
     <xsl:text>            //reset timer
 </xsl:text>
     <xsl:text>            this.enTimer = false;
@@ -4834,16 +5000,18 @@
 </xsl:text>
     <xsl:text>        var html_dist = 0;
 </xsl:text>
+    <xsl:text>        let [min,max,start,totallength] = this.range;
+</xsl:text>
     <xsl:text>
 </xsl:text>
     <xsl:text>        //calculate size of widget in html
 </xsl:text>
     <xsl:text>        var range_borders = this.range_elt.getBoundingClientRect();
 </xsl:text>
+    <xsl:text>        var [minX,minY,maxX,maxY] = [range_borders.left,range_borders.bottom,range_borders.right,range_borders.top];
+</xsl:text>
     <xsl:text>        var range_length = Math.sqrt( range_borders.height*range_borders.height + range_borders.width*range_borders.width );
 </xsl:text>
-    <xsl:text>        var [minX,minY,maxX,maxY] = [range_borders.left,range_borders.bottom,range_borders.right,range_borders.top];
-</xsl:text>
     <xsl:text>
 </xsl:text>
     <xsl:text>        //get range and mouse coordinates
@@ -4870,112 +5038,240 @@
 </xsl:text>
     <xsl:text>
 </xsl:text>
-    <xsl:text>        //get handle distance from mouse position
-</xsl:text>
-    <xsl:text>        if (minX &gt; mouseX &amp;&amp; minY &lt; mouseY){
-</xsl:text>
-    <xsl:text>            html_dist = 0;
+    <xsl:text>        // calculate position
+</xsl:text>
+    <xsl:text>        if (this.handle_click){ //if clicked on handle
+</xsl:text>
+    <xsl:text>            let moveDist = 0, resizeAdd = 0;
+</xsl:text>
+    <xsl:text>            let range_percent = 1;
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>            //set paramters for resizeable handle
+</xsl:text>
+    <xsl:text>            if (this.scroll_size != undefined){
+</xsl:text>
+    <xsl:text>                // add one more object to stay inside range
+</xsl:text>
+    <xsl:text>                resizeAdd = 1;
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>                //chack if range is bigger than display option and
+</xsl:text>
+    <xsl:text>                // calculate percent of range with out handle
+</xsl:text>
+    <xsl:text>                if(((max/(max*this.min_size)) &lt; (max-min+1))){
+</xsl:text>
+    <xsl:text>                    range_percent = 1-this.min_size;
+</xsl:text>
+    <xsl:text>                }
+</xsl:text>
+    <xsl:text>                else{
+</xsl:text>
+    <xsl:text>                    range_percent = 1-(max-max*this.min_size*(max-min))/max;
+</xsl:text>
+    <xsl:text>                }
+</xsl:text>
+    <xsl:text>            }
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>            //calculate value difference on x or y axis
+</xsl:text>
+    <xsl:text>            if(this.fi &gt; 0.7){
+</xsl:text>
+    <xsl:text>                moveDist = ((max-min+resizeAdd)/(range_length*range_percent))*((this.handle_click[1]-mouseY)/Math.sin(this.fi));
+</xsl:text>
+    <xsl:text>            }
+</xsl:text>
+    <xsl:text>            else{
+</xsl:text>
+    <xsl:text>                moveDist = ((max-min+resizeAdd)/(range_length*range_percent))*((mouseX-this.handle_click[0])/Math.cos(this.fi));
+</xsl:text>
+    <xsl:text>            }
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>            this.curr_value = Math.ceil(this.handle_click[2] + moveDist);
 </xsl:text>
     <xsl:text>        }
 </xsl:text>
-    <xsl:text>        else if (maxX &lt; mouseX &amp;&amp; maxY &gt; mouseY){
-</xsl:text>
-    <xsl:text>            html_dist = range_length;
+    <xsl:text>        else{ //if clicked on widget
+</xsl:text>
+    <xsl:text>            //get handle distance from mouse position
+</xsl:text>
+    <xsl:text>            if (minX &gt; mouseX &amp;&amp; minY &lt; mouseY){
+</xsl:text>
+    <xsl:text>                html_dist = 0;
+</xsl:text>
+    <xsl:text>            }
+</xsl:text>
+    <xsl:text>            else if (maxX &lt; mouseX &amp;&amp; maxY &gt; mouseY){
+</xsl:text>
+    <xsl:text>                html_dist = range_length;
+</xsl:text>
+    <xsl:text>            }
+</xsl:text>
+    <xsl:text>            else{
+</xsl:text>
+    <xsl:text>                if(this.fi &gt; 0.7){
+</xsl:text>
+    <xsl:text>                    html_dist = (minY - mouseY)/Math.sin(this.fi);
+</xsl:text>
+    <xsl:text>                }
+</xsl:text>
+    <xsl:text>                else{
+</xsl:text>
+    <xsl:text>                    html_dist = (mouseX - minX)/Math.cos(this.fi);
+</xsl:text>
+    <xsl:text>                }
+</xsl:text>
+    <xsl:text>            }
+</xsl:text>
+    <xsl:text>            //calculate distance
+</xsl:text>
+    <xsl:text>            this.curr_value=Math.ceil((html_dist/range_length)*(this.range[1]-this.range[0])+this.range[0]);
 </xsl:text>
     <xsl:text>        }
 </xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>        //check if in range
+</xsl:text>
+    <xsl:text>        if (this.curr_value &gt; max){
+</xsl:text>
+    <xsl:text>            this.curr_value = max;
+</xsl:text>
+    <xsl:text>        }
+</xsl:text>
+    <xsl:text>        else if (this.curr_value &lt; min){
+</xsl:text>
+    <xsl:text>            this.curr_value = min;
+</xsl:text>
+    <xsl:text>        }
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>        this.apply_hmi_value(0, this.curr_value);
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>        //redraw handle
+</xsl:text>
+    <xsl:text>        this.request_animate();
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>    }
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>    animate(){
+</xsl:text>
+    <xsl:text>        // redraw handle on screen refresh
+</xsl:text>
+    <xsl:text>        // check if setpoint(ghost) handle exsist otherwise update main handle
+</xsl:text>
+    <xsl:text>        if(this.setpoint_elt != undefined){
+</xsl:text>
+    <xsl:text>            this.update_DOM(this.curr_value, this.setpoint_elt);
+</xsl:text>
+    <xsl:text>        }
+</xsl:text>
     <xsl:text>        else{
 </xsl:text>
-    <xsl:text>            // calculate distace
-</xsl:text>
-    <xsl:text>            if(this.fi &gt; 0.7){
-</xsl:text>
-    <xsl:text>                html_dist = (minY - mouseY)/Math.sin(this.fi);
+    <xsl:text>            this.update_DOM(this.curr_value, this.handle_elt);
+</xsl:text>
+    <xsl:text>        }
+</xsl:text>
+    <xsl:text>    }
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>    on_select(evt){
+</xsl:text>
+    <xsl:text>        //enable drag flag and timer
+</xsl:text>
+    <xsl:text>        this.drag = true;
+</xsl:text>
+    <xsl:text>        this.enTimer = true;
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>        //bind events
+</xsl:text>
+    <xsl:text>        window.addEventListener("touchmove", this.on_bound_drag, true);
+</xsl:text>
+    <xsl:text>        window.addEventListener("mousemove", this.on_bound_drag, true);
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>        window.addEventListener("mouseup", this.bound_on_release, true)
+</xsl:text>
+    <xsl:text>        window.addEventListener("touchend", this.bound_on_release, true);
+</xsl:text>
+    <xsl:text>        window.addEventListener("touchcancel", this.bound_on_release, true);
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>        // check if handle was pressed
+</xsl:text>
+    <xsl:text>        if (evt.currentTarget == this.handle_elt){
+</xsl:text>
+    <xsl:text>            //get mouse position on the handle
+</xsl:text>
+    <xsl:text>            let mouseX = undefined;
+</xsl:text>
+    <xsl:text>            let mouseY = undefined;
+</xsl:text>
+    <xsl:text>            if (evt.type.startsWith("touch")){
+</xsl:text>
+    <xsl:text>                mouseX = Math.ceil(evt.touches[0].clientX);
+</xsl:text>
+    <xsl:text>                mouseY = Math.ceil(evt.touches[0].clientY);
 </xsl:text>
     <xsl:text>            }
 </xsl:text>
     <xsl:text>            else{
 </xsl:text>
-    <xsl:text>                html_dist = (mouseX - minX)/Math.cos(this.fi);
+    <xsl:text>                mouseX = evt.pageX;
+</xsl:text>
+    <xsl:text>                mouseY = evt.pageY;
 </xsl:text>
     <xsl:text>            }
 </xsl:text>
-    <xsl:text>
-</xsl:text>
-    <xsl:text>            //check if in range
-</xsl:text>
-    <xsl:text>            if (html_dist &gt; range_length){
-</xsl:text>
-    <xsl:text>                html_dist = range_length;
-</xsl:text>
-    <xsl:text>            }
-</xsl:text>
-    <xsl:text>            else if (html_dist &lt; 0){
-</xsl:text>
-    <xsl:text>                html_dist = 0;
-</xsl:text>
-    <xsl:text>            }
-</xsl:text>
-    <xsl:text>
+    <xsl:text>            //save coordinates and orig value
+</xsl:text>
+    <xsl:text>            this.handle_click = [mouseX,mouseY,this.curr_value];
 </xsl:text>
     <xsl:text>        }
 </xsl:text>
-    <xsl:text>
-</xsl:text>
-    <xsl:text>        this.svg_dist=Math.ceil((html_dist/range_length)*this.range[1]);
-</xsl:text>
-    <xsl:text>
-</xsl:text>
-    <xsl:text>        this.apply_hmi_value(0, this.svg_dist);
-</xsl:text>
-    <xsl:text>
-</xsl:text>
-    <xsl:text>        // update ghost cursor
-</xsl:text>
-    <xsl:text>        if(this.setpoint_elt != undefined){
-</xsl:text>
-    <xsl:text>            this.request_animate();
+    <xsl:text>        else{
+</xsl:text>
+    <xsl:text>            // get new handle position and reset if handle was not pressed
+</xsl:text>
+    <xsl:text>            this.handle_click = undefined;
+</xsl:text>
+    <xsl:text>            this.update_position(evt);
 </xsl:text>
     <xsl:text>        }
 </xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>        //prevent next events
+</xsl:text>
+    <xsl:text>        evt.stopPropagation();
+</xsl:text>
     <xsl:text>    }
 </xsl:text>
     <xsl:text>
 </xsl:text>
-    <xsl:text>    animate(){
-</xsl:text>
-    <xsl:text>        this.update_DOM(this.svg_dist, this.setpoint_elt);
-</xsl:text>
-    <xsl:text>    }
-</xsl:text>
-    <xsl:text>
-</xsl:text>
-    <xsl:text>    on_select(evt){
-</xsl:text>
-    <xsl:text>        this.drag = true;
-</xsl:text>
-    <xsl:text>        this.enTimer = true;
-</xsl:text>
-    <xsl:text>        window.addEventListener("touchmove", this.on_bound_drag, true);
-</xsl:text>
-    <xsl:text>        window.addEventListener("mousemove", this.on_bound_drag, true);
-</xsl:text>
-    <xsl:text>
-</xsl:text>
-    <xsl:text>        window.addEventListener("mouseup", this.bound_on_release, true)
-</xsl:text>
-    <xsl:text>        window.addEventListener("touchend", this.bound_on_release, true);
-</xsl:text>
-    <xsl:text>        window.addEventListener("touchcancel", this.bound_on_release, true);
-</xsl:text>
-    <xsl:text>        this.update_position(evt);
-</xsl:text>
-    <xsl:text>    }
-</xsl:text>
-    <xsl:text>
-</xsl:text>
     <xsl:text>    init() {
 </xsl:text>
+    <xsl:text>        //set min max value if not defined
+</xsl:text>
     <xsl:text>        let min = this.min_elt ?
 </xsl:text>
     <xsl:text>                    Number(this.min_elt.textContent) :
@@ -4990,6 +5286,10 @@
 </xsl:text>
     <xsl:text>
 </xsl:text>
+    <xsl:text>        // save initial parameters
+</xsl:text>
+    <xsl:text>        this.range_elt.style.strokeMiterlimit="0";
+</xsl:text>
     <xsl:text>        this.range = [min, max, this.range_elt.getPointAtLength(0),this.range_elt.getTotalLength()];
 </xsl:text>
     <xsl:text>        let start = this.range_elt.getPointAtLength(0);
@@ -4998,7 +5298,11 @@
 </xsl:text>
     <xsl:text>        this.fi = Math.atan2(start.y-end.y, end.x-start.x);
 </xsl:text>
-    <xsl:text>
+    <xsl:text>        this.handle_orig = this.handle_elt.getBBox();
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>        //bind functions
 </xsl:text>
     <xsl:text>        this.bound_on_select = this.on_select.bind(this);
 </xsl:text>
@@ -5008,6 +5312,8 @@
 </xsl:text>
     <xsl:text>
 </xsl:text>
+    <xsl:text>        this.handle_elt.addEventListener("mousedown", this.bound_on_select);
+</xsl:text>
     <xsl:text>        this.element.addEventListener("mousedown", this.bound_on_select);
 </xsl:text>
     <xsl:text>        this.element.addEventListener("touchstart", this.bound_on_select);
@@ -5119,38 +5425,56 @@
 </xsl:text>
     <xsl:text>    dispatch(value) {
 </xsl:text>
-    <xsl:text>        this.state = value;
+    <xsl:text>        if(this.state != value){
+</xsl:text>
+    <xsl:text>            this.state = value;
+</xsl:text>
+    <xsl:text>            if (this.state) {
+</xsl:text>
+    <xsl:text>                this.active_elt.setAttribute("style", this.active_style);
+</xsl:text>
+    <xsl:text>                this.inactive_elt.setAttribute("style", "display:none");
+</xsl:text>
+    <xsl:text>            } else {
+</xsl:text>
+    <xsl:text>                this.inactive_elt.setAttribute("style", this.inactive_style);
+</xsl:text>
+    <xsl:text>                this.active_elt.setAttribute("style", "display:none");
+</xsl:text>
+    <xsl:text>            }
+</xsl:text>
+    <xsl:text>        }
+</xsl:text>
+    <xsl:text>    }
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>    on_click(evt) {
 </xsl:text>
     <xsl:text>        if (this.state) {
 </xsl:text>
+    <xsl:text>            this.inactive_elt.setAttribute("style", this.inactive_style);
+</xsl:text>
+    <xsl:text>            this.active_elt.setAttribute("style", "display:none");
+</xsl:text>
+    <xsl:text>            this.state = 0;
+</xsl:text>
+    <xsl:text>        } else {
+</xsl:text>
     <xsl:text>            this.active_elt.setAttribute("style", this.active_style);
 </xsl:text>
     <xsl:text>            this.inactive_elt.setAttribute("style", "display:none");
 </xsl:text>
-    <xsl:text>            this.state = 0;
-</xsl:text>
-    <xsl:text>        } else {
-</xsl:text>
-    <xsl:text>            this.inactive_elt.setAttribute("style", this.inactive_style);
-</xsl:text>
-    <xsl:text>            this.active_elt.setAttribute("style", "display:none");
-</xsl:text>
     <xsl:text>            this.state = 1;
 </xsl:text>
     <xsl:text>        }
 </xsl:text>
+    <xsl:text>        this.apply_hmi_value(0, this.state);
+</xsl:text>
     <xsl:text>    }
 </xsl:text>
     <xsl:text>
 </xsl:text>
-    <xsl:text>    on_click(evt) {
-</xsl:text>
-    <xsl:text>        this.apply_hmi_value(0, this.state);
-</xsl:text>
-    <xsl:text>    }
-</xsl:text>
-    <xsl:text>
-</xsl:text>
     <xsl:text>    init() {
 </xsl:text>
     <xsl:text>        this.active_style = this.active_elt.style.cssText;
@@ -5159,6 +5483,10 @@
 </xsl:text>
     <xsl:text>        this.element.setAttribute("onclick", "hmi_widgets['"+this.element_id+"'].on_click(evt)");
 </xsl:text>
+    <xsl:text>        this.inactive_elt.setAttribute("style", this.inactive_style);
+</xsl:text>
+    <xsl:text>        this.active_elt.setAttribute("style", "display:none");
+</xsl:text>
     <xsl:text>    }
 </xsl:text>
     <xsl:text>}
--- a/svghmi/widget_button.ysl2	Tue Sep 15 13:57:06 2020 +0200
+++ b/svghmi/widget_button.ysl2	Thu Sep 17 11:30:22 2020 +0200
@@ -5,45 +5,54 @@
     class ButtonWidget extends Widget{
         frequency = 5;
         state = 0;
+        plc_lock = false;
         active_style = undefined;
         inactive_style = undefined;
 
-        // TODO decouple update of DOM from event (i.e use animate())
-
-
-        // TODO State of the button should distinguish UI feedbak from current PLC value
-
-        on_mouse_down(evt) {
-            if (this.active_style && this.inactive_style) {
-                this.active_elt.setAttribute("style", this.active_style);
-                this.inactive_elt.setAttribute("style", "display:none");
+        dispatch(value) {
+            if(value){
+                this.button_release();
             }
-            this.apply_hmi_value(0, 1);
-            // TODO inhibit all mouse/touch events except mouse up (in other word grab cursor)
         }
 
-        on_mouse_up(evt) {
+         on_mouse_down(evt) {
+             if (this.active_style && this.inactive_style) {
+                 this.active_elt.setAttribute("style", this.active_style);
+                 this.inactive_elt.setAttribute("style", "display:none");
+             }
+             this.apply_hmi_value(0, 1);
+             this.plc_lock = false;
+         }
+
+         on_mouse_up(evt) {
+             this.button_release();
+         }
+
+         button_release(){
+            if(!this.plc_lock){
+                this.plc_lock = true;
+            }
+            else{
+                if (this.active_style && this.inactive_style) {
+                     this.active_elt.setAttribute("style", "display:none");
+                     this.inactive_elt.setAttribute("style", this.inactive_style);
+                 }
+                 this.apply_hmi_value(0, 0);
+            }
+         }
+
+         init() {
+            this.active_style = this.active_elt ? this.active_elt.style.cssText : undefined;
+            this.inactive_style = this.inactive_elt ? this.inactive_elt.style.cssText : undefined;
+
             if (this.active_style && this.inactive_style) {
                 this.active_elt.setAttribute("style", "display:none");
                 this.inactive_elt.setAttribute("style", this.inactive_style);
             }
-            this.apply_hmi_value(0, 0);
-            // TODO release inhibited events 
-        }
 
-        init() {
-           // TODO : move to widget_defs so that we can have generated string literals directly
-           this.active_style = this.active_elt ? this.active_elt.style.cssText : undefined;
-           this.inactive_style = this.inactive_elt ? this.inactive_elt.style.cssText : undefined;
-
-           if (this.active_style && this.inactive_style) {
-               this.active_elt.setAttribute("style", "display:none");
-               this.inactive_elt.setAttribute("style", this.inactive_style);
-           }
-
-           this.element.setAttribute("onmousedown", "hmi_widgets[\""+this.element_id+"\"].on_mouse_down(evt)");
-           this.element.setAttribute("onmouseup", "hmi_widgets[\""+this.element_id+"\"].on_mouse_up(evt)");
-        }
+            this.element.setAttribute("onmousedown", "hmi_widgets["+this.element_id+"].on_mouse_down(evt)");
+            this.element.setAttribute("onmouseup", "hmi_widgets["+this.element_id+"].on_mouse_up(evt)");
+         }
     }
     ||
 }
--- a/svghmi/widget_slider.ysl2	Tue Sep 15 13:57:06 2020 +0200
+++ b/svghmi/widget_slider.ysl2	Thu Sep 17 11:30:22 2020 +0200
@@ -5,27 +5,85 @@
     class SliderWidget extends Widget{
         frequency = 5;
         range = undefined;
+        handle_orig = undefined;
+        scroll_size = 10;
+        min_size = 0.07;
         fi = undefined;
-        svg_dist = undefined;
+        curr_value = 0;
         drag = false;
         enTimer = false;
+        handle_click = undefined;
+        last_drag = false;
 
         dispatch(value) {
+            //save current value inside widget
+            this.curr_value = value;
+
             if(this.value_elt)
                 this.value_elt.textContent = String(value);
 
-            this.update_DOM(value, this.handle_elt);
-
-        }
-
-        last_drag = false;
+            //don't update if draging and setpoint ghost doesn't exist
+            if(!this.drag || (this.setpoint_elt != undefined)){
+                this.update_DOM(value, this.handle_elt);
+            }
+        }
 
         update_DOM(value, elt){
             let [min,max,start,totallength] = this.range;
-            let length = Math.max(0,Math.min(totallength,(Number(value)-min)*totallength/(max-min)));
-            let tip = this.range_elt.getPointAtLength(length);
-            elt.setAttribute('transform',"translate("+(tip.x-start.x)+","+(tip.y-start.y)+")");
-
+            // check if handle is resizeable
+            if (this.scroll_size != undefined){ //size changes
+                //get parameters
+                let length = Math.max(min,Math.min(max,(Number(value)-min)*max/(max-min)));
+                let tip = this.range_elt.getPointAtLength(length);
+                let handle_min = totallength*this.min_size;
+
+                let step = 1;
+                //check if range is bigger than  max displayed and recalculate step
+                if ((totallength/handle_min) < (max-min+1)){
+                    step = (max-min+1)/(totallength/handle_min-1);
+                }
+
+                let kx,ky,offseY,offseX = undefined;
+                //scale on x or y axes
+                if (this.fi > 0.75){
+                    //get scale factor
+                    if(step > 1){
+                        ky = handle_min/this.handle_orig.height;
+                    }
+                    else{
+                        ky = (totallength-handle_min*(max-min))/this.handle_orig.height;
+                    }
+                    kx = 1;
+                    //get 0 offset to stay inside range
+                    offseY = start.y - (this.handle_orig.height + this.handle_orig.y) * ky;
+                    offseX = 0;
+                    //get distance from value
+                    tip.y =this.range_elt.getPointAtLength(0).y - length/step *handle_min;
+                }
+                else{
+                    //get scale factor
+                    if(step > 1){
+                        kx = handle_min/this.handle_orig.width;
+                    }
+                    else{
+                        kx = (totallength-handle_min*(max-min))/this.handle_orig.width;
+                    }
+                    ky = 1;
+                    //get 0 offset to stay inside range
+                    offseX = start.x - (this.handle_orig.x * kx);
+                    offseY = 0;
+                    //get distance from value
+                    tip.x =this.range_elt.getPointAtLength(0).x + length/step *handle_min;
+                }
+                elt.setAttribute('transform',"matrix("+(kx)+" 0 0 "+(ky)+" "+(tip.x-start.x+offseX)+" "+(tip.y-start.y+offseY)+")");
+            }
+            else{ //size stays the same
+                let length = Math.max(0,Math.min(totallength,(Number(value)-min)*totallength/(max-min)));
+                let tip = this.range_elt.getPointAtLength(length);
+                elt.setAttribute('transform',"translate("+(tip.x-start.x)+","+(tip.y-start.y)+")");
+            }
+
+            // show or hide ghost if exists
             if(this.setpoint_elt != undefined){
                 if(this.last_drag!= this.drag){
                     if(this.drag){
@@ -39,22 +97,29 @@
         }
 
         on_release(evt) {
+            //unbind events
             window.removeEventListener("touchmove", this.on_bound_drag, true);
             window.removeEventListener("mousemove", this.on_bound_drag, true);
 
             window.removeEventListener("mouseup", this.bound_on_release, true)
             window.removeEventListener("touchend", this.bound_on_release, true);
             window.removeEventListener("touchcancel", this.bound_on_release, true);
+
+            //reset drag flag
             if(this.drag){
                 this.drag = false;
             }
+
+            // get final position
             this.update_position(evt);
-        }
-
+
+        }
 
         on_drag(evt){
+            //ignore drag event for X amount of time and if not selected
             if(this.enTimer && this.drag){
                 this.update_position(evt);
+
                 //reset timer
                 this.enTimer = false;
                 setTimeout("{hmi_widgets['"+this.element_id+"'].enTimer = true;}", 100);
@@ -63,11 +128,12 @@
 
         update_position(evt){
             var html_dist = 0;
+            let [min,max,start,totallength] = this.range;
 
             //calculate size of widget in html
             var range_borders = this.range_elt.getBoundingClientRect();
+            var [minX,minY,maxX,maxY] = [range_borders.left,range_borders.bottom,range_borders.right,range_borders.top];
             var range_length = Math.sqrt( range_borders.height*range_borders.height + range_borders.width*range_borders.width );
-            var [minX,minY,maxX,maxY] = [range_borders.left,range_borders.bottom,range_borders.right,range_borders.top];
 
             //get range and mouse coordinates
             var mouseX = undefined;
@@ -81,59 +147,123 @@
                 mouseY = evt.pageY;
             }
 
-            //get handle distance from mouse position
-            if (minX > mouseX && minY < mouseY){
-                html_dist = 0;
-            }
-            else if (maxX < mouseX && maxY > mouseY){
-                html_dist = range_length;
+            // calculate position
+            if (this.handle_click){ //if clicked on handle
+                let moveDist = 0, resizeAdd = 0;
+                let range_percent = 1;
+
+                //set paramters for resizeable handle
+                if (this.scroll_size != undefined){
+                    // add one more object to stay inside range
+                    resizeAdd = 1;
+
+                    //chack if range is bigger than display option and
+                    // calculate percent of range with out handle
+                    if(((max/(max*this.min_size)) < (max-min+1))){
+                        range_percent = 1-this.min_size;
+                    }
+                    else{
+                        range_percent = 1-(max-max*this.min_size*(max-min))/max;
+                    }
+                }
+
+                //calculate value difference on x or y axis
+                if(this.fi > 0.7){
+                    moveDist = ((max-min+resizeAdd)/(range_length*range_percent))*((this.handle_click[1]-mouseY)/Math.sin(this.fi));
+                }
+                else{
+                    moveDist = ((max-min+resizeAdd)/(range_length*range_percent))*((mouseX-this.handle_click[0])/Math.cos(this.fi));
+                }
+
+                this.curr_value = Math.ceil(this.handle_click[2] + moveDist);
+            }
+            else{ //if clicked on widget
+                //get handle distance from mouse position
+                if (minX > mouseX && minY < mouseY){
+                    html_dist = 0;
+                }
+                else if (maxX < mouseX && maxY > mouseY){
+                    html_dist = range_length;
+                }
+                else{
+                    if(this.fi > 0.7){
+                        html_dist = (minY - mouseY)/Math.sin(this.fi);
+                    }
+                    else{
+                        html_dist = (mouseX - minX)/Math.cos(this.fi);
+                    }
+                }
+                //calculate distance
+                this.curr_value=Math.ceil((html_dist/range_length)*(this.range[1]-this.range[0])+this.range[0]);
+            }
+
+            //check if in range
+            if (this.curr_value > max){
+                this.curr_value = max;
+            }
+            else if (this.curr_value < min){
+                this.curr_value = min;
+            }
+
+            this.apply_hmi_value(0, this.curr_value);
+
+            //redraw handle
+            this.request_animate();
+
+        }
+
+        animate(){
+            // redraw handle on screen refresh
+            // check if setpoint(ghost) handle exsist otherwise update main handle
+            if(this.setpoint_elt != undefined){
+                this.update_DOM(this.curr_value, this.setpoint_elt);
             }
             else{
-                // calculate distace
-                if(this.fi > 0.7){
-                    html_dist = (minY - mouseY)/Math.sin(this.fi);
-                }
-                else{
-                    html_dist = (mouseX - minX)/Math.cos(this.fi);
-                }
-
-                //check if in range
-                if (html_dist > range_length){
-                    html_dist = range_length;
-                }
-                else if (html_dist < 0){
-                    html_dist = 0;
-                }
-
-            }
-
-            this.svg_dist=Math.ceil((html_dist/range_length)*this.range[1]);
-
-            this.apply_hmi_value(0, this.svg_dist);
-
-            // update ghost cursor
-            if(this.setpoint_elt != undefined){
-                this.request_animate();
-            }
-        }
-
-        animate(){
-            this.update_DOM(this.svg_dist, this.setpoint_elt);
+                this.update_DOM(this.curr_value, this.handle_elt);
+            }
         }
 
         on_select(evt){
+            //enable drag flag and timer
             this.drag = true;
             this.enTimer = true;
+
+            //bind events
             window.addEventListener("touchmove", this.on_bound_drag, true);
             window.addEventListener("mousemove", this.on_bound_drag, true);
 
             window.addEventListener("mouseup", this.bound_on_release, true)
             window.addEventListener("touchend", this.bound_on_release, true);
             window.addEventListener("touchcancel", this.bound_on_release, true);
-            this.update_position(evt);
+
+            // check if handle was pressed
+            if (evt.currentTarget == this.handle_elt){
+                //get mouse position on the handle
+                let mouseX = undefined;
+                let mouseY = undefined;
+                if (evt.type.startsWith("touch")){
+                    mouseX = Math.ceil(evt.touches[0].clientX);
+                    mouseY = Math.ceil(evt.touches[0].clientY);
+                }
+                else{
+                    mouseX = evt.pageX;
+                    mouseY = evt.pageY;
+                }
+                //save coordinates and orig value
+                this.handle_click = [mouseX,mouseY,this.curr_value];
+            }
+            else{
+                // get new handle position and reset if handle was not pressed
+                this.handle_click = undefined;
+                this.update_position(evt);
+            }
+
+            //prevent next events
+            evt.stopPropagation();
         }
 
         init() {
+            //set min max value if not defined
             let min = this.min_elt ?
                         Number(this.min_elt.textContent) :
                         this.args.length >= 1 ? this.args[0] : 0;
@@ -141,15 +271,20 @@
                         Number(this.max_elt.textContent) :
                         this.args.length >= 2 ? this.args[1] : 100;
 
+            // save initial parameters
+            this.range_elt.style.strokeMiterlimit="0";
             this.range = [min, max, this.range_elt.getPointAtLength(0),this.range_elt.getTotalLength()];
             let start = this.range_elt.getPointAtLength(0);
             let end = this.range_elt.getPointAtLength(this.range_elt.getTotalLength());
             this.fi = Math.atan2(start.y-end.y, end.x-start.x);
-
+            this.handle_orig = this.handle_elt.getBBox();
+
+            //bind functions
             this.bound_on_select = this.on_select.bind(this);
             this.bound_on_release = this.on_release.bind(this);
             this.on_bound_drag = this.on_drag.bind(this);
 
+            this.handle_elt.addEventListener("mousedown", this.bound_on_select);
             this.element.addEventListener("mousedown", this.bound_on_select);
             this.element.addEventListener("touchstart", this.bound_on_select);
 
--- a/svghmi/widget_tooglebutton.ysl2	Tue Sep 15 13:57:06 2020 +0200
+++ b/svghmi/widget_tooglebutton.ysl2	Thu Sep 17 11:30:22 2020 +0200
@@ -10,19 +10,28 @@
         inactive_style = undefined;
 
         dispatch(value) {
-            this.state = value;
-            if (this.state) {
-                this.active_elt.setAttribute("style", this.active_style);
-                this.inactive_elt.setAttribute("style", "display:none");
-                this.state = 0;
-            } else {
-                this.inactive_elt.setAttribute("style", this.inactive_style);
-                this.active_elt.setAttribute("style", "display:none");
-                this.state = 1;
+            if(this.state != value){
+                this.state = value;
+                if (this.state) {
+                    this.active_elt.setAttribute("style", this.active_style);
+                    this.inactive_elt.setAttribute("style", "display:none");
+                } else {
+                    this.inactive_elt.setAttribute("style", this.inactive_style);
+                    this.active_elt.setAttribute("style", "display:none");
+                }
             }
         }
 
         on_click(evt) {
+            if (this.state) {
+                this.inactive_elt.setAttribute("style", this.inactive_style);
+                this.active_elt.setAttribute("style", "display:none");
+                this.state = 0;
+            } else {
+                this.active_elt.setAttribute("style", this.active_style);
+                this.inactive_elt.setAttribute("style", "display:none");
+                this.state = 1;
+            }
             this.apply_hmi_value(0, this.state);
         }
 
@@ -30,6 +39,8 @@
             this.active_style = this.active_elt.style.cssText;
             this.inactive_style = this.inactive_elt.style.cssText;
             this.element.setAttribute("onclick", "hmi_widgets['"+this.element_id+"'].on_click(evt)");
+            this.inactive_elt.setAttribute("style", this.inactive_style);
+            this.active_elt.setAttribute("style", "display:none");
         }
     }
     ||
--- a/svghmi/widgets_common.ysl2	Tue Sep 15 13:57:06 2020 +0200
+++ b/svghmi/widgets_common.ysl2	Thu Sep 17 11:30:22 2020 +0200
@@ -29,8 +29,10 @@
         choose {
             when "not(@index)" {
                 choose {
-                    when "not(@type)" 
-                        error > Widget «$widget/@type» id="«$eltid»" : No match for path "«@value»" in HMI tree
+                    when "not(@type)" {
+                        warning > Widget «$widget/@type» id="«$eltid»" : No match for path "«@value»" in HMI tree
+                        > undefined`if "position()!=last()" > ,`
+                    }
                     when "@type = 'PAGE_LOCAL'" 
                         > "«@value»"`if "position()!=last()" > ,`
                     when "@type = 'HMI_LOCAL'" 
@@ -152,6 +154,7 @@
             if(!this.unsubscribable)
                 for(let i = 0; i < this.indexes.length; i++) {
                     let index = this.get_variable_index(i);
+                    if(index == undefined) continue;
                     subscribers(index).add(this);
                 }
             need_cache_apply.push(this); 
@@ -161,6 +164,7 @@
             if(!this.unsubscribable) for(let index in this.indexes){
                 /* dispatch current cache in newly opened page widgets */
                 let realindex = this.get_variable_index(index);
+                if(realindex == undefined) continue;
                 let cached_val = cache[realindex];
                 if(cached_val != undefined)
                     this._dispatch(cached_val, cached_val, index);
@@ -178,18 +182,23 @@
             }
             return index;
         }
-        change_hmi_value(index,opstr) {
-            return change_hmi_value(this.get_variable_index(index), opstr);
+        change_hmi_value(index, opstr) {
+            let realindex = this.get_variable_index(index);
+            if(realindex == undefined) return undefined;
+            return change_hmi_value(realindex, opstr);
         }
 
         apply_hmi_value(index, new_val) {
-            return apply_hmi_value(this.get_variable_index(index), new_val);
+            let realindex = this.get_variable_index(index);
+            if(realindex == undefined) return undefined;
+            return apply_hmi_value(realindex, new_val);
         }
 
         new_hmi_value(index, value, oldval) {
             // TODO avoid searching, store index at sub()
             for(let i = 0; i < this.indexes.length; i++) {
                 let refindex = this.get_variable_index(i);
+                if(refindex == undefined) continue;
 
                 if(index == refindex) {
                     this._dispatch(value, oldval, i);
--- a/tests/svghmi_v2/plc.xml	Tue Sep 15 13:57:06 2020 +0200
+++ b/tests/svghmi_v2/plc.xml	Thu Sep 17 11:30:22 2020 +0200
@@ -1,7 +1,7 @@
 <?xml version='1.0' encoding='utf-8'?>
 <project xmlns:ns1="http://www.plcopen.org/xml/tc6_0201" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.plcopen.org/xml/tc6_0201">
   <fileHeader companyName="Unknown" productName="Unnamed" productVersion="1" creationDateTime="2019-08-06T14:23:42"/>
-  <contentHeader name="Unnamed" modificationDateTime="2020-07-30T12:04:22">
+  <contentHeader name="Unnamed" modificationDateTime="2020-09-15T14:59:06">
     <coordinateInfo>
       <fbd>
         <scaling x="5" y="5"/>
@@ -55,6 +55,21 @@
                 <derived name="HMI_INT"/>
               </type>
             </variable>
+            <variable name="Toggle">
+              <type>
+                <derived name="HMI_BOOL"/>
+              </type>
+            </variable>
+            <variable name="Toggle1">
+              <type>
+                <derived name="HMI_BOOL"/>
+              </type>
+            </variable>
+            <variable name="Toggle2">
+              <type>
+                <derived name="HMI_BOOL"/>
+              </type>
+            </variable>
             <variable name="MultistateExt">
               <type>
                 <INT/>
@@ -88,7 +103,7 @@
               <expression>TargetPressure</expression>
             </inVariable>
             <inVariable localId="6" executionOrderId="0" height="25" width="90" negated="false">
-              <position x="155" y="220"/>
+              <position x="130" y="60"/>
               <connectionPointOut>
                 <relPosition x="90" y="10"/>
               </connectionPointOut>
@@ -100,7 +115,9 @@
                 <relPosition x="0" y="10"/>
                 <connection refLocalId="6">
                   <position x="495" y="230"/>
-                  <position x="245" y="230"/>
+                  <position x="367" y="230"/>
+                  <position x="367" y="70"/>
+                  <position x="220" y="70"/>
                 </connection>
               </connectionPointIn>
               <expression>TestLocal</expression>
@@ -190,34 +207,12 @@
               </connectionPointOut>
               <expression>TargetPressure</expression>
             </inVariable>
-            <inOutVariable localId="4" executionOrderId="0" height="30" width="60" negatedOut="false" negatedIn="false">
-              <position x="510" y="80"/>
-              <connectionPointIn>
-                <relPosition x="0" y="15"/>
-                <connection refLocalId="6" formalParameter="OUT">
-                  <position x="510" y="95"/>
-                  <position x="470" y="95"/>
-                </connection>
-              </connectionPointIn>
-              <connectionPointOut>
-                <relPosition x="60" y="15"/>
-              </connectionPointOut>
-              <expression>Sloth</expression>
-            </inOutVariable>
             <block localId="6" typeName="ADD" executionOrderId="0" height="60" width="65">
               <position x="405" y="65"/>
               <inputVariables>
                 <variable formalParameter="IN1">
                   <connectionPointIn>
                     <relPosition x="0" y="30"/>
-                    <connection refLocalId="4">
-                      <position x="405" y="95"/>
-                      <position x="385" y="95"/>
-                      <position x="385" y="50"/>
-                      <position x="580" y="50"/>
-                      <position x="580" y="95"/>
-                      <position x="570" y="95"/>
-                    </connection>
                   </connectionPointIn>
                 </variable>
                 <variable formalParameter="IN2">
@@ -559,6 +554,13 @@
               </connectionPointOut>
               <expression>0</expression>
             </inVariable>
+            <inVariable localId="4" executionOrderId="0" height="30" width="60" negated="false">
+              <position x="510" y="80"/>
+              <connectionPointOut>
+                <relPosition x="60" y="15"/>
+              </connectionPointOut>
+              <expression>Sloth</expression>
+            </inVariable>
           </FBD>
         </body>
       </pou>
--- a/tests/svghmi_v2/svghmi_0@svghmi/svghmi.svg	Tue Sep 15 13:57:06 2020 +0200
+++ b/tests/svghmi_v2/svghmi_0@svghmi/svghmi.svg	Thu Sep 17 11:30:22 2020 +0200
@@ -16,7 +16,7 @@
    version="1.1"
    id="hmi0"
    sodipodi:docname="svghmi.svg"
-   inkscape:version="0.92.3 (2405546, 2018-03-11)"
+   inkscape:version="0.92.5 (0.92.5+68)"
    inkscape:label="Layer">
   <metadata
      id="metadata4542">
@@ -53,17 +53,6 @@
        inkscape:vp_z="1272 : 385 : 1"
        inkscape:persp3d-origin="536 : 237 : 1"
        id="perspective445" />
-    <inkscape:tag
-       id="Set 1"
-       inkscape:label="HMI:AccessList@Admin"
-       inkscape:expanded="true">
-      <inkscape:tagref
-         xlink:href="#text995"
-         id="tagref192" />
-      <inkscape:tagref
-         xlink:href="#g991"
-         id="tagref194" />
-    </inkscape:tag>
     <linearGradient
        inkscape:collect="always"
        id="linearGradient962">
@@ -91,26 +80,6 @@
          id="path924"
          inkscape:connector-curvature="0" />
     </marker>
-    <inkscape:tag
-       id="Set 3"
-       inkscape:expanded="true"
-       inkscape:label="HMI:Translate">
-      <inkscape:tagref
-         xlink:href="#text831"
-         id="tagref1085" />
-      <inkscape:tagref
-         xlink:href="#text827"
-         id="tagref1087" />
-      <inkscape:tagref
-         xlink:href="#text4497"
-         id="tagref1089" />
-      <inkscape:tagref
-         xlink:href="#home_jmp"
-         id="tagref1091" />
-      <inkscape:tagref
-         xlink:href="#setting_jmp"
-         id="tagref1093" />
-    </inkscape:tag>
     <linearGradient
        inkscape:collect="always"
        xlink:href="#linearGradient962"
@@ -137,16 +106,16 @@
      inkscape:pageopacity="0"
      inkscape:pageshadow="2"
      inkscape:document-units="px"
-     inkscape:current-layer="g110-0-9"
+     inkscape:current-layer="hmi0"
      showgrid="false"
      units="px"
      inkscape:zoom="1.4142136"
-     inkscape:cx="437.24009"
-     inkscape:cy="177.36896"
-     inkscape:window-width="1800"
-     inkscape:window-height="836"
-     inkscape:window-x="0"
-     inkscape:window-y="27"
+     inkscape:cx="462.89448"
+     inkscape:cy="318.79031"
+     inkscape:window-width="2503"
+     inkscape:window-height="1416"
+     inkscape:window-x="57"
+     inkscape:window-y="24"
      inkscape:window-maximized="1"
      showguides="true"
      inkscape:guide-bbox="true" />
@@ -200,7 +169,7 @@
          y="-64.195457"
          x="113.27539"
          sodipodi:role="line"
-         id="tspan1409">100</tspan></text>
+         id="tspan1409">10</tspan></text>
     <text
        xml:space="preserve"
        style="font-style:normal;font-weight:normal;font-size:10.58333302px;line-height:125%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#ff6600;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
@@ -1221,7 +1190,7 @@
          y="5.501111"
          x="159.67337"
          sodipodi:role="line"
-         id="tspan1409-1">100</tspan></text>
+         id="tspan1409-1">1000</tspan></text>
     <text
        xml:space="preserve"
        style="font-style:normal;font-weight:normal;font-size:7.78479624px;line-height:125%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#ff6600;fill-opacity:1;stroke:none;stroke-width:0.19461991px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
@@ -1522,4 +1491,66 @@
          y="258.16129"
          style="fill:#ff00ca;fill-opacity:1;stroke:none;stroke-width:2.25346255px;stroke-opacity:1">000</tspan></text>
   </g>
+  <g
+     id="g4791"
+     inkscape:label="HMI:ToggleButton@/TOGGLE2">
+    <rect
+       inkscape:label="inactive"
+       y="46.127251"
+       x="906.51086"
+       height="44.547726"
+       width="45.254833"
+       id="rect4772"
+       style="opacity:1;fill:#ff0015;fill-opacity:1;stroke:none" />
+    <rect
+       inkscape:label="active"
+       y="46.127251"
+       x="906.51086"
+       height="44.547726"
+       width="45.254833"
+       id="rect4772-3"
+       style="opacity:1;fill:#00ff03;fill-opacity:1;stroke:none" />
+  </g>
+  <g
+     transform="translate(-67.175138,-1.0606552)"
+     id="g4791-6"
+     inkscape:label="HMI:ToggleButton@/TOGGLE1">
+    <rect
+       inkscape:label="inactive"
+       y="47.187904"
+       x="906.51086"
+       height="44.547726"
+       width="45.254833"
+       id="rect4772-5"
+       style="opacity:1;fill:#ff0015;fill-opacity:1;stroke:none" />
+    <rect
+       inkscape:label="active"
+       y="47.187904"
+       x="906.51086"
+       height="44.547726"
+       width="45.254833"
+       id="rect4772-3-7"
+       style="opacity:1;fill:#00ff03;fill-opacity:1;stroke:none" />
+  </g>
+  <g
+     transform="translate(63.639613)"
+     id="g4791-3"
+     inkscape:label="HMI:ToggleButton@/TOGGLE">
+    <rect
+       inkscape:label="active"
+       y="46.127251"
+       x="906.51086"
+       height="44.547726"
+       width="45.254833"
+       id="rect4772-3-5"
+       style="opacity:1;fill:#00ff03;fill-opacity:1;stroke:none" />
+    <rect
+       inkscape:label="inactive"
+       y="46.127251"
+       x="906.51086"
+       height="44.547726"
+       width="45.254833"
+       id="rect4772-6"
+       style="opacity:1;fill:#ff0015;fill-opacity:1;stroke:none" />
+  </g>
 </svg>