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

Modular Content Widget #1169

Closed
wants to merge 23 commits into from
Closed

Modular Content Widget #1169

wants to merge 23 commits into from

Conversation

anthonysapp
Copy link

@anthonysapp anthonysapp commented Mar 12, 2018

- Summary
The motivation for this pull request is so the CMS can support a front matter property containing a list of modular content or blocks to construct a custom page output.

- Test plan
Add "modular" as widget type in config.yml. This content type accepts a "types" list instead of "fields". Functionality is similar to a list widget with its valueType set to valueTypes.SINGLE. Added the functionality of a dropdown control at the top of the list which chooses the content type to be added to the modular content list. Because it is a variation of the List widget, it retains the ability to re-order child widgets via drag and drop.

configyml

Modular content configuration

modular-content-widget
Modular content widget

- Description for the changelog
Added modular Widget for saving modular content lists to frontmatter.

cute animal

fixes #1066

@verythorough
Copy link
Contributor

verythorough commented Mar 12, 2018

Deploy preview for netlify-cms-www ready!

Built with commit 3abf66a

https://deploy-preview-1169--netlify-cms-www.netlify.com

@verythorough
Copy link
Contributor

verythorough commented Mar 12, 2018

Deploy preview for cms-demo ready!

Built with commit 3abf66a

https://deploy-preview-1169--cms-demo.netlify.com

Copy link
Contributor

@erquhart erquhart left a comment

Choose a reason for hiding this comment

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

@anthonysapp this is awesome work, does just what it says on the tin! This will be extremely useful for a lot of folks.

I have a rather large change request for you: we should refactor this to be a feature on the existing List widget.

Consider that the list widget is already doing exactly what this does, except you can only define one "type". If a types field is present, we should use that instead of fields and convert the add button to a dropdown. Beyond that, the functionality we need is already there in the List widget.

Let me know if any of that doesn't make sense or if you have questions. Excited to see this get in!

@anthonysapp
Copy link
Author

@erquhart l will have a look tonight or tomorrow morning, for sure.

@Benaiah
Copy link
Contributor

Benaiah commented Mar 15, 2018

@erquhart What's the use case for your suggestion? Other widgets wrap the List widget for list functionality as well, and introducing this as a feature of the list widget, instead of a wrapper around it, seems kinda weird architecturally.

@anthonysapp
Copy link
Author

@Benaiah I think maybe because it is a bit of code duplication? My widget is pretty close to the list widget and can probably be merged pretty easily.

@anthonysapp
Copy link
Author

anthonysapp commented Mar 15, 2018

@erquhart I had a crack at this this morning. I think I've merged all the functionality in the appropriate places. Updated including moving documentation to the list component. The only area I'm not sure if I could make a little cleaner is my modification to the ObjectPreview class.

@erquhart
Copy link
Contributor

erquhart commented Mar 15, 2018

@Benaiah I don't think use case and architecture map to one another - the use case is a website that needs flexible content authoring either way. I'm not saying combining this into one widget is the best way to do it, but that it might be the best way to do it at the moment for our codebase. I'm not aware of any widget wrapping the list widget, and I don't believe it's currently possible to do so cleanly.

That said, I was thinking that this PR only allowed named field groups to be selected, but it's actually allowing individual fields to be selected by name, which is a big difference, and a noteworthy divergence from the List widget's general role of providing repeatable groups.

@anthonysapp
Copy link
Author

anthonysapp commented Mar 15, 2018

@erquhart I think this is why I decided against merging it into the List widget in the first place. Nevertheless it was a lot of code duplication so I think your reasoning had merit as well. In any case I merged into List and have all the functionality back as of the latest commit to the PR.

@erquhart
Copy link
Contributor

@anthonysapp to help simplify the review, could you do the following?

  • Re-add dist the .gitignore and remove that directory from the PR
  • Remove any changes that only affect code style
  • Remove the hardcoded previewing of specific widgets in the preview component

Let me know if you have questions on that last one.

@anthonysapp
Copy link
Author

@erquhart Not sure I have any changes that only apply to code style? And yeah a pointer on that last one would help for sure. Thanks!

@verythorough
Copy link
Contributor

Ooh, I am very much looking forward to this. I'm pretty sure I'll be able to use it to use Netlify CMS to edit the config.yml file, specifically to make it possible to add new collections via UI.

Copy link
Contributor

@tech4him1 tech4him1 left a comment

Choose a reason for hiding this comment

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

Just blocking to make sure we don't merge with the dist or package.json changes.

Copy link
Contributor

@erquhart erquhart left a comment

Choose a reason for hiding this comment

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

Here's a review pointing out the code style changes and a few other things.

