Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable Arrays to be marked as required, and fixes for strings marked as required #442

Merged
merged 15 commits into from
Jan 20, 2017

Conversation

crumblix
Copy link
Collaborator

Reasons for making this change

Reasons for making this change

New feature:

Enable the arrays to be marked as "required". Meaning that the user must select "at least one" option from the list.

This references the array part of ticket:
#411

It also refers to the string part of
#410 and #402

Checklist

  • I'm updating documentation
    • I've checked the rendering of the Markdown text I've added
    • If I'm adding a new section, I've updated the Table of Content
  • I'm adding or updating code
    • I've added and/or updated tests
    • I've updated docs if needed
  • I'm adding a new feature
    • I've updated the playground with an example use of the feature

@crumblix crumblix changed the title Branch6 Enable Arrays to be marked as required, and fixes for strings marked as required Jan 18, 2017
@@ -558,6 +558,8 @@ const uiSchema = {
};
```

In addition it is valid to maked an array as "required". This means the user must select at least one items to add to the array.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to maked? Looks like a typo. Maybe "to mark"?

@crumblix
Copy link
Collaborator Author

Nice pickup ... thank you!!! :)

Copy link
Collaborator

@n1k0 n1k0 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still some heavy questions, and a few nits here and there.

Also wondering about <SelectWidget>, what should we do with enums containing an empty string when it's selected?

@@ -558,6 +558,8 @@ const uiSchema = {
};
```

In addition it is valid to mark an array as "required". This means the user must select at least one item to add to the array.
Copy link
Collaborator

@n1k0 n1k0 Jan 18, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm realizing slightly late that this is overlapping with the minItems jsonschema directive. I think the jsonschema spec wants a required object array property to be an array, possibly an empty one. With jsonschema, if we wanted at least one item in our array, we'd set minItems to 1 instead of relying on the required directive.

So that's a strong direction we're taking here, we're officially diverging from the jsonschema spec to fit Web forms use cases & habits. Is everybody okay with this?

Would appreciate inputs from @MoOx @maartenth @olzraiti @leplatrem @Natim @dehli

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair call. I wasn't aware of that directive. If we can achieve a similar effect with existing code that is standards compliant I would certainly support that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed that's a serious diff. Not sure I am comfortable with that but I guess we can't have the silver bullet.

Copy link
Collaborator Author

@crumblix crumblix Jan 18, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SelectWidget is an interesting case. In truth I don't use it much because I've written my own custom widget based on react-select which better suited my needs for a nullable and multiselect-able drop down widget. (Happy to share if anyone wants it, but I thought since it was an extra dependency it was better off outside the core project.)

I could be convinced to do it here as well, since of course the "required" tag won't work, and while I prefer react-select for this case, perhaps like dates we possibly should cater for the most general case.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This wouldn't affect my code base, but I'm not sure if we would benefit enough from adding this considering the consequences.

We aren't extending the json schema spec in a way that would make something possible that was impossible before (using minItem provides the functionality that is wanted here). We are merely making something more convenient than before, with the cost of breaking compatibility with json schema spec.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JSON Schema is not a form builder

The issue is that people import existing schemas and expect them to work with forms with no need to tweak their schemas (uiSchemas have been introduced for that specific purpose).

That could be addressed with an appropriate FAQ/Caveats section though. The same way we could document the minItems jsonschema directive.

the difference between an empty array and undefined is simply not visible in a form

That's indeed a fair point. Another approach would be to make the difference actually visible, but that's rather tricky to get right, and would probably clutter UX a lot. Also this would have to work with bootstrap and other styles.

So my current thinking is:

  1. Keep the empty string as undefined value feature from this patch.
  2. Remove the required array should contain at least one item one and add proper docs describing how to achieve it using the minItems jsonschema directive.
  3. Leave SelectWidget as it is, users should provide enum values matching their expectations.

What do people think?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with you @n1k0 . I'm currently using minItems in my application b/c that's the json-schema way of doing it. If we added documentation describing it, then it'll be more obvious that it's the way to do it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good to me! Empty string as undefined seems well-grounded.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm OK with your proposed solution, @n1k0. I think it is an improvement to the current situation either way.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good, @crumblix would you mind updating the patch accordingly if you agree with this decision? :)

Thanks a bunch 👍

this.asyncSetState({
items: this.state.items.filter((_, i) => i !== index)
items: newitems
}, {validate: true}); // refs #195
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const items = this.state.items.filter((_, i) => i !== index);
this.asyncSetState({
  items: items.length > 0 ? items : undefined,
}, {validate: true}); // refs #195

return _onChange(undefined);
} else {
return _onChange(event.target.value);
}}}/>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const _onChange = ({target: {value}}) => {
  return props.onChange(value === "" ? undefined : value);
};
// ...
onChange={_onChange} />

