examples/tutorials/5_minder.html

Summary

Maintainability
Test Coverage
<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>