Code style changes are changes that only impact how the code looks rather than what it does, like breaking one long line of code down into a few more readable lines. These changes are helpful, but their presence increases the overall risk associated with merging the PR, since the code style changes themselves can break things. Code style changes should be presented in a separate pull request.

package.json Outdated
@@ -1,6 +1,6 @@
{
Copy link
Contributor

Choose a reason for hiding this comment

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

All changes to this file should be removed.

<div {...props} className={`list-item ${ props.className || '' }`}>
{props.children}
</div>
);
Copy link
Contributor

Choose a reason for hiding this comment

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

Code style change.

const SortableList = SortableContainer(({ items, renderItem }) => {
return <div>{items.map(renderItem)}</div>;
});
const SortableList = SortableContainer(({ items, renderItem }) => <div>{items.map(renderItem)}</div>);
Copy link
Contributor

Choose a reason for hiding this comment

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

Code style change.

const listValue = e.target.value
.split(',')
.map(el => el.trim())
.filter(el => el);
Copy link
Contributor

Choose a reason for hiding this comment

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

Code style change.

this.setState({ value: valueToString(listValue) });
this.props.setInactiveStyle();
};

handleAdd = (e) => {
e.preventDefault();
const { value, onChange } = this.props;
const parsedValue = (this.valueType === valueTypes.SINGLE) ? null : Map();
const parsedValue = this.valueType === valueTypes.SINGLE ? null : Map();
Copy link
Contributor

Choose a reason for hiding this comment

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

This line is a code style change.

PropTypes.object,
PropTypes.bool,
]),
value: PropTypes.oneOfType([PropTypes.node, PropTypes.object, PropTypes.bool]),
Copy link
Contributor

Choose a reason for hiding this comment

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

Code style change.

@@ -90,19 +84,27 @@ export default class ObjectControl extends Component {

handleCollapseToggle = () => {
this.setState({ collapsed: !this.state.collapsed });
}
};
Copy link
Contributor

Choose a reason for hiding this comment

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

Code style change.

{ forList ? null : <TopBar collapsed={collapsed} onCollapseToggle={this.handleCollapseToggle} /> }
{ collapsed ? null : multiFields.map((f, idx) => this.controlFor(f, idx)) }
{forList ? null : <TopBar collapsed={collapsed} onCollapseToggle={this.handleCollapseToggle} />}
{collapsed ? null : multiFields.map((f, idx) => this.controlFor(f, idx))}
Copy link
Contributor

Choose a reason for hiding this comment

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

Code style change.

let result = value.get('___name') || value.get(value.get('name'));
let valuePreview = null;

switch (widget) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This switch statement ties this widget to the widgets below in an undesirable way - the object widget shouldn't know about these other widgets.

@@ -8,14 +8,19 @@ type: "widget"

Copy link
Contributor

Choose a reason for hiding this comment

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

The only changes this file should be additions - changes like switching the list item marker from * to - are code style changes.

@anthonysapp
Copy link
Author

anthonysapp commented Mar 19, 2018

@erquhart I think I've addressed everything in your request.
The only thing I ran into while getting rid of hard coded references to preview components in ObjectPreview (and the reason I hard-coded the references in the first place) is when there is a user defined preview component that overrides the default preview component (IE in index.html), the user defined component expects an "entry" prop. I wasn't sure how to handle this so for now I've wrapped my resulting component in an error boundary component as described here. If an error is caught, it just tries to output a stringified value of the result. Let me know if you think there's a better solution.

@erquhart
Copy link
Contributor

Cool, we'll have to get some eyes on that preview component and consider an alternate approach.