return undefined;
} else {
return ret;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const ret = selected.filter(v => v !== value);
return ret.length === 0 ? undefined : ret;

@@ -19,7 +24,7 @@ function CheckboxesWidget(props) {
return (
<div className="checkboxes" id={id}>{
enumOptions.map((option, index) => {
const checked = value.indexOf(option.value) !== -1;
const checked = (value === undefined) ? false : value.indexOf(option.value) !== -1;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Unnecessary wrapping parenthesis.

return _onChange(undefined);
} else {
return _onChange(event.target.value);
}}}/>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See previous impl. suggestion above.

@crumblix
Copy link
Collaborator Author

Thank you very much for the code review! Much appreciated, I have no problem addressing a few nits :)

@@ -216,8 +216,9 @@ class ArrayField extends Component {
if (event) {
event.preventDefault();
}
var items = this.state.items.filter((_, i) => i !== index);
Copy link
Contributor

@dehli dehli Jan 18, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be a const or let. It looks like you never assign to it again, so a const would be the way to go.

@crumblix
Copy link
Collaborator Author

Thanks again everyone for the feedback :)
I reverted the documentation and fixed the last nit to make the code cleaner and better.

I'll need to read up on minItems before making any changes to the documentation for that as I haven't used it before.

My plan is to try and get it working in the playground later before updating the documentation about it.

Either that or if someone who knows how it works already sends me a sentence I'll just include it.

@crumblix
Copy link
Collaborator Author

OK, so I've reverted the array changes, played around with minItems in the playground, and tried to make it clear how to achieve a similar (and more feature rich) result by using it in the documentation.

Thanks for pointing out minItems!

Copy link
Collaborator

@n1k0 n1k0 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're getting there! Could you please add two simple tests for baseinput and textarea widgets to ensure they propagate undefined when their change event value is set to ""? Thank you!

@@ -547,6 +547,23 @@ const uiSchema = {
};
```

Unlike other types, array's do not support the `required` property. You can however instead specify the minimum number of items the user must select with the `minItems` property.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It actually supports the required property, it only checks that an array property value is provided, which may or may not be empty. Put another way, for

schema = {
  type: "object",
  required: ["foo"],
  properties: {
    foo: {type: "array"}
  }
}

{foo: []} is valid against this schema while {} is not.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No problem! While it's true supports the required property, I suspect that it doesn't operate in the fashion most UI builders would expect, at the very least it's a potential source of confusion. I'd try and update the documentation to be clear in that regard. :)

And yes to the tests as well! :)

Copy link
Collaborator

@n1k0 n1k0 Jan 20, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suspect that it doesn't operate in the fashion most UI builders would expect

I think we need to stay true to the jsonschema spec as much as possible, as it's our data model layer behind our form views. One of the goal of this lib is also to make forms and their underlying data model more robust and consistent. I wouldn't want to bring regular web forms flakiness to jsonschema, which would kind of defeat its purpose if you ask me :)

The best patch for preventing confusion is good documentation.

…equired and minItems properties when used in arrays.
@@ -547,7 +547,7 @@ const uiSchema = {
};
```

Unlike other types, array's do not support the `required` property. You can however instead specify the minimum number of items the user must select with the `minItems` property.
Care should be taken when using the `required` property with arrays. An empty array is sufficient to pass that validation check. If you wish to ensure the user populates the array, you can specify the minimum number of items the user must select with the `minItems` property.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is perfect 👍

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excellent :)
I'll have to get to the tests over the weekend. It's a part of the code base I haven't been into yet.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately there wasn't a text area widget section that I could see so I created one .... but it isn't right. I will have to look at it over the weekend.

What's checked in is passing, but it's not using the widget.

I've tried the change:

const uiSchema = {"ui:widget": "textarea"};

it("should handle an empty string change event", () => {
  const {comp, node} = createFormComponent({schema: {
    type: "string",
  }, uiSchema});

... which seems to me like it should work but that fails for reasons that currently escape me. I'll need to look at it over the weekend. Got to head off now though!

Copy link
Collaborator

@n1k0 n1k0 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is now ready to land, thanks for your patience and the great work!

@n1k0 n1k0 merged commit ffc1737 into rjsf-team:master Jan 20, 2017
@crumblix
Copy link
Collaborator Author

You're welcome :), but I think Textarea widget test is wrong ... isn't it?

n1k0 added a commit that referenced this pull request Jan 20, 2017
@n1k0
Copy link
Collaborator

n1k0 commented Jan 20, 2017

I think Textarea widget test is wrong ... isn't it?

It was, fixed by 8c4707a :)

n1k0 added a commit that referenced this pull request Jan 28, 2017
Breaking changes

When a text input is emptied by the user, it's value is now reset to `undefined` instead of being set to `""` (empty string) as previously. This better matches traditional HTML forms behavior.

New features

* Add an array field template component (#437)
* Wrap radio and checkbox labels into span to enable styling. (#428)
* Reset text inputs value when emptied (#442)
* Add transform hook for changing errors from json schema validation (#432)
* Add a noHtml5Validate prop (#448)
* Add support for a onBlur event handler (#431)
* Allow empty option for enum fields (#451)

Bugfixes

* Fix #452: Support recursively referenced definitions. (#453)
* Fix #454: Document what master actually is, suffix its version with -dev.
@n1k0
Copy link
Collaborator

n1k0 commented Jan 28, 2017

Released in v0.42.0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants