Quantcast
Channel: Planet Plone - Where Developers And Integrators Write
Viewing all articles
Browse latest Browse all 3535

Asko Soukka: Building a Plone form widget with React + Redux

$
0
0

As much I love the new through-the-web resource registries in Plone 5 (I really do), for the current Plone 5 sites in development or already in production, I've ended up bundling all front-end resources into theme with Webpack.That gives me the same "state of art" frontend toolchain to other current projects, but also adds some overhead, because I need to do extra work for each new add-on with front-end resources. So, I still cannot really recommend Webpack for Plone, unless you are already familiar with Webpack. Yet, learning to bundle everything with Webpack really helps to appreciate, how well Plone 5 resource registries already work.

My current workflow, in brief, is to add all common configration into plonetheme.webpack and re-use that as a git submodule in individual projects, similarly to plonetheme.webpackexample. The latter also includes the example code for this post. I was asked, how everything goes together when using React and Redux for building widgets for Plone. Here's how...

(You can see the complete example in plonetheme.webpackexample, particuarly in 1 and 2.)

Injecting a pattern with Diazo

In a usual use case, I have a custom content type (maybe TTW designed) with simple textline or lines (textarea) fields, which require rich JavaScript widgets to ease entering of valid input.

The current Plone convention for such widgets is to implement the widget as a Patternslib compatible pattern. The required classname (and options) for the pattern initialization could, of course, be injected by registering a custom z3c.form widget for the field, but it can also be done with a relatively simple Diazo rule with some XSLT:


<!-- Inject license selector pattern -->
<replacecss:content="textarea#form-widgets-IDublinCore-rights">
<xsl:copy>
<xsl:attributename="class">
<xsl:value-ofselect="concat(@class, ' pat-license-selector')"/>
</xsl:attribute>
<xsl:apply-templatesselect="@*[name()!='class']|node()"/>
</xsl:copy>
</replace>

Registering a pattern in ES6

Of course, you cannot yet use ES6 in Plone without figuring out a way to way to transpile it into JavaScript currently supported by your target browsers and RequireJS (that something, which comes quite easily with Webpack). If you can do it, registering a Patternslib compatible pattern in ES6 appeared to be really simple:


importRegistryfrom'patternslib/core/registry';

// ... (imports for other requirements)

Registry.register({

name:'license-selector',
trigger:'.pat-license-selector',

init($el,options){
// ... (pattern code)
}
});

Choosing React + Redux for widgets

You must have already heard about the greatest benefits in using React as a view rendering library: simple unidirectional data flow with stateless views and pretty fast rendering with "shadow DOM" based optimization. While there are many alternatives for React now, it probably has the best ecosystem, and React Lite-like optimized implementations, make it small enough to be embeddable anywhere.

Redux, while technically independent from React, helps to enforce the React ideals of predictable stateless views in your React app. In my use case of building widgets for individual input fields, it feels optimal because of its "single data store model": It's simple to both serialize the widget value (Redux store state) into a single input field and de-serialize it later from the field for editing.

Single file React + Redux skeleton

Even that Redux is very small library with simple conventions, it seems to be hard to find an easy example for using it. That's because most of the examples seem to assume that you are building a large scale app with them. Yet, with a single widget, it would be nice to have all the required parts close to each other in a single file.

As an example, I implemented a simple Creative Commons license selector widget, which includes all the required parts of React + Redux based widget in a single file (including Patternslib initialization):


importReactfrom'react';
importReactDOMfrom'react-dom';
import{createStore,compose}from'redux'
importRegistryfrom'patternslib/core/registry';

// ... (all the required imports)
// ... (all repeating marker values as constants)

functiondeserialize(value){
// ... (deserialize value from field into initial Redux store state)
}

functionserialize(state){
// ... (serialize value Redux store state into input field value)
}

functionreducer(state={},action){
// ... ("reducer" to apply action to state and return new state)
}

exportdefaultclassLicenseSelectorextendsReact.Component{
render(){
// ...
}
}

LicenseSelector.propTypes={
// ...
};

// ... (all the required React components with property annotations)

Registry.register({
name:'license-selector',
trigger:'.pat-license-selector',

init($el){
// Get form input element and hide it
constel=$el.hide().get(0)

// Define Redux store and initialize it from the field value
conststore=createStore(reducer,deserialize($el.val()));

// Create container for the widget
constcontainer=document.createElement('div');
el.parentNode.insertBefore(container,el);
container.className='license-selector';

// Define main render
functionrender(){
// Serialize current widget value back into input field
$el.val(serialize(store.getState()));

// Render widget with current state
ReactDOM.render((
<LicenseSelector
// Pass state
{...store.getState()}
// Pass Redux action factories
setSharing={(value)=>store.dispatch({
type:SET_SHARING,
value:value
})}
setCommercial={(value)=>store.dispatch({
type:SET_COMMERCIAL,
value:value
})}
/>
),container);
}

// Subscribe to render when state changes
store.subscribe(render);

// Call initial render
render();
}
});

Not too complex, after all...

Implementing and injecting a display widget as a themefragment

Usually displaying value from a custom field requires more HTML that's convenient to inline into Diazo rules, and may also require data, which is not rendered by the default Dexterity views. My convention for implementing these "display widgets" in theme is the following combination of theme fragments and Diazo rules.

At first, I define a theme fragment. Theme fragments are simple TAL templates saved in ./fragments folder inside a theme, and are supported by installing collective.themefragments add-on. My example theme has the following fragment at ./fragments/license.pt:


<htmlxmlns="http://www.w3.org/1999/xhtml"
xmlns:tal="http://xml.zope.org/namespaces/tal">
<body>
<ptal:condition="context/rights|undefined">
<imgsrc="https://i.creativecommons.org/l/${context/rights}/4.0/88x31.png"
alt="${context/rights}"/>
</p>
</body>
</html>

Finally, the fragment is injected into desired place using Diazo. In my example, I use Diazo inline XSLT to append the fragment into below content viewlets' container:


<!-- Inject license badge below content body -->
<replacecss:content="#viewlet-below-content-body"
css:if-not-content="textarea#form-widgets-IDublinCore-rights">
<xsl:copy>
<xsl:apply-templatesselect="@*|node()"/>
<xsl:copy-ofselect="document('@@theme-fragment/license',
$diazo-base-document)/html/body/*"
/>
</xsl:copy>
</replace>

Viewing all articles
Browse latest Browse all 3535

Trending Articles