There are still some points from my review that need to be addressed - if you look at the file changes, there are a number of comments still showing. Let me know if you have questions on any of them. Mostly code style changes still remaining, but also .gitignore should have no changes in this PR (currently dist is still being added), and the dist` files that were added to the PR should be removed.

@anthonysapp
Copy link
Author

@erquhart I had fixed the .gitignore and removed the dist files before the last commit, so not sure why you were still seeing those. Apologies - I think I got to all the code style issues now.

const { value, onChange } = this.props;
const parsedValue = itemType;
this.setState({ itemsCollapsed: this.state.itemsCollapsed.push(false) });
onChange((value || List()).push(parsedValue));

Choose a reason for hiding this comment

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

one thing I noticed when trying to use this: the whole of the definition from types is written to the output and you end up with widget and label and the other configuration fields in your formatted file.

this is then read in render, and the widget rendering depends on the whole of the type definition being in the the value object.

ideally this would just store the type name and data in the output, and get the type definition from this.props.types based on the stored type name when rendering.

@morrislaptop
Copy link

Can't wait for this!

@erquhart
Copy link
Contributor

Hey @anthonysapp just checking in - still planning to circle back on the issues raised in the latest comments?

@anthonysapp
Copy link
Author

Hey all this is some great discussion. So sorry to have been unresponsive. I'm quite busy at the moment on another project but I would indeed like to loop back to this in the next week or so, as I'd love to use it for an upcoming project myself :).
If anyone else feels the urge to help out, by all means.

@shaunbent
Copy link

Awesome work on this @anthonysapp - I need this exact feature right now. I've sort of managed to recreate it using registerEditorComponent but its not the best user experience and it also means might front end is a little heavier as I need to transform my Markdown AST to React on the client.

This PR/feature would completely solve my problem. I really hope this makes it in soon.

@anthonysapp
Copy link
Author

@erquhart @cjbrooks12 hi guys, I took a look at this last night and I'm just curious if you both think this should still be merged with the List widget, or you think it warrants its own widget with the proposed changes? Also - I'm not 100% sure I understand this comment. What is inherently wrong with using the "name" field to reference each component? I know I'm probably missing something easy :)

@cjbrooks12
Copy link

cjbrooks12 commented Jun 1, 2018

Technically-speaking, I would see the List widget as a special case of the Modular widget where there is only one available type. I think there is still a case for keeping the Modular widget separate for clarity of intent.

As for the practical reason for wanting to customize the field used as the key, it is simply that the consumers of these modular blocks may consume them differently than Netlify CMS does.

For example, I'm build a SSG with this kind of modularity baked into the framework itself, and Orchid reads these blocks from the type field, not name. See here for an example: the components top-level field is the modular container, and each list item is a modular block identified by its type. Orchid wouldn't be able to understand the output of this widget if it only worked with name.

Or take Forestry's Blocks, which uses template to identify the type of the modular item. If someone were to migrate to Netlify CMS from Forestry, wouldn't it be easier to tell the Netlify CMS widget to look for the template parameter rather than finding and changing all instances of template with name and hoping you got it all right?

As far as making it happen in the code, it shouldn't be much of a change. Rather than doing

item.name

to get the modular type, it would just be one step back with a configuration value to get it instead:

// config.typeKey defaults to 'name' but is configurable by the end-user
item[config.typeKey]

@anthonysapp
Copy link
Author

@cjbrooks12 I think I'm following now. Thanks for the feedback!

@gil--
Copy link

gil-- commented Jun 12, 2018

Just thought I'd say that this is an extremely exciting widget and will really elevate the possibilities (custom layouts on a per page basis). 💯

Looking forward to seeing it shipped.

@LoveYouFyi
Copy link

LoveYouFyi commented Jun 27, 2018

I'm monitoring the status of this modular content widget as it is all I'm waiting for to jump into Netlify CMS.

@erquhart
Copy link
Contributor

Closing as stale - if someone wants to pick it up, please comment.

@airtonix
Copy link

airtonix commented Aug 11, 2018

it would be great if we could use a group of other collections in place of types. This way, existing blocks are re-usable.

DjangoCMS and EPiServer calls these blocks.

And if you have a collection of blocks that are actually layout types with further modular widgets in them, you can do awesome things.

Perhaps the widget should be called include or content-area or blocks, a field that lets me include one or more other collection items from one or more other collections.

@criticalmash
Copy link

What will it take to get this past the finish line? Are there particular features that still need to be implemented? or tests to pass?

I'm willing to help out, but I have zero React experience.

@loremipson
Copy link
Contributor

Hey, our team is currently exploring some ideas that are very similar on our own fork. We'll give an update when we feel more confident in our approach here.

@erquhart
Copy link
Contributor

@criticalmash things are pretty well laid out in the comments above, just a matter of someone putting together a good solution.

@loremipson sounds good 🤞

@mittalyashu
Copy link
Contributor

mittalyashu commented Sep 12, 2018

As discussed in the NetlifyCMS community meeting.

I was trying to say this that it can split the .json file (each object) into different card.

@alexanderfountain
Copy link

This sounds awesome. I have been unable to get this pull working at all with current version of netlifycms though.

@chan360
Copy link

chan360 commented Oct 7, 2018

This sounds like a must have widget when it comes to customizability and I'm wondering if there has been new updates on this?

@erquhart
Copy link
Contributor

No updates, still needs a sponsor.

@erquhart
Copy link
Contributor

erquhart commented Nov 6, 2018

For those watching this issue, a new implementation just popped up courtesy of @smnh in #1857 - we need some reviewing and testing, please check it out and make comments!

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

Successfully merging this pull request may close these issues.

Flexible Layouts Custom Widget