Numeric Entry Field deferUpdates

Why is there not a deferUpdates option on the Numeric Entry Field?

I would like a change script to change the background of the field to green when the number entered is between a min-max range. Doesn’t seem to be a way to do this until focus is lost. Not idea for an iPad.

Bump. A colleague has a button he's disabling when the numeric input is a certain value, but users are able to click it after entering a number and not pressing enter. The button doesn't actually execute the actions, so it's only aesthetic, but confusing

1 Like

I am also looking for a solution to this.

Don't Reject Updates on Numeric Entry Field | Voters | Inductive Automation

1 Like

Bump - Is there any reason this can’t be implemented? We have many instances where we have forms with a mixture of text and numeric entry fields. We typically bidirectionally bind those values to either session or custom properties to access in our save/submit scripts. In cases where we have the numeric entry field, users have to click off of the component in order for the value they entered to be committed which is non-intuitive. Ideally, all input fields would have this option for a consistent experience.

1 Like

I wanted more control over not only updates, but also a spinner that would continually inc/dec the value if you hold down the button, so I rolled my own:

Here’s the view JSON for anyone interested in trying it:

{
  "custom": {
    "running": true
  },
  "events": {
    "system": {
      "onShutdown": {
        "config": {
          "script": "\tself.custom.running \u003d False"
        },
        "scope": "G",
        "type": "script"
      }
    }
  },
  "params": {
    "buttonStyle": {
      "classes": ""
    },
    "buttonWidth": 0.2,
    "deferUpdates": false,
    "enabled": true,
    "focus": false,
    "increment": 1,
    "isFloat": false,
    "maximum": null,
    "minimum": 0,
    "pressDelay": 0.2,
    "pressDelayAccelerate": 0.015,
    "pressDelayMinimum": 0.03,
    "pressInitialDelay": 0.33,
    "rejectUpdatesWhileFocused": true,
    "style": {
      "borderBottomLeftRadius": 5,
      "borderBottomRightRadius": 5,
      "borderColor": "#000000",
      "borderStyle": "solid",
      "borderTopLeftRadius": 5,
      "borderTopRightRadius": 5,
      "borderWidth": 1,
      "classes": "",
      "overflow": "hidden"
    },
    "textFieldStyle": {
      "borderStyle": "none",
      "classes": "",
      "fontFamily": "monospace",
      "fontSize": "200%",
      "paddingRight": "25%",
      "textAlign": "right"
    },
    "value": 0
  },
  "propConfig": {
    "custom.running": {
      "persistent": true
    },
    "params.buttonStyle": {
      "paramDirection": "input",
      "persistent": true
    },
    "params.buttonWidth": {
      "paramDirection": "input",
      "persistent": true
    },
    "params.deferUpdates": {
      "paramDirection": "input",
      "persistent": true
    },
    "params.enabled": {
      "paramDirection": "input",
      "persistent": true
    },
    "params.focus": {
      "onChange": {
        "enabled": null,
        "script": "\tif currentValue.value and self.custom.running:\n\t\tself.getChild(\"root\").getChild(\"TextField\").focus()\n\t\tself.params.focus \u003d False"
      },
      "paramDirection": "inout",
      "persistent": true
    },
    "params.increment": {
      "paramDirection": "input",
      "persistent": true
    },
    "params.isFloat": {
      "paramDirection": "input",
      "persistent": true
    },
    "params.maximum": {
      "paramDirection": "input",
      "persistent": true
    },
    "params.minimum": {
      "paramDirection": "input",
      "persistent": true
    },
    "params.pressDelay": {
      "paramDirection": "input",
      "persistent": true
    },
    "params.pressDelayAccelerate": {
      "paramDirection": "input",
      "persistent": true
    },
    "params.pressDelayMinimum": {
      "paramDirection": "input",
      "persistent": true
    },
    "params.pressInitialDelay": {
      "paramDirection": "input",
      "persistent": true
    },
    "params.rejectUpdatesWhileFocused": {
      "paramDirection": "input",
      "persistent": true
    },
    "params.style": {
      "paramDirection": "input",
      "persistent": true
    },
    "params.textFieldStyle": {
      "paramDirection": "inout",
      "persistent": true
    },
    "params.value": {
      "binding": {
        "config": {
          "bidirectional": true,
          "path": "/root/TextField.props.text"
        },
        "type": "property"
      },
      "paramDirection": "inout",
      "persistent": true
    }
  },
  "props": {
    "defaultSize": {
      "height": 38,
      "width": 120
    }
  },
  "root": {
    "children": [
      {
        "meta": {
          "name": "TextField"
        },
        "position": {
          "height": 1,
          "width": 1
        },
        "propConfig": {
          "props.deferUpdates": {
            "binding": {
              "config": {
                "path": "view.params.deferUpdates"
              },
              "type": "property"
            }
          },
          "props.enabled": {
            "binding": {
              "config": {
                "path": "view.params.enabled"
              },
              "type": "property"
            }
          },
          "props.rejectUpdatesWhileFocused": {
            "binding": {
              "config": {
                "path": "view.params.rejectUpdatesWhileFocused"
              },
              "type": "property"
            }
          },
          "props.style": {
            "binding": {
              "config": {
                "path": "view.params.textFieldStyle"
              },
              "type": "property"
            }
          },
          "props.text": {
            "onChange": {
              "enabled": null,
              "script": "\tif currentValue.value is not None and currentValue.value !\u003d \u0027\u0027:\n\t\tif self.view.params.isFloat:\n\t\t\tif not Strings.isNumeric(currentValue.value) and Strings.isNumeric(previousValue.value):\n\t\t\t\tself.props.text \u003d previousValue.value\n\t\telse:\n\t\t\tif not Strings.isInteger(currentValue.value) and Strings.isInteger(previousValue.value):\n\t\t\t\tself.props.text \u003d previousValue.value\n\t\tself.parent.increment(0)"
            }
          }
        },
        "props": {
          "spellcheck": false,
          "text": 0
        },
        "type": "ia.input.text-field"
      },
      {
        "custom": {
          "mouseIsDown": false
        },
        "events": {
          "component": {
            "onActionPerformed": {
              "config": {
                "script": "\tself.custom.mouseIsDown \u003d False\n\tself.parent.increment(self.view.params.increment)"
              },
              "scope": "G",
              "type": "script"
            }
          },
          "dom": {
            "onMouseDown": {
              "config": {
                "script": "\tself.custom.mouseIsDown \u003d True"
              },
              "scope": "G",
              "type": "script"
            },
            "onMouseLeave": {
              "config": {
                "script": "\tself.custom.mouseIsDown \u003d False"
              },
              "scope": "G",
              "type": "script"
            },
            "onMouseUp": {
              "config": {
                "script": "\tself.custom.mouseIsDown \u003d False"
              },
              "scope": "G",
              "type": "script"
            }
          }
        },
        "meta": {
          "name": "Up"
        },
        "position": {
          "height": 0.5
        },
        "propConfig": {
          "custom.mouseIsDown": {
            "onChange": {
              "enabled": null,
              "script": "\timport time\n\t\n\tdef inc(delay\u003dself.view.params.pressDelay):\n\t\tif self.custom.mouseIsDown:\n\t\t\tself.parent.increment(self.view.params.increment)\n\t\t\ttime.sleep(delay)\n\t\t\tinc(max(delay - self.view.params.pressDelayAccelerate, self.view.params.pressDelayMinimum))\n\t\t\t\n\tdef asynch():\n\t\ttime.sleep(self.view.params.pressInitialDelay)\n\t\tinc()\n\t\t\n\tif currentValue.value:\n\t\tsystem.util.invokeAsynchronous(asynch)"
            }
          },
          "position.width": {
            "binding": {
              "config": {
                "path": "view.params.buttonWidth"
              },
              "type": "property"
            }
          },
          "position.x": {
            "binding": {
              "config": {
                "expression": "1.001 - {view.params.buttonWidth}"
              },
              "type": "expr"
            }
          },
          "props.style": {
            "binding": {
              "config": {
                "path": "view.params.buttonStyle"
              },
              "type": "property"
            }
          }
        },
        "props": {
          "image": {
            "height": "100%",
            "icon": {
              "path": "material/keyboard_arrow_up"
            },
            "width": "100%"
          },
          "primary": false,
          "text": ""
        },
        "type": "ia.input.button"
      },
      {
        "custom": {
          "mouseIsDown": false
        },
        "events": {
          "component": {
            "onActionPerformed": {
              "config": {
                "script": "\tself.custom.mouseIsDown \u003d False\n\tself.parent.increment(-self.view.params.increment)"
              },
              "scope": "G",
              "type": "script"
            }
          },
          "dom": {
            "onMouseDown": {
              "config": {
                "script": "\tself.custom.mouseIsDown \u003d True"
              },
              "scope": "G",
              "type": "script"
            },
            "onMouseLeave": {
              "config": {
                "script": "\tself.custom.mouseIsDown \u003d False"
              },
              "scope": "G",
              "type": "script"
            },
            "onMouseUp": {
              "config": {
                "script": "\tself.custom.mouseIsDown \u003d False"
              },
              "scope": "G",
              "type": "script"
            }
          }
        },
        "meta": {
          "name": "Down"
        },
        "position": {
          "height": 0.5,
          "y": 0.5
        },
        "propConfig": {
          "custom.mouseIsDown": {
            "onChange": {
              "enabled": null,
              "script": "\timport time\n\t\t\t\n\tdef inc(delay\u003dself.view.params.pressDelay):\n\t\tif self.custom.mouseIsDown:\n\t\t\tself.parent.increment(-self.view.params.increment)\n\t\t\ttime.sleep(delay)\n\t\t\tinc(max(delay - self.view.params.pressDelayAccelerate, self.view.params.pressDelayMinimum))\n\t\t\t\n\tdef asynch():\n\t\ttime.sleep(self.view.params.pressInitialDelay)\n\t\tinc()\n\t\t\n\tif currentValue.value:\n\t\tsystem.util.invokeAsynchronous(asynch)"
            }
          },
          "position.width": {
            "binding": {
              "config": {
                "path": "view.params.buttonWidth"
              },
              "type": "property"
            }
          },
          "position.x": {
            "binding": {
              "config": {
                "expression": "1.001 - {view.params.buttonWidth}"
              },
              "type": "expr"
            }
          },
          "props.style": {
            "binding": {
              "config": {
                "path": "view.params.buttonStyle"
              },
              "type": "property"
            }
          }
        },
        "props": {
          "image": {
            "height": "100%",
            "icon": {
              "path": "material/keyboard_arrow_down"
            },
            "width": "100%"
          },
          "primary": false,
          "text": ""
        },
        "type": "ia.input.button"
      }
    ],
    "meta": {
      "name": "root"
    },
    "propConfig": {
      "props.style": {
        "binding": {
          "config": {
            "path": "view.params.style"
          },
          "type": "property"
        }
      }
    },
    "props": {
      "mode": "percent"
    },
    "scripts": {
      "customMethods": [
        {
          "name": "increment",
          "params": [
            "amt"
          ],
          "script": "\tif self.getChild(\"TextField\").props.text is None or self.getChild(\"TextField\").props.text \u003d\u003d \u0027\u0027:\n\t\tct \u003d 0\n\telif self.view.params.isFloat:\n\t\tct \u003d float(self.getChild(\"TextField\").props.text)\n\telse:\n\t\tct \u003d int(self.getChild(\"TextField\").props.text)\n\tct +\u003d amt\n\tif self.view.params.minimum is not None and ct \u003c self.view.params.minimum:\n\t\tct \u003d self.view.params.minimum\n\telif self.view.params.maximum is not None and ct \u003e self.view.params.maximum:\n\t\tct \u003d self.view.params.maximum\n\tif self.view.params.isFloat:\n\t\tself.getChild(\"TextField\").props.text \u003d float(ct)\n\telse:\n\t\tself.getChild(\"TextField\").props.text \u003d int(ct)"
        }
      ],
      "extensionFunctions": null,
      "messageHandlers": []
    },
    "type": "ia.container.coord"
  }
}
1 Like

Using a gateway script like this will always have the risk of it continuing to increment/decrement, despite taking your finger off the mouse button. It will run into the same issues as momentary buttons. This type of this needs to be done in (locally running) JavaScript, not on the gateway

2 Likes

True. I haven’t noticed any issues with it, but we access our MES via PC/mouse, which may be more reliable in that regard than an HMI…