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

Add preliminary support for object "additionalProperties" #1021

Merged
merged 3 commits into from
Oct 2, 2018

Conversation

christianlent
Copy link
Contributor

Reasons for making this change

This will add the ability for the form to understand "additionalProperties" definitions in the schema (see json-schema properties documentation).

Related issue: #228

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've run npm run cs-format on my branch to conform my code to prettier coding style
  • I'm adding a new feature
    • I've updated the playground with an example use of the feature

Details

Specifically, this will enable the following behaviors when additionalProperties is enabled:

  • If the form data contains properties that are not explicitly defined, those fields will be automatically added to the form
  • An "expand" button will be [optionally] added that allows the form user to manually add new properties.
  • In any case in which an additional property has been added, a new text field will be rendered to allow the user to rename the key

Opportunities for improvement:

  • Ability to remove additional properties
  • Somewhat better test coverage (particularly around type detection)

if (!schema.additionalProperties) {
return false;
}
let { expandable } = getUiOptions(uiSchema);
Copy link
Contributor

Choose a reason for hiding this comment

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

No need to make expandable a let:

const { expandable } = getUiOptions(uiSchema);
if (expandable !== false) {
      // if ui:options.expandable was not explicitly set to false, we can add
      // another property if we have not exceeded maxProperties yet
      if (schema.maxProperties !== undefined) {
        return Object.keys(formData).length < schema.maxProperties;
      } else {
        return true;
      }
    }
return expandable;

@@ -42,6 +69,13 @@ class ObjectField extends Component {
readonly: false,
};

constructor(props) {
super(props);
this.state = {
Copy link
Contributor

Choose a reason for hiding this comment

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

it looks like we've got the class fields proposal enabled, so you can do this with:

state = {
   additionalProperties: {}, 
}

}
}

handleAddClick = schema => {
Copy link
Contributor

Choose a reason for hiding this comment

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

handleAddClick = schema => () = >

src/utils.js Outdated
}
return schema;
Copy link
Contributor

Choose a reason for hiding this comment

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

avoid reassigning params

* avoid modifying parameters
* avoid unnecessary let using returns
* eliminate unnecessary bind
* terse closure return syntax
* take advantage of class fields
@christianlent
Copy link
Contributor Author

Thanks for the great feedback @graingert!

@mjasinski5
Copy link

mjasinski5 commented Sep 10, 2018

🙏 👍

@mjasinski5
Copy link

mjasinski5 commented Sep 17, 2018

@christianclent CI failed because of the timeout.. I think all you have to do is to rerun it ;) It would be a big loss if such a remarkable work couldnt be merged 👍

@graingert could you rerun ?:)

Binb1 pushed a commit to Binb1/react-jsonschema-form that referenced this pull request Sep 20, 2018
@derberg
Copy link

derberg commented Sep 24, 2018

@christianclent maybe close this and open new PR as this will trigger travis again https://stackoverflow.com/questions/17606874/trigger-a-travis-ci-rebuild-without-pushing-a-commit
otherwise I'm not sure if anyone will treat this PR seriously

@christianlent
Copy link
Contributor Author

Thank you for the hot tip @derberg!

I've been using this branch in one of my personal projects for a few weeks now, and there are definitely areas I would like to improve.

  • The meat of the change in is SchemaField (with some ancillary code in ObjectField), but I wonder if I should have contained the change to ObjectField only
  • Because the new keys are not known ahead of time, it typically isn't feasible to apply uiSchemas to the additional properties.
  • It doesn't handle ordering very well
  • Because ids of underlying DOM elements incorporate object keys, it will annoyingly bork your tab order when changing an additional property's key.
  • Maybe more?

Some of those may be feasible to fix in the next few weekends if they are deal-breakers for anybody.

@pkosiec
Copy link

pkosiec commented Sep 27, 2018

Hi @christianclent,
Thank you very much for your changes! I think the improvements you've mentioned could be made in another pull request. We really need object support in additionalProperties!

@graingert Could you please take another look at this PR? Thanks!

Copy link
Contributor

@glasserc glasserc left a comment

Choose a reason for hiding this comment

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

This looks pretty good! One feature of rjsf is that traditionally we preserve properties that aren't described by the schema (although we don't show them in any way). Is that still true here, in the cases where additionalProperties is false?

My other concern is about UX. Each property is two labeled inputs, and it isn't obvious at first glance that they're related. Is there any way we could use the first input as the label to the second input? Or is that just crazy talk?

}
return true;
}
return expandable;
Copy link
Contributor

Choose a reason for hiding this comment

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

You could refactor this as

if (expandable === false) {
  return false;
}
if (schema.maxProperties !== undefined) {
  return Object.keys(formData).length < schema.maxProperties;
}
return true;

This would save a level of indentation. But that's just a personal preference.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think this is a widely held preference, and I strongly agree. Some folks call this the "bouncer pattern": http://rikschennink.nl/thoughts/the-bouncer-pattern/

errorSchema &&
this.props.errorSchema && {
...this.props.errorSchema,
[name]: errorSchema,
Copy link
Contributor

Choose a reason for hiding this comment

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

Should name here be value? Or more generally, where does name come from in this scope?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Haha, yes, this was copy pasta from onPropertyChange

src/utils.js Outdated
const resolvedSchema = resolveSchema(schema, definitions, formData);
const hasAdditionalProperties =
resolvedSchema.hasOwnProperty("additionalProperties") &&
resolvedSchema.hasAdditionalProperties !== false;
Copy link
Contributor

Choose a reason for hiding this comment

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

What is the hasAdditionalProperties property? Is this maybe supposed to be additionalProperties?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes indeed!

@christianlent
Copy link
Contributor Author

Great points @glasserc!

I didn't even consider preserving non-schema properties. It would likely not be difficult to. The following is my instinct for ideal behavior, but I am very open to whatever folks like best. Whatever we decide, this certainly calls for unit tests!
typeof additionalProperties !== 'boolean': preserve non-schema properties
additionalProperties === false: do not preserve non-schema properties
additionalProperties === true: display non-schema properties with new UI

Yes, that is a very good idea - I considered something like it, but ultimately chose to prioritize power and flexibility over user experience. In hindsight, I think you are right and UX should be paramount. Another slightly different idea is to make the key related to the title (perhaps via something like suglify: https://www.npmjs.com/package/slugify ).

I will address these points and your specific feedback by EOD today with any luck.

Fix misnamed "additionalProperties" attribute
Invert conditional to reduce indentation
Added unit tests to ensure non-schema properties are maintained
@christianlent
Copy link
Contributor Author

It turns out that this continues to preserve non-schema properties in every case. That being said, the existing validation should complain if the user sets non-schema properties and sets additionalProperties to false. I have added unit tests for this behavior.

I haven't changed the user experience for keys yet. Do you definitely like using the title as the object key? If so, I'll try to get to that this week.

Copy link
Contributor

@glasserc glasserc 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 great, thanks! It's clearly an improvement over what we have now, so I'll merge it. I'll add a note to the documentation that the UX is still experimental for now, and maybe someone else, or even you, will revisit it.

@glasserc glasserc merged commit 1d54db6 into rjsf-team:master Oct 2, 2018
@pkosiec
Copy link

pkosiec commented Oct 2, 2018

@glasserc Thank you for merging this! Do you plan a new release soon?

@glasserc
Copy link
Contributor

glasserc commented Oct 2, 2018

@pkosiec I don't know. It seems like 1.0.5 is broken so I might cut another one once we figure out what to do about #1042.

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.

6 participants