I'm always in search of better ways to develop "the Ignition way". Similar to how, when writing python code, there's often a "pythonic" way of writing something that more closely matches the intent of the language, even when other methods work.
This quote from a post by @pturmel inspired me to start a list of best practices:
And here's one I have to contribute:
Does anyone else have any nuggets of wisdom to share? What are some things you learned from experience that you wish you understood sooner?
Sorry, I do this all the time with user entry forms and user-editable tables.
Where the source of the one-binding is the unedited value (database/tag/whatever). User interface operations that edit a field or propagate table edits will modify the target of the one-way binding, and that it no longer matches its binding source lets the code know that edits are present.
When the user wishes to save/confirm, the corresponding script can write back to the unedited value (via its own two-way binding, or bulk tag writes, or suitable database operations).
{ Canceling edits is even easier: just refresh the binding on the source property and it'll propagate through the one-way bindings. }
For example, let's say you have a configuration page with dropdowns. You might want to bind their `value' prop so the display the current configuration as it's stored. Picking a new, different value will write to a property with a binding.
But it's not a problem. The binding gave it its initial value, but there's no reason you shouldn't overwrite it.
Now you can refresh the binding to set it back to its original value, or compare the value as it's stored and the current value to see if it's been edited, etc.
I would take a different approach to that scenario, personally, and I'm curious of your thoughts on it.
My Reasoning
The reason I try to stay away from writing values to bound variables is that they could update at any time, often for an unexpected reason. If, say, two people were editing the same tag through an input bound to that tag, then the first person to save that value would silently update the input value on the screen of the second person.
I also feel like it may be harder to understand the intent and functionality. When I find a variable with a binding, I assume that binding is the only driver for that variable. If it got updated by a script somewhere else, I wouldn't expect to look for that.
An Alternative Approach
My approach would be to have an initialization script in the view (or root) that gets and sets all the initial variables. Making that into a generic function could allow for that easy edit cancelling feature.
Where Should Logic Live?
Maybe the greater question here is: Where do we put logic for a Perspective view?
Lots of features can be implemented using nearly any combination of:
Tag/Property/Expression/Database Bindings
Change/Event/Project/Global Scripts
... and that logic can be implemented in nearly any place:
Component/Custom/Session Properties
Sister/Parent/Root Components
Tags/Named Queries
Does anyone have a go-to or ordered list of how to choose where logic should live?
Ex. When tag updates, show popup with calculated result to user.
Option 1
a. Create a second tag with an expression for the calculated result based on the first tag.
b. Bind the second tag to root component.
c. Add a change script to the bound root component custom property to launch the popup.
Option 2
a. Bind the tag to the root component.
b. Add a change script to the bound root component custom property.
c. From there, call a global script to get the calculated result.
d. Launch the popup.
The difference between these two is that in one, the logic is implemented in the tag system, and in the other, it's implemented in a global script. It could've just as easily been implemented in a component script or the change script itself. Which of the 4 options would you pick and why? (Or does it depend on the context? In which case, how so?)
Well, the user needs to know if that happens. If overwriting what they are doing is inappropriate, then a second intermediate property can be used, with forwarding by change event, but only if editing hasn't begun. I expect there to be highlighting for the user on field/cells that have been changed from initial, and different highlighting could be used if the live value changes from initial during editing. Otherwise, it should go through.
Maybe, but the common pattern for this sort of thing is that the uni-directional binding is only the last binding driving an entry field, and its source is a custom prop on the same component. With a another boolean custom prop that signals "dirty" for UI styling.
I would say that this is only hard to understand if someone is prone to overuse scripts--they aren't looking for situations where scripts aren't doing the work.
See @PGriffith's best practices post. If you can do it without a script, do it without a script. Really.
If common/needed in the entire system, without indirection, and regardless of whether a UI is looking at it, it belongs in gateway scope. Perhaps an expression tag, perhaps a memory tag with a timer or scheduled event to script some calculations, perhaps some flavor of tag change script. Perhaps a cache object in a project library script. (In about that order.)
If common/needed throughout a user interface session, with indirection based on user/role/zone, or some state information independent per user session, it likely belongs in Perspective session properties or Vision client tags. Bindings preferred. Scripts if you must.
If common/needed on a per-page/tab in a Perspective session, across all views, you may need to stick something in a permanently docked view, messaging changes to page scope. (This tricky case inspired my Integration Toolkit module's pageVarMap() function, fwiw.) As always, bindings preferred over scripts.
If common/needed within a Perspective view or Vision window or template, across multiple components in various containers, possibly with indirection, it belongs in the view custom parameters or window root container properties or template root internal parameters. As always, bindings preferred over scripts.
If none of the above apply (the information is only used in the UI flowing inward), then a component or container custom property is appropriate. As always, bindings preferred over scripts.
Any tag change that will drive a UI popup should be an event attached to a tag binding at the appropriate level (not including gateway scope). Such events need Perspective page scope or Vision client scope to work.
In general, any tag indirection or ephemeral calculation whose purpose is for user interaction should be implemented in the session. Pull information into the UI, don't push it from gateway scope.
You missed Option 3:
a) Bind a tag to a root component prop (view custom prop in Perspective).
b) Bind an expression to another root component prop
c) Launch the popup from the second prop's change event.
{ See, you weren't even considering an expression, even though you planned on an expression (tag) in option 1. }
I should add that the unidirectional binding on an entry field with source property and dirty flag is a design pattern used in Vision a great deal, because Vision's property change events do not have an origin indicator. It is almost impossible to distinguish user changes from binding refreshes in Vision without this pattern.