getodk / xforms-spec

The XForms-derived specification used in the ODK ecosystem. If you are interested in building a tool that is compliant with the forms rendered by ODK tools, this is the place to start. ✨⚒✨
https://getodk.github.io/xforms-spec/
30 stars 26 forks source link

Proposal: deprecate trigger #115

Open lognaturel opened 7 years ago

lognaturel commented 7 years ago

We've been having some conversations about trigger with the Collect community: opendatakit/collect#908

The punchline is that we're not sure why trigger was used to represent boolean values. Does anyone know?

I'd like to propose that we remove it from here and try to encourage people to use boolean inputs instead. Clients can choose their deprecation timeline.

MartijnR commented 7 years ago

I remember @ctsims mentioning the issues with booleans in JavaRosa though (and why they avoid them). Even without javarosa quirks, I just want to highlight that working with booleans in XPath is somewhat difficult. E.g in:

<data>
     <a/>
     <b>0</b>
     <c>false</c>
</data>

correct results are:

boolean(//a) -> true! boolean(//b) -> true! boolean(//c) -> true!

(see codepen here)

Note that the type specified in the <bind> has nothing to do with evaluating XPath expressions. It's irrelevant (in a compliant XPath evaluator).

So checking for an 'OK' string, may be more practically useful than e.g. using 0 and 1 values and converting to a number to get the desired boolean value if( number(//b), .. , .. ).

lognaturel commented 7 years ago

Ah, thanks for that information. I'm having a bit of a hard time putting the pieces together, though!

I would expect the following to work:

<?xml version="1.0"?>
<h:html xmlns="http://www.w3.org/2002/xforms" xmlns:h="http://www.w3.org/1999/xhtml" xmlns:jr="http://openrosa.org/javarosa" xmlns:orx="http://openrosa.org/xforms" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <h:head>
        <h:title>Desired boolean syntax</h:title>
        <model>
            <instance>
                <data id="desired-boolean">
                    <boolean1/>
                </data>
            </instance>

            <bind nodeset="/data/boolean1" type="xsd:boolean" />
        </model>
    </h:head>

    <h:body>
        <input ref="/data/boolean1">
          <label>Boolean1</label>
        </input>
    </h:body>
</h:html>

And to result in an instance that looks something like <?xml version='1.0' ?><data id="desired-boolean"><boolean1>true</boolean1></data>

Are you saying that might not be a good idea because if later on in the form I wanted to use the value input by the user in boolean1 then false would be evaluated as true in an XPath expression?? That does indeed sound pretty annoying.

It's the approach that the XForms Cookbook recommends.

MartijnR commented 7 years ago

Yes, exactly, the issue is with retrieving the value as a boolean. It's so confusing, it makes me wonder if we should keep discouraging the use of them. I think the only way to use ${boolean} in expressions is by comparing the string values (${boolean} = 'false'). So storing the boolean as type="string" in the first place may be less prone to misunderstandings (especially amongst users with some programming skills actually).

I got so caught up in responding to the boolean solution, that I neglected to say that indeed it seems that <trigger> has been abused. I never realized this! For that reason, I'd be for deprecating this since our form builders could convert a 'type=confirm/trigger' question into a select one with one option with value 'OK' (which is now exactly what Enketo is doing when it finds a <trigger>). That may be a hard sell though, unless we intend to use <trigger> for what it is meant to do in XForms. But it has my vote for purist reasons.

lognaturel commented 7 years ago

Wooooow, that boolean handling is terrible. Now the use of trigger is starting to make a little more sense.

What does Enketo do with the form I included a couple comments up? Does it show a checkbox of some sort or not?

I'm going to have to think about the implications of all this some more. I think actually using trigger for its intended purpose would be tough given how many forms probably use it to mean this boolean thing. But I also have purist tendencies.

MartijnR commented 7 years ago

Yes, the historic choice to use the 'OK' string does make sense, indeed.

Enketo has no support for booleans, so it would show a text input field. I actually don't know how this type made it into the spec. It may have been an accident (caused by me)....

But I also have purist tendencies.

Sorry to hear that. I know it can be tough. ;)

IanLawrence commented 7 years ago

The correct use of trigger could be useful I think. For instance conversation paths of a chatbot lend themselves to XForms control with relevance and so on. In this type of ODK client 'user interface' buttons and events can be very effective since they let users own the words and give a good illusion of NLP Probably way out of scope for this discussion but thought I would mention it anyway

lognaturel commented 7 years ago

@IanLawrence If you have a practical need for this, I think it'd be great to get a developed writeup of an ideal interface over on one of the client repos. Once the use case is fleshed out, we can see what kinds of spec additions/changes would be needed to support it.

tiritea commented 7 years ago

Other than the XForms author having to remember to check boolean elements in XPath expressions to the strings 'true' and 'false', are there really any other significant problems with this data type? It seems like a smaller price to pay than arguably abusing 'triggers', and the mess that seems to have led to (IMO)... :-)

tiritea commented 7 years ago

Note, the handling of the XForm 'boolean' type within an XPath expression is really no different, or worse, than any other data type; basically, you just have to remember that an XForm 'boolean' isnt actually an XPath boolean, hence you cant use in situ as a (XPath) boolean sub-expression but must instead use something like (foo='true'). [or we could make it a bit easier with, say, a custom XPath function isTrue(foo)...]. I dont think that XForms boolean != XPath boolean should be a deal breaker - in this regard its no worse than anybody else. Of course, this is all independent of whether Collect exposes this via a TriggerWidget, or something else; how a boolean control is presented in Collect UI is somewhat secondary to Collect supporting the (existing) XForm 'boolean' type in the first place.

tiritea commented 5 years ago

Just going back to @MartijnR example...

I just want to highlight that working with booleans in XPath is somewhat difficult. E.g in:

<data>
     <a/>
     <b>0</b>
     <c>false</c>
</data>

correct results are:

boolean(//a) -> true! boolean(//b) -> true! boolean(//c) -> true!

These are certainly legit complaints about the XPath handling of booleans, but I dont think that necessarily makes the XForm boolean datatype any less appealing (nor guilty-by-association... :-) Form writers are going to have to cope with the unexpected nuances of XPath boolean sub-expressions, with or without the presence of an XForm boolean datatype.

tiritea commented 5 years ago

I'd be for deprecating this since our form builders could convert a 'type=confirm/trigger' question into a select one with one option with value 'OK'

I'm not sure I can agree. Substituting a single option select1 for a trigger only works today by virtue of the fact current widgets dont allow for the deselection (of all) in a select1. But that may well change in future; eg we're identified clearing a select1 as a user need (I'm thinking of implementing it via a LONG press on an option...) So I still see a need for the existing functionality that trigger provides, irrespective of it being historically abused (perhaps due in fact to a lack of boolean availability... [sic])

MartijnR commented 5 years ago

Substituting a single option select1 for a trigger only works today by virtue of the fact current widgets dont allow for the deselection (of all) in a select1.

it doesn't? It does in Enketo, so we can already untrigger in Enketo ;).

tiritea commented 5 years ago

well, yes... I had noticed I could 'un-ack' in Enketo, but I didnt want to say anything... :-P

But that reiterates my point that the current trigger (from the docs: "A completed trigger response is stored as the string 'OK'.") is somewaht semantically different than a select; specifically you cant (or shouldn't!) be able to 'un-trigger'/unacknowledge it.

So I had a thought (while driving home)... if the root of the/a problem here is how terrible XPath's inherent handling of booleans is - as nicely illustrated by @MartijnR's eample above - how about we introduce a new build-for-purpose custom XPath function, eg bool(), that actually does a boolean conversion 'property' !? Then we can recommend ODK form writers that whenrver they are dealing with boolean expressions, including this new XForm boolean data type, in their forms to always test it using bool(). Because otherwise the native XPath boolean() may give unexpected results (and that XPath will perform automatic type casting with it if you do nothing!)

MartijnR commented 5 years ago

Yes, good point about the semantic difference. I'm not sure if un-acknowledging is a bad thing though, but it probably can be in some use cases.

We have already have boolean-from-string(). That's essentially what you proposed, right?

tiritea commented 5 years ago

We have already have boolean-from-string(). That's essentially what you proposed, right

Probably, although it should behave as expected when given a 1 or 0 numeric argument (in case its being used in an intermediary expression) which I presume boolean-from-string doesnt? [or at least I'm pretty sure Validate will toss-its-toys if you give it a number... ;-) )

MartijnR commented 5 years ago

when given a 1 or 0 numeric argument

That shouldn't make any difference, due to XPath's conversion rules (see 2nd paragraph here as well). It would be bug if it makes a difference!

lognaturel commented 5 years ago

To be clear, I directed @tiritea here when he brought up boolean in #xforms-spec because my understanding is that he would like that a primary instance node with bind type boolean and a corresponding input body element be displayed in some way. This is currently done by the trigger body element in the ODK XForms world. I think that from a user perspective, it would be confusing to introduce something new with the same functionality unless we also put trigger on a deprecation path.

From Slack:

Having a simple boolean - with 'true'(false), 'yes'(no), 'pass'(fail) appearance - covers a LOT of usecases, and doesnt require the user having to populate option labels and associated values.

Except that "boolean" has no meaning to non-developers, right? If we're going to need to have aliases anyway, can we just do this at the XLSForm level?

Enketo has no support for booleans, so it would show a text input field. I actually don't know how this type made it into the spec. It may have been an accident (caused by me)....

The data types section in the spec seems a little odd to me. Its title implies it's about data types in general but really it wants to be about bind types exclusively, right? I think in the short term we should add a little more context to this section and remove boolean. Boolean expressions are supported and so in that sense the data type is but using it as a bind type has no effect.

Substituting a single option select1 for a trigger only works today by virtue of the fact current widgets dont allow for the deselection (of all) in a select1.

The semantics of ODK XForms trigger should be identical to a select/select1 with a single "OK" option. Both Collect and Enketo allow for deselection of all options in selects, select1s and triggers. In Collect, all question types allow long pressing to clear and trigger is displayed like a select so that it can be checked and unchecked. If there's any documentation that suggests otherwise, it should get fixed!

I'm not sure if un-acknowledging is a bad thing though, but it probably can be in some use cases.

Typically it's combined with a required expression if it's a "you must acknowledge this to continue" type of question. I've seen forms where consent for a certain section is optional and a trigger without a required is useful there.

I presume boolean-from-string doesnt

If it doesn't, it's a bug and we should fix it!

tiritea commented 5 years ago

... a primary instance node with bind type boolean and a corresponding input body element be displayed in some way. This is currently done by the trigger body element in the ODK XForms world.

Not quite... the subtly here is that the existing trigger only allows for two conceivable element values: "OK" (implied 'true'?) or null (implied 'false'?). Whereas an actual binded boolean element permits three: "true", "false" or null (ie no response).

Although you can mimic this with a yes/no select one, you cant actually mimic the same with a trigger.

lognaturel commented 5 years ago

Whereas an actual binded boolean element permits three: "true", "false" or null (ie no response).

Ah, but that's not a boolean since there are three states. XML has no concept of null. The empty string is interpreted as false (XML schema, XPath 1.0 ref). This is exactly why I think it's better not to add support for boolean. It's truly quite confusing!

tiritea commented 5 years ago

Yup, but I still believe think there is semantic difference between an XPath boolean (which must necessarily always return true or false), and an actual XForm control with a boolean binding, which in the corresponding instance XML may be null. Indeed, from https://www.w3.org/MarkUp/Forms/2010/xforms11-qr.html#Types:

Type (all in the XForms namespace, derived as necessary from XSD types, in which case they also allow empty content)

Perhaps I'm misinterpreting, but in my view a boolean (bound) XForm control may be true, false, or not set. If it happens to subsequently be used in a boolean XPath expression then, obviously, it will be mapped to true/false. But it is still quite possible to perform a legitimate check on whether the boolean control is null or not, specifically for the purpose of evaluating required (ie mandatory); ie I want to know if the user answered the question yet - not specifically whether they answered yes or no - before allowing them to finish.

If I were to use a trigger as a substitute for a boolean control, I have no way to determine whether the user didnt answer the question, vs (implicitly) answered "no" (by effecitvely not saying yes). That distinction is critical IMO, and presently has to be accomplished via a more heavyweight select1 instead.

[Am I making any sense? We seem to be going round in circles... :-\ ]

lognaturel commented 5 years ago

in my view a boolean (bound) XForm control may be true, false, or not set

I believe you're right but I think that's why it was avoided in the initial spec. A boolean with three states if you happen to do string comparison but two if you use it in an XPath expression is not very comforting. Add that to the fact that normal folks don't know what boolean means and it's really very confusing.

Taking a step back, is the user-facing problem you are trying to solve that it is tedious to build forms with two-option selects? Perhaps what you're seeking is an XLSForm shortcut to generate a two-option select? (Note: I'm not very excited about that prospect either. What do you do about translations? How do you decide which choices to support? You mentioned wanting true/false, pass/fail, yes/no appearances. I can see this becoming a real rathole when selects provide the necessary flexibility.)

tiritea commented 5 years ago

Add that to the fact that normal folks...

Implying what exactly!? :-) :-) haha

Pretty much yes, I'm approaching it from the (purest) XML standpoint that having to create a select1, populating options, yadda, yadda is a lot more tedious than simply being able to define a boolean control widget that does pretty much everything I want, and for which there's already a defined data type (apparantly) for that exact purpose anyway. And a simple appearance seems a natural way to concisely define how to present this (binary) choice: yes/no (or true/false, or pass/fail). ie

<input ref="/data/boolean" appearance="passfail"><label>Outcome?</label></input>

vs

<select1 appearance="compact" ref="/data/boolean">
        <label>Outcome?</label>
        <item>
          <label>Pass</label>
          <value>true</value>
        </item>
        <item>
          <label>Fail</label>
          <value>false</value>
        </item>
      </select1>

[although perhaps wrt XLSForms the brevity and simplicty distinction is less important]

Probably 90% of our checklists are just pass/fail, so it makes sense in our environment to accommodate that efficiently with a very basic control.

tiritea commented 5 years ago

It is also worth noting that it can be desirable for a boolean/binary-specific widget to have a distinct (and different) appearance than what is available - and what necessarily has to be - a more general purpose multi-value select widget. eg a boolean widget can (and is preferable?) to be presented with, say, a standard UI switch widget:

ios7_switches

Having binary-specific UI widgets isnt readily possible (without perhaps counting the number of options...) if everything has to get rolled into more generic select1 appearances

[yes, I'm ignoring for the moment how you'd go about represented 'null' in the above switch, but hopefully you get my point: purely yes/no, true/false UI elements lend themselves to rather different UI presentations]