examples/tutorials/5_minder.html
<html>
<head>
<title>Getting Started - Minder</title>
<style>
/*CSS*/
/*END CSS*/
</style>
</head>
<body>
<!-- HTML -->
<script src="../../dist/milo.bundle.js"></script>
<!-- Minder is the key to creating reactive connections in milo. We can bind
the data of components and models together in 1 or 2 way bindings, with
a variety of options including data transformations, and validation. -->
<!-- We'll start by binding an input to a span in a simple 1-way binding -->
<input ml-bind="[data]:nameInput" /> <span ml-bind="[data]:nameLabel"></span>
<!-- Then we'll bind a nested data structure to a milo model. The top
level component has data facet so it can be the top of the tree. And it
needs the container facet in order to create a scope for its child comps-->
<div ml-bind="[data, container]:userProfile">
<!-- Notice that the child components have the data facet. This is how
nested data propagates up and down the tree. The component names
should match the model properties. -->
<p><strong>First Name:</strong> <span ml-bind="[data]:firstName"></span></p>
<p><strong>Last Name:</strong> <span ml-bind="[data]:lastName"></span></p>
</div>
<!-- Then we'll do a 2-way bind. The easiest way show this is with input fields.
So let's make two nested components with some inputs in them. -->
<div ml-bind="[data, container]:form1">
<input ml-bind="[data]:title" placeholder="Title" /><br>
<input ml-bind="[data]:desc" placeholder="Description" /><br>
<input type="checkbox" ml-bind="[data]:available" />
</div>
<!-- To make things interesting, let's bind these two components through a model.
So we can see the contents of the model, we'll print its contents in a
simple component here. -->
<div ml-bind="[data]:formOut"></div>
<!-- Here is our second component for the 2-way bind -->
<div ml-bind="[data, container]:form2">
<input ml-bind="[data]:title" placeholder="Title" /><br>
<input ml-bind="[data]:desc" placeholder="Description" /><br>
<input type="checkbox" ml-bind="[data]:available" />
</div>
<!-- Finally I want to show you how to work with path translation in
milo.minder. Path translation can be used to link up data with
different structure or data paths. Let's create a component data
structure that does not match our previous form components. We'll
also use this example to explore data transformation and validation-->
<div ml-bind="[data, container]:bookData">
<ul ml-bind="[data, container]:info">
<li ml-bind="[data]:name"></li>
<li ml-bind="[data]:description"></li>
</ul>
FREE?: <span ml-bind="[data]:free"></span>
</div>
<!-- TODO more complex example -->
<!-- END HTML -->
<script>
//JS
milo(function() {
//We'll setup some models and bind our components
var userModel = new milo.Model
, formModel = new milo.Model
, scope = milo.binder()
, nameInput = scope.nameInput
, nameLabel = scope.nameLabel
, userProfile = scope.userProfile
, form1 = scope.form1
, form2 = scope.form2
, formOut = scope.formOut
, bookData = scope.bookData;
// We can establish a milo connection by calling milo.minder and
// passing in two data sources, and a string which describes the
// connection mode. The mode string is used to indicate both direction
// and depth of the connection.
// '->' = one way connection of scalar values
// '->>' = one way connection of an object or an array of scalar values.
// '->>>' = one way connection of an array of objects
var connection1 = milo.minder(nameInput.data, '->', nameLabel.data);
// Connection objects returned by milo have the following methods
// connection.turnOff() - to stop the connection
// connection.turnOn() - to turn it back on
// connection.destroy() - to destroy the connection for good
// We can also bind nested data structures to the dom. Let's setup
// another bind our userProfile component to userModel. Being nested
// data, we will need to bind one level deeper. Notice that for the
// component, the data source is not the component itself, but it's
// data facet.
var connection2 = milo.minder(userModel, '->>', userProfile.data);
// Now we can set the model, and the component data, and it's nested
// structure of data will be updated.
userModel.set({
firstName: 'Jason',
lastName: 'Green'
});
// In two seconds we'll set one of the nested values of the model,
// because of the deeper bind, this will propagate to the DOM data.
setTimeout(function() {
userModel('.lastName').set('Verde');
}, 2000);
// Remember we wanted to connect our two form components with a
// model in between. For that we'll need two connections.
// Both of them are two way and one level deep (two arrows).
var formConnect1 = milo.minder(form1.data, '<<->>', formModel);
var formConnect2 = milo.minder(formModel, '<<->>', form2.data);
// So we can see the structure of the model, let's listen to one
// level of data changes (the single '*'), and stringify the model
// out to our formOut component.
formModel.on('*', function(msg, data) {
// Get a the model data as a JSON string
var modelStr = JSON.stringify(formModel.get());
// And set the text of our formOut component
formOut.data.set(modelStr);
});
// Now we want to make a more complicated data connection. Remember
// the last component we set up, it had a completely different data
// structure to our model. We can still link it up, though, by
// using the options of milo.minder.
// Options, for milo.minder, have to be passed as an object, with
// the properties of the options you would like to customise. We
// can set options for path translation, data translation, and
// validation. Let's have a look at the options below.
var options = {
// Here we define our path translation. The property name
// represents the first data source that we pass to minder,
// and the value is the second. So you see we are linking 'title'
// of the model to 'info.name' of the component. Using this,
// we are free to have whatever view structure we like and link
// it to any model data.
pathTranslation: {
'.title': '.info.name',
'.desc': '.info.description',
'.available': '.free'
},
// Next we are going to define a little bit of data translation.
// For each direction, we define what happens to the model's
// data as it gets sent over to the component.
dataTranslation: {
'->': {
// Here we change the title text to upper case.
'.title': function (val) {
return val && val.toUpperCase();
},
// And here we provide a custom message instead of true
// and false. Notice we have to guard the values, in
// case val is undefined.
'.available': function (val) {
return val && val ? 'It\'s in' : 'It\'s gone';
}
}
},
// And now we declare som validation. Validation will do two
// things. If it is invalid, it will prevent the data from
// being written to the destination data source. And in all
// cases it will emit validation events on the origin data
// source. You can decide what data get's sent with the message.
dataValidation: {
// So, when data goes from the model to the component.
'->': {
// For the title we'll pass an array of validation
// functions, these will be executing in order.
'.title': [
// We will call this function, which gets the value
// and a callback as arguments.
function validateLength(val, callback) {
// First we find out if our data is valid
var valid = val && val.length > 4;
// Then we'll compose our response object
var response = valid
? { valid: true }
: { valid: false, reason: 'must be minimum of 5 characters' };
// And pass it to the callback. The first argument
// passed is an error, it should only be not-null
// if an actual error occured, not if the validation
// returns false.
callback(null, response);
}
]
}
}
}
// And now we can link that form to our bookData component's data facet.
// Notice we are passing the options at the end.
var formConnect3 = milo.minder(formModel, '->>', bookData.data, options);
// Let's listen to the validated event on the model to see what happens
// on each validation. You can use this event to take action
formModel.on('validated', function(msg, response) {
console.log(msg, response);
});
});
// Just for fun, let's implement the standard reactive programming task,
// the two-way temperature converter
var C = new milo.Model;
var F = new milo.Model;
milo.minder(C, '<->', F, {
dataTranslation: {
'->': { '': val => val * 1.8 + 32 },
'<-': { '': val => (val - 32) / 1.8 }
}
});
C.set(32); // F will be 89.6 (after data propagation)
F.set(100); // C will be 37.7... (after data propagation)
//END JS
</script>
</body>
</html>