react-tmpl v0.1.6
React-TMPL
I found myself repeating the same stuff over and over again when creating React templates. This is an attempt at abstracting a bunch of the boilerplate.
What is a template?
The way I work, React components can be separated in two types:
- Functional
- Representational Yes, I know this is kind of a throwback to MVC (or at least VC), but there isn't really another way to obtain the following benefits:
- Switch your templates for React-Native
- Offer modules that can be seamlessly integrated with material-ui or bootstrap with minimal effort
- not write a lot of boilerplate to pass down presentational options from components to nested components. I want class names to be concatenated, styles to be merged, recursively and without writing anything.
How it works
just use template(renderFunction,defaultProps)
import tmpl from 'react-tmpl';
tmpl(
function Button(props){
if(props.action){
props.onClick = props.action;
}
return (<button {...props} key={key}>{props.text}{this.getTime()}</button>);
}
, {
propTypes:['text']
, type:'primary'
, className:'button'
, getTime(){
return new Date();
}
}
)Now you can use template.Button.
Any property (that is not a function) given in the object will be a defaultProp.
Special keys:
statewill be the initial statestylewill be recursively mergedcsswill be recusiverly merged IF the propincludeCSSistrue(which allows to develop inline styles, then export them to css)classNamewill be recursively merged. In your render template, they will be automatically converted to a string through classnamespropTypeswill become the Component'spropTypes, and can be an array of keys (in which case, they will all default toPropTypes.any.isRequired). You are also free to pass in a regular propTypes object.namewill be used asdisplayName, and will override the function name (if there was one). Note thatnameis required.buildLocalsis a function you can use to pre-process the locals.initializeis a constructor function to set properties you needstyle.hoverandstyle.focuswill automatically createonMouseEnter,onMouseOut(oronFocus,onBlur) and add keys (state.hoverorstate.focus). Note thatcss.hoverandcss.focusdo not trigger this behavior (the assumption being, you do not want to handle those states in javascript if you have those in css).
Additionally:
Any property that is a function will be added to the template's prototype, making it available in the render function
Any property that begins with on, such as onResize, onClick and so on, will be scoped to the current object automatically (good old magic)
Any property that begins with a capital letter will:
- create a method of the same name on the object
- be considered a template's options, thus overriding the default template options.
Consider the following Component:
tmpl(
function CloseButton(props){
return this.Button({type:'secondary'})
}
, {
Button:{
text:'×'
}
, classname:'close'
}
)This component does several things:
this.Button()calls theButtontemplate (or throws ifButtonwas not defined). The function is binded to the instance and takes two arguments: optionalprops, and optionalkey. This allows to use the function directly in a map (return props.array.map(this.Template))- it will merge the props given in the render function (
{type:'secondary'}) with the default defined props ({text:'×'}) - it will merge those props with the
Buttontemplate default props (typeandgetTime) - the final button will have the classes
button close.
Should you want to override this default behavior, make Button a function:
tmpl(
function CloseButton(props){
return this.Button({type:'secondary'})
}
, {
Button(givenProps){
const locals = this.locals;
const closeButtonProps = locals.props;
// `givenProps` is what is passed in the render function above ({type:'secondary'})
// `locals` is the full options object
// `locals.prop` is the object passed to the render function
}
}
)If you want to include a template without passing options, it's enough to just do:
{
/*...*/
Button:{}
/*...*/
}To switch a template:
CloseButton.templates.Button = class MyButton extends React.Component{/*...*/}react-tmpl offers a helper to get data from the current locals:
prop(predicate), where predicate can be either a string, or a function.
Here's how you would use it:
import tmpl,{prop} from 'react-tmpl'
var ids = 0;
tmpl(
function CloseButton(props){
return this.Button({type:'secondary'})
}
, {
initialize(props){
// here, `props` is what is natively passed to the component
this.id = ids++
}
, buildLocals(locals){
locals.props.id = this.slug(props.text+this.id);
return props;
}
, slug(text){
return text.replace(/[\s*&%$#]/g,'-');
}
, text:prop((locals)=>locals.props.mini?'×':'close')
, Button:{
text:prop('text')
, id:prop('slug')
}
}
)prop('string') is equivalent to props(locals=>locals.props.string).
One last thing to know is that you can namespace your templates:
import {createTemplates} from 'react-tmpl'
const template = createTemplates();
template(/*...use it as usual*/)Install
npm install react-tmplStructure of a React Template
How locals are built
this.locals is rebuilt on every render. It contains, at the minimum, a property this.locals.props wich gets passed to the render function. It may also contain a number of keys for every nested template (e.g., this.locals.Button).
The built process is as follows:
this.locals.props.classNamegets merged from default properties (passed at template creation) and passed props (from a parent template, or from the user)this.locals.props.stylegets merged from default properties and passed props- Any function that begins with an
onXgets added tothis.locals(soonClickand company are added to the bundle -- Bear in mind those functions have been scoped to the current instance already at this point). However, if passed props also contain a similarly namedonXfunction, they will overwrite those (which are still accessible in the render function asthis.onX) this.locals.propswill get merged with passedprops, the latter keys overriding the former.this.localsgets through a functionprocessProps, that does nothing (useful for overriding stuff in your templates)this.localsgets 'computed'. That is, everypropcall gets resolved. At this point,this.localsis a fully serializable object.this.locals.props.classNamegets through classnamesthis.localsget through a functionbuildLocals, that does nothing. This is equivalent toprocessProps, the difference being, this is a fully resolved object.
TODO
- Better documentation
- Tests
- Find a way to extract
cssto a stylesheet