Ignore propertyChange script on load

I have a template repeater with these components.

The Hours input is bound to a custom property on the template. The Start Time and End Time inputs have a propertyChange script that runs when the dataValue is changed, calculates the difference between the two, and writes it to the custom property that is bound to the Hours input.

This is the script on the Start Time and End Time inputs:

#If the time was changed, save the differennce between the two to the hour input
if (event.propertyName == 'dateValue'):
	entryStart = event.source.parent.getComponent('entryStart').dateValue
	entryEnd = event.source.parent.getComponent('entryEnd').dateValue
	
	# Convert to milliseconds
	entryStartMS = system.date.toMillis(entryStart)
	entryEndMS = system.date.toMillis(entryEnd)
	
	# Time spent
	entryHoursMS = entryEndMS - entryStartMS
	
	# Convert to decimal hours. Make sur to use decimals (float), otherwise the resulting value will not have any. 
	entryHours = entryHoursMS / (1000.0*60.0*60.0)
	
	# Save the value to the custom property. In turn, this will be saved to the input box
	event.source.parent.entryHours = entryHours

This script works great. In the screenshot, because start = end, hours = 0.

The problem is that when this template is originally loaded, I don’t want this script to run. I am using a template repeater. Usually the custom property of the template has a value and I want that to be displayed in the input box until a user changes either the Start Time or End Time. The Start Time and End Time properties are not always used or saved, so I can not rely on my script to calculate the hours every time.

So… Can anyone give me an idea how I can do this? I would like the Hours input to use the custom property value, until a user changes either the start or end time where it will then use the value calculated by the script- and not before.

Alternatively, if someone knows a better way to do this, I’m all ears!

I usually solve these sorts of problems by modifying the bindings supplying the initial values to the entry fields. Specifically, I add custom properties for the “raw” or incoming value, and uni-directionally bind the component’s value field (dateValue in this case) to that custom property. Then in your existing propertyChange scripts, also check if the new value matches the custom property. If so, it wasn’t operator entry, and that event can be ignored.

Don’t forget to write back to the custom property in your code, mimicing a bidirectional binding, or it won’t work reliably.

Thanks for the tips; however, I’m not sure that helps me with this particular issue. I think the setup you described will work if I use a button to calculate the difference between the two times, and save the value then; but because I’m using a property change script, this will either work too often, or not at all.

When I load the template, the script is triggered and overwrites the hour value. If I had saved that value to a custom property, separately, that separate value is not changed (good!). I can now type a value into the hour input box, and that will be saved to my database (also good).

I guess my issue is… I want my script to run when a value is changed, but not when the template is first loaded - loading the template triggers a value change which will overwrite my data coming from the database.

Is there a way to tell the template that at the very very very end of loading, I want it to check the database for new values? This way I could simply let my script overwrite my values, and then at the end of the loading process, overwrite them with the correct ones.

I was going to suggest an expression binding on your entryHours template property something like this:

({Root Container.entryEnd.dateInMillis}-{Root Container.entryStart.dateInMillis})/(1000.0*60.0*60.0)

But after reading more closely, I understand you do not want the value here to always reflect the difference between start and end time like it would with the expression binding. Instead, it should maintain the value it receives via the custom property until a user changes the start or end time. Would adding a condition to your if statement something like this accomplish what you’re looking for?

#If the time was changed, save the differennce between the two to the hour input
if (event.propertyName == 'dateValue' and event.newValue != event.oldValue):

Depending on what’s available in your Ignition version (not sure if this was in older versions), you could drop a couple lines from your script, if desired, by using the dateInMillis property instead of dateValue.On the other hand, results using dateInMillis may not always be what you expect:
http://forum.inductiveautomation.com/t/spinner-control-issue/7512

My suggestion was to avoid running the balance of the script when the initial bindings are evaluated, as at that point, the new dataValue would equal the custom datetime it comes from. It precisely solves the problem of avoiding the calculation when the template is loaded.
No button required.

if event.propertyName == 'dateValue' and event.newValue != event.source.customProp:
    # do the calculation
    # and save to hours
    # mimic bidirectional binding to be ready for any following edit
    event.source.customProp = event.newValue

Perhaps I wasn't clear enough -- the current bindings bringing initial values to dateValue are to be moved to the new custom properties in this setup. The dateValue properties are then bound only to the custom properties.

If I’m picturing this correctly:

  1. entryEnd / entryStart propertyChange triggers script. These values may or may not be initialized on template load (aren’t always saved; implies sometimes saved).
  2. entryHours custom property on template gives initial value to Hours input
  3. script on entryEnd/Start propertyChange should only execute if user changed entryEnd/entryStart (should not execute on load)

As I understand Phil’s solution, entryEnd and entryStart would be written to custom properties (along with entryHours) on load so current input component values could be compared as Phil shows to detect user initiated changes. These custom properties would have one-way binding to components so they would only be updated on load or by component propertyChange scripts as Phil shows.

I didn’t test the possible solution I posted, but suspect it is going to execute on load if the start/end values are written on load (I had assumed they weren’t ever loaded, but on reread suspect they are sometimes loaded but not others).

Another option is to include an isInitialChange boolean property that is set true on load and check in the propertyChange script. If it is true, set it to false. If it is false, execute the rest of the script.

Let me describe the overall sequence of events here:

  • Template repeater gets dataset or repeat count and starts to load/reload templates
  • Template instance is created by deserializing the template master. This sets properties to initial values (as saved in the template) without firing any propertyChange events.
  • Bindings are installed and then components started up. Bindings begin executing.
  • Binding values start arriving, and synchronously invoke propertyChange events. In my recommendation, dateValue is set by the binding from customProp, so the new value of dateValue is equal to customProp. Checking this gives the chance to do nothing in the event at this point.
  • User interacts with the template and changes either start or end dateValue. The binding from customProp is unidirectional, so customProp doesn’t change. dateValue no longer matches customProp, which can only happen due to user input, and the balance of the event can be executed.
1 Like

It's a little simplistic but you could also use a Try/Except clause

Time spent

try:
entryHoursMS = entryEndMS - entryStartMS
# Convert to decimal hours. Make sur to use decimals (float), otherwise the resulting value will not have any.
entryHours = entryHoursMS / (1000.060.060.0)
except:
entryHoursMS = 0

Save the value to the custom property. In turn, this will be saved to the input box

event.source.parent.entryHours = entryHours

Bind expressions also have a Try clause in the form of:
try( expression, failover )