Template2
July 7, 2016 · View on GitHub
comerc:template2
MVVM for Meteor with Two-Way Binding via Model Schema.
Table of Contents
Table of Contents generated with DocToc
- Intro
- Key features
- How to run Demo
- Installation
- Basic Usage
- API
- Two-Way Binding features
- Configuration
- TODO
- Inspired by
- License
Intro
Fork of TemplateController (what difference):
Supports the best practices of writing Blaze templates
Blaze is awesome but writing the Js part of templates always felt a bit awkward. This package just provides a very thin layer of syntactic sugar on top of the standard API, so you can follow best practices outlined in the Blaze guide more easily.
Now you can turn this:
You have clicked the button {{counter}} times.
<button>click</button>
Template.hello.onCreated(function helloOnCreated() {
// counter starts at 0
this.counter = new ReactiveVar(0);
});
Template.hello.helpers({
counter() {
return Template.instance().counter.get();
},
});
Template.hello.events({
'click button'(event, instance) {
// increment the counter when button is clicked
instance.counter.set(instance.counter.get() + 1);
},
});
into that:
You have clicked the button {{state.counter}} times.
<button>click</button>
Template2('hello', {
states: {
counter: 0 // default value
},
events: {
'click button'() {
// increment the counter when button is clicked
this.state.counter += 1;
}
}
});
Yeah i have used ViewModel before – it's also nice, and there are also Blaze Components. My only "problem" with these existing packages is that they introduce new concepts on top of the standard Blaze api. I just wanted less boilerplate and that best practices like setting up ReactiveVars, validating properties passed to a template or accessing Template.instance() become a no-brainer for the whole team.
The idea for this package came up during a Meteor training with some Devs where realized how complicated it is to explain the best practices with Blaze and that they had a ton of questions like "how can i access the template instance in helpers / event handlers" or "how does a template manage state" – which is so basic that it should be the easiest thing in the world.
Key features
- Compatible with Blaze Template - we love it.
- Minimum changes for migration your great project to Template2.
- One time declaration of variables to Model via
<input value-bind>attribute. - Validate input data and get doc for writing to Model without coding.
- Support of SimpleSchema, may be extended for support any other Model (Astronomy etc.)
- Usage of Two-Way Binding Features without Model.
How to run Demo
$ git clone https://github.com/comerc/meteor-template2.git
$ cd meteor-template2
$ meteor
...then http://localhost:3000
Installation
In your Meteor app directory, enter:
$ meteor add comerc:template2
Basic Usage
$ meteor add aldeed:simple-schema
$ meteor add aldeed:collection2
Posts = new Mongo.Collection('posts');
PostSchema = new SimpleSchema({
myValue: {
type: String,
min: 3,
defaultValue: '777'
}
});
Posts.attachSchema(PostSchema);
<body>
{{> hello myParam="123"}}
</body>
<template name="hello">
<p><code>props.myParam</code> {{props.myParam}}</p>
<p><code>state.myValue</code> {{state.myValue}}</p>
<form>
<input value-bind="myValue"/>
<button type="submit">Submit</button>
</form>
<p>{{state.errorMessages}}</p>
</template>
Template2('hello', {
// Validate the properties passed to the template from parents
propsSchema: new SimpleSchema({
param: { type: String }
}),
// Setup Model Schema
modelSchema: Posts.simpleSchema(),
// Setup reactive template states
states: {},
// Helpers & Events work like before but <this> is always the template instance!
helpers: {}, events: {},
// Lifecycle callbacks work exactly like with standard Blaze
onCreated() {}, onRendered() {}, onDestroyed() {},
});
// events declaration by old style, but with context by Template.instance()
Template.hello.eventsByInstance({
'submit form': function(e) {
e.preventDefault();
// Get doc after clean and validation for save to model
this.viewDoc(function(error, doc) {
if (error) return;
Posts.insert(doc);
});
}
});
// onRendered declaration by old style may also be used
Template.hello.onRendered(function() {
var self = this;
this.autorun(function() {
var doc = Posts.findOne();
if (doc) {
// Set doc from model to view
self.modelDoc(doc);
}
});
});
API
onCreated, onRendered, onDestroyed
Work exactly the same as with standard Blaze.
events, helpers
Work exactly the same as normal but this inside the handlers is always
a reference to the Template.instance(). In most cases that's what you want
and would expect. You can still access the data context via this.data.
propsSchema: { clean: Function, validate: Function }
Any data passed to your component should be validated to avoid UI bugs
that are hard to find. You can pass any object to the propsSchema option, which
provides a clean and validate function. clean is called first and can
be used to cleanup the data context before validating it (e.g: adding default
properties, transforming values etc.). validate is called directly after
and should throw validation errors if something does not conform the schema.
This api is compatible but not limited to
SimpleSchema.
This is a best practice outlined in the
Blaze guide - validate data context
section. Template2 does provide a bit more functionality though:
any property you define in the schema is turned into a template helper
that can be used as a reactive getter, also in the html template:
Template2('hello', {
propsSchema: new SimpleSchema({
messageCount: {
type: Number, // allows only integers!
defaultValue: 0
}
})
});
<template name="hello">
You have {{props.messageCount}} messages.
</template>
… and you can access the value of messageCount anywhere in helpers etc. with
this.props.messageCount
a parent template can provide the messageCount prop with standard Blaze:
<template name="parent">
{{> hello messageCount=unreadMessagesCount}}
</template>
If the parent passes down anything else than an integer value for messageCount
our component will throw a nice validation error.
modelSchema: { schema: Function, newContext: Function }
You can pass any object to the modelSchema option, which provides a schema and newContext function.
This api is compatible but not limited to SimpleSchema.
states: { myProperty: defaultValue, … }
Each state property you define is turned into a ReactiveVar and you can get
the value with this.state.myProperty and set it like a normal property
this.state.myProperty = newValue. The reason why we are not using
ReactiveVar directly is simple: we need a template helper to render it in
our html template! So Template2 actually adds a state template
helper which returns this.state and thus you can render any state var in
your templates like this:
You have clicked the button {{state.counter}} times.
But you can also modify the state var easily in your Js code like this:
events: {
'click button'() {
this.state.counter += 1;
}
}
Since each state var is turned into a separate reactive var you do not run into any reactivity issues with re-rendering too much portions of your template.
viewDoc(callback)
Get doc via callback after clean and validation for save to model.
var t = Template.instance();
t.viewDoc(function(error, doc) {
if (error) return;
Posts.insert(doc);
});
modelDoc(doc)
Set doc from model to view.
var t = Template.instance();
var doc = Posts.findOne();
t.modelDoc(doc);
Two-Way Binding features
Described here.
Configuration
Template2Config.propsClean
Enables you to configure the props cleaning operation of libs like SimpleSchema. Checkout SimpleSchema clean docs to see your options.
Here is one example why removeEmptyStrings: true is the default config:
{{> button label=(i18n 'button_label') }}
i18n might initially return an empty string for your translation.
This would cause an validation error because SimpleSchema removes empty strings by default when cleaning the data.
Template2Config.modelClean
The same as previos, but for model.
TODO
- AstronomySchema (for compatible with SimpleSchema)
- Add
originalanderror* helpers, like useful:forms - Wait for throttle & debounce before form submit
- Demo with meteor7
- Actions, like blaze-magic-events
- Remove underscore dependence
- Demo with Material Design Lite
- Remove helpers
propsOfandstateOf, we may use{{state.[field-name]}} - Dependencies
- Conditional formatting and disabling
Change Log
1.5.0
Template2.mixin()rename toTemplate2()Template2.setPropsCleanConfiguration(Object)rename toTemplate2Config.propsCleanTemplate2.setModelCleanConfiguration(Object)rename toTemplate2Config.modelClean
Inspired by
- jQuery.my
- Aurelia
- Vue
- ReactLink
- Blaze
- aldeed:autoform
- manuel:viewmodel
- nov1n:reactive-bind
- space:template-controller
- themeteorites:blaze-magic-events
- moberegger:validated-template
- voidale:helpers-everywhere
- useful:blaze-state
- useful:forms
- ouk:template-destruct
- mpowaga:template-schema
- peerlibrary:blaze-components
- aldeed:template-extension
- meteorhacks:flow-components
- kadira:blaze-plus
- templates:forms
License
MIT