Quick Start Tutorial - adjavaherian/wulf_mvc GitHub Wiki

[Section 1] This Tutorial is designed to get you up and running with Wulf MVC. You should be fairly familiar with Unix, Javascript, jQuery, OOP and the MVC design pattern before attempting the implementation.

  1. Download the latest stable release from [https://github.com/adjavaherian/wulf_mvc/archive/master.zip]

or

  1. Clone the git repo — git clone https://github.com/adjavaherian/wulf_mvc.git

  2. Unpack the repo and move the www directory to your Apache document root.

    mv ~/Desktop/wulf_mvc/www /Library/WebServer/Document/wulf

  3. Check the install at http://localhost/wulf/ note: PHP4 should be installed for the test Ajax server to work

  4. The www directory has a bunch of HTML5 boilerplate errata and css and images for 960 Grid. These, images, text files, etc. can be safely ignored. What we are really interested in this tutorial is the js folder, which should contain the following files:

  • controller.js
  • delegate.js
  • main.js
  • model.js
  • plugins.js
  • view.js

We can ignore the main and the plugins files too. Advanced users can put global functions or plug-ins options in these files.

[Section 2] Delegate Implementation

This implementation maintains strict responsibilities for the HTML, JS, and CSS components. As such, the default install will not have inline styles or Javascript in the HTML. This allows you to concentrate on the MVC implementation and later on you may find this useful for dividing code responsibilities among other developers.

  1. Because we want don't want inline code, you'll notice that the web application classes are instantiated by jQuery's $(document).ready(function(){}); in delegate.js. Advanced users can use the delegate to share global variables or extend the classes.

  2. You'll notice that our delegate is pretty lean, we simply instantiate the classes here and extend them with the Controller.

$(document).ready(function () {

    // instantiate MVC classes
    var model = new $.Model();
    var view = new $.View($("#console"));
    var controller = new $.Controller(model, view);

    // manually call default view constructor
    view.initializeView();

});

Once, I instantiate the classes, I like to manually call view.initializeView(); initalizeView is a helper method that is implemented only in view.js. This is a good place to get the ball rolling in the View. Here , you can do initial DOM manipulations, like emptying divs, setting classes, forking logic for different web apps or notifying listeners that you are ready to hydrate the app with ReST data from the Model.

[Section 3] View.js Implementation

[1.] The View class is essentially an extended function.

jQuery.extend({

    View: function ($console) {

Utilizing this pattern allows you to create clean nested functions, complete with encapsulated methods and inheritance. Through the use of listeners in the Controller class, you can message the View or the Model via dot notation. ie view.yourFunction(args); In our case, you can see that the Delegate has instantiated the view with a jQuery selector as an argument. This allows you to pass DOM objects that you might want the view to know about.

[2.] You'll notice that one of the first few lines of view.js allows us to keep a reference to ourselves.

var that = this;

This line basically maintains the scope of 'this'. 'That' will come in handy later when you want to call methods or access objects within view.js. For example if you want to re-initialize the View from within view.js you might do something like that.initializeView(); instead of view.initializeView(); because View doesn't know what view.anything is. I suppose we could do something like view = this; but we haven't tried that yet (you shouldn't either, until you are comfortable with your application!).

[3.] Next we create a global array which acts as storage for our listeners. This maybe beyond the scope of our tutorial. Check back for the advance tutorials later. You will also notice a little config object. This guy lets you store common variables in one place. Later, from the Controller, you can call these options with view.config.lang or view.config["your string"].

        var listeners = [];

        that.config = {
            lang: 'EN',
            version: '1A01',
            baseURL: 'php/ajax.php'
        };

[4.] In our case, the initializeView function we were talking about earlier, simply appends a couple of buttons to the $console object, which as you may recall was sent to us by the Delegate. Its really just a selector for console div.


        that.initializeView = function () {

            // create buttons to get messages
            $console.append($("<input type='button' value='Load JSON' id='load-json'/>").click(function () {
                that.notifyLoadJson();
            }));

            $console.append($("<input type='button' value='Load HTML' id='load-html'/>").click(function () {
                that.notifyLoadHtml();
            }));
        };

In this example, we also attach a quick and dirty click listener to the buttons. You'll notice that the callback function for the click event is that.notifyLoadJson(); This essentially means that when you click the button, we will notify the Controller of the click through that.notifyLoadJson(); Remember that 'that' refers to the View itself. The second button calls yet another notifier. This is merely to give you a couple of examples to start with. You could easily design the the notifyLoadJson() method to accept arguments and be useful in more than one way.

[5.] updateMessages(str) is an example of an miscellaneous function. You can create as many of these as you need. Our example simply appends the string to the #messages div.

        this.updateMessages = function (str) {
            $('#messages').append(str + "<br>");
        };

[6.] this.addListener() This block should not be modified. It allows the Controller (or other classes) to register itself as a ViewListener. All listeners are stored in an array and messaged by notifiers (see below).

        this.addListener = function (list) {
            listeners.push(list);
        };

[7.] Function notifyLoadJson() is a perfect example of how we prepare messages for the listeners. In this case the only listener is the Controller. Essentially, you can think of these as outlets or notifiers to other classes. You can also create as many of these as you need. As long as you maintain the pattern we've provided for you, you should be able to create notifiers quickly and easily.

        this.notifyLoadJson = function () {
            $.each(listeners, function (i) {
                listeners[i].loadJson();
            });
        };

These notifiers are a great idea, because they provide discrete interfaces between the classes. If you follow the pattern, your code will be very easy to read and debug by simply following the flow. For example, in abstract, you can think of the program flow as such:

view.click -> view.notify -> view.message_listeners -> controller.listen_for_notification controller.do_something -> model.get_data

The above abstract assumes a callback in the Controller. In abstract, a complete round trip might look something like this:

view.click -> view.notify -> view.message_listeners -> controller.listen_for_notification controller.do_something -> model.get_data -> model.notify -> model.message_listeners -> controller.listen_for_notification -> controller.do_something -> view.display_the_data

[8.] The final block implements some of the magic of the listeners. Essentially, this pattern needs to remain the same throughout the implementation and you only need to write this one time (so don't touch it!). However, you will need to name your own outlets or listeners within the block;

    ViewListener: function (list) {
        if (!list) { list = {}; }
        return $.extend({
            loadJson: function () {},
            loadHtml: function () {}
        }, list);
    }

See above where we have defined loadJson and loadHtml? These are essentially declared functions or 'interfaces' for the Controller, so you will want to have the same function names represented in the Controller's listener sections. In our case, this.notifyLoadJson() actually calls the loadJson() method of all our listeners. So when you fire this method, you are firing that method on all the the listeners. Again, in our case you are only firing the method in the Controller, but later, your more complicated web app might have a few different listeners. For now, simply make sure to prototype each function you want your listeners to know about here. Follow the pattern and add your prototypes below: loadHtml: function () {} A loaded ViewListener might look something like this:

    ViewListener: function (list) {
        if (!list) { list = {}; }
        return $.extend({
            loadJson    : function () {},
            loadFoo     : function () {},
            loadBar     : function () {},
            sendArg     : function (myarg) {},
            postJson    : function (url, myJson) {}
        }, list);
    }

Easy Right?! If you get anything out of the tutorial make to review this section a few times before moving on. Once you fully understand how this works, you will be able to create all sorts of virtual classes using the same pattern. In fact, when you get to the Model, you will notice that the pattern is identical to the View. The only difference is what methods and listeners you define and how you use them. The idea behind any good MVC is to create a clear separation of responsibilities between classes. This methodology keeps your code from looking like spaghetti and allows you and your team to quickly and easily fix bugs and introduce new functionality to your web app. Oh, and have I mentioned that it's fast!?

[Section 4] Controller.js Implementation

[1.] Just like the View implementation in Section 3, you'll notice that the Controller is also an extended object (essentially two objects folded together).

jQuery.extend({

    Controller: function (model, view) {

However, in the case of the Controller, you will see two very important arguments which allow us to extend the listeners to the Controller. Eventually, you may have a few classes you want to listen to in the argument list. But, for now. KISS! I'm sure you are very bright. ...but don't mess with these lines yet.

[2.] Preparing the listeners: These lines are also very important to a working implementation.

var vlist = $.ViewListener({});
view.addListener(vlist);

You won't see the lines like this in your package, but essentially this is what they need to look like.

Notice that ViewListener({}) is the name of the ViewListener function in View. These names need to be the same. So any listeners that you create in the Controller should be present in the classes you have created. For example, you will also notice that we have ModelListener({}) present in the Controller and the Model as well.

[3.] If you follow the default pattern in Wulf MVC, you will not need to make any changes to these lines, but you will probably want to create your own listener methods in the Controller. To do this, take a look at one of the functions we have prepared for you:

            loadJson: function () {

                var data = {
                    'id': 'true'
                };

                model.getJSON(view.config.baseURL, data, function (response) {
                    $.each(response.employees, function (key, val) {
                        view.updateMessages(val.name); // or val['name']
                    });

                });

            },

Remember the loadJson function that we declared in the View? Here is where she gets implemented.

The first line must contain the name of extended listener and any arguments you wish to pass to it.

loadJson: function () {}

To maintain the nested function pattern, subsequent listeners in the block will require a comma between them. For example the hypothetical set of listeners that we defined in the ViewListener in Section 3, will look just like this in our extended ViewListener function in the Controller.

    var vlist = $.ViewListener({
    
            loadJson    : function () {},
            loadFoo     : function () {},
            loadBar     : function () {},
            sendArg     : function (myarg) {},
            postJson    : function (url, myJson) {}
    
    });
    
    view.addListener(vlist);

[4.] Once the listeners have been properly setup, you can go ahead and do pretty much anything you want within the functions. In our loadJson example above, we simply define the variable 'data' and invoke the model.getJSON(url, data, callback) method. On response, we loop through the data, (without any error checking.) and update the View using view.UpdateMessages(string). This is an example of a one way message, where the Model doesn't fire off any of its own notifiers. It just returns the data to the response function on success. Remember, these methods are not part of any sort of API or such. They are pretty much arbitrary, but designed to help you understand functional uses of the pattern. In fact, the loadJson() method might be a bit to complex for this tutorial, so lets try using the loadHtml() function instead.

[5.] The other listener you will see in the default ViewListener is loadHtml().

            loadHtml: function () {
                model.getHtml();
            }

This guy could not be any simpler. In abstract; listen to the view, when you hear loadHtml get called, go directly to the Model's getHtml() method. What does model.getHtml() do? Well, since Model uses the same pattern as View, we'll skip the details of Model's implementation and just talk about how what that function does. But, hold on, we're almost done! Let's do a quick review of the Controller's ModelListener too.

[6.] ModelListener

Like I said before, the ModelListeners look just like the ViewListeners! Essentially, you create your mlist object just like you did with vlist and you add it to the list of listeners in the Model. The only rule here is that you define the ModelListener with the same name as you defined it in the Model itself.

var mlist = $.ModelListener({});
model.addListener(mlist);

Any listeners in within $.ModelListener({}); are discrete functions in the Controller and you can use them to perform any kind of simple or advance functionality. Our simple example has a method that helps you update the View when one of your AJAX calls fails.

            loadFail: function () {
                view.updateMessages("ajax error");
            },

This is essentially the implementation for the loadFail interface defined in the Model's listener set. On the flipside in model.js you will notice that the getHtml() function has a notifier attached to the ajax error handler.

[Section 5] Model.js Implementation

[1.] Model.js is just like View.js! Well, not exactly. Models don't like to mix it up with your basic View class. Sure, you can write her emails, or buy her cosmetics line, but she is in a class of her own. That means, she has her own functions, her own properties and her own listeners... and she doesn't talk to anybody but her Controller, right?!

[2.] That being well and good, let's wrap it up by examining one of her functions and its related notifier

    this.getHtml = function () {

            $.ajax({
                url: 'php/ajax.php',
                type: 'GET',
                dataType: 'html',
                timeout: 1000,
                error: function () {
                    that.notifyLoadFail();
                },
                success: function (data) {
                    that.notifyLoadFinish(data);
                }
            });

        };

Above, you will see model.getHtml(). This is an arbitrary helper function that simply uses jQuery's ajax method to GET data from the PHP server. The request either succeeds or fails and each of the cases has its own notifier.

[3.] Notify Load FAIL! or that.notifyLoadFail(); is the simpler of the two, because it excepts no arguments, but essentially perform similar functions as simple notifiers to the listeners.

        this.notifyLoadFail = function () {
            $.each(listeners, function (i) {
                listeners[i].loadFail();
            });
        };

It's all coming together, right? You'll be on your own in no time. What does this guy do? He simply invokes the loadFail() method on each of the Model's listeners.

[4.] The listeners are in turn extend thus:

    ModelListener: function (list) {
        if (!list) { list = {}; }
        return $.extend({
            loadFinish: function (data) {},
            loadFail: function () {}
        }, list);
    }

Congratulations!

You are now done with your first Wulf MVC tutorial. We hope you had a good time learning about how to use the pattern. Now that you understand how this stuff works, go ahead and play around. Try creating your own methods and listeners and try sending messages back and forth between the classes. Based on what we discussed, it should be fairly trivial for you to use the View to bind an on click event to button or div. Try making the round trip:

View to the View Listener to the Controller to the Model to the Model listener to Controller and back to the View.

Once you get comfortable with that, try doing a half trip with a callback in the ViewListener. The possibilities are endless: GETs, POSTs, static data in the Model, dynamic data, cached data, fully ReSTFull web apps, etc.

Here's an example of a site I created with Wulf MVC while working in Cupertino.

http://www.apple.com/support/systemstatus/

I've actually used Wulf MVC for many projects over the last few years. Its been totally fun and useful for me.

Happy coding, please stay tuned for more advanced tutorials and use cases.

-ad