Search

Categories

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Send mail to the author(s) E-mail

# Thursday, 29 September 2011
( BackboneJS | UX | VidPub )

image

catching save.

FormView = Backbone.View.extend({
    initialize: function () {
        _.bindAll(this, 'render');
        this.template = $("#productionFormTemplate");
    },

    events: {
        "change input": "updateModel",
        "submit #productionForm": "save"
    },
    save: function () {
        this.model.save(
            this.model.attributes,
            {
                success: function (model, response) {
                    alert(model.get("Title") + " saved");
                    model.collection.trigger("itemSaved", model);
                },
                error: function (model, response) {
                    alert("problem");
                    model.trigger("itemError", "There was a problem saving " + model.get("Title"));
                }
            }
        );
        return false;
    },

 

**BREAK**

heading towards this:

image

image

nice notifications!

Validations are built into backbone:

image

Is it Really Worth It?

  • Time spent on user experience is really valuable
  • Compelling to use
  • Good for us as developers to stretch
| | # 
# Wednesday, 28 September 2011

http://documentcloud.github.com/backbone/

MVC for js?

http://documentcloud.github.com/backbone/examples/todos/index.html

image

http://www.quora.com/What-are-some-good-resources-for-Backbone-js

this is hard!  Rob used peepcode.com  backbone screencast.  Took him 2 weeks to do this.

Use IIS Express.

Areas

Added an area called Api

image

http://localhost:50363/api/productions/

namespace VidPub.Web.Areas.Api.Controllers{
    public class ProductionsController : ApplicationController {
        DynamicModel _productions;

        public ProductionsController(ITokenHandler tokenStore):base(tokenStore) {
            _productions = new Productions();
        }

        [HttpGet]
        public ActionResult Index() {
            return VidpubJSON(_productions.All());
        }
    }
}

image

Alt+J – in chrome to open downloads, then tab lots to show in folder

Added backbone-min.js from another branch https://github.com/documentcloud/backbone/tree/gh-pages

and underscore-min.js http://documentcloud.github.com/underscore/

VidPub/Productions - View

Backbone is Event-driven.

el is short for Element.

Reference an ID: 

#production-list

Reference a class:

.production-list ?

ListView = Backbone.View.extend({

    render: function () {
        $(this.el).html("<h1>Hello!</h1>");
        return this;
    }

});

jQuery(function () {
    var view = new ListView();
    $("#production-list").html(view.render().el);

});

asdf

@Assets.Script("underscore-min.js")
@Assets.Script("backbone-min.js")
@Assets.Script("production-admin.js")

<div id="production-list"></div>

image

and refactoring:

ListView = Backbone.View.extend({

    initialize: function () {
        this.template = $("#listTemplate");
    },

    render: function () {
        var html = this.template.tmpl({ name: "Tekpub" });
        $(this.el).html(html);
        return this;
    }

});

jQuery(function () {
    var view = new ListView();
    $("#production-list").html(view.render().el);

});

view:

<script type = "text/javascript" src="http://ajax.aspnetcdn.com/ajax/jquery.templates/beta1/jquery.tmpl.min.js"></script>
@Assets.Script("underscore-min.js")
@Assets.Script("backbone-min.js")
@Assets.Script("production-admin.js")

<script id="listTemplate" type="text/html">
    <h1>${name}</h1>
</script>
<div id="production-list"></div>

image

so all he’s doing here is pushing html into the dom.

ViewEvents

But its part of the template and not available when the page loads.

image

View:

<script type = "text/javascript" src="http://ajax.aspnetcdn.com/ajax/jquery.templates/beta1/jquery.tmpl.min.js"></script>
@Assets.Script("underscore-min.js")
@Assets.Script("backbone-min.js")
@Assets.Script("production-admin.js")

<script id="listTemplate" type="text/html">
<h1>${name}</h1>
<a href="#">Click me!</a>
</script>
<div id="production-list"></div>

js

ListView = Backbone.View.extend({

    initialize: function () {
        this.template = $("#listTemplate");
    },
    events: {
        "click a:": "clickityClick"
    },
    clickityClick: function () {
        alert("I been clicked");
    },

    render: function () {
        var html = this.template.tmpl({ name: "Tekpub" });
        $(this.el).html(html);
        return this;
    }

});

jQuery(function () {
    var view = new ListView();
    $("#production-list").html(view.render().el);

});

here is an example of el not wired up.. and event doesn’t work:

ListView = Backbone.View.extend({

    initialize: function () {
        this.template = $("#listTemplate");
    },
    events: {
        "click a:": "clickityClick"
    },
    clickityClick: function () {
        alert("I been clicked");
    },

    render: function () {
        var html = this.template.tmpl({ name: "Tekpub" });
        //$(this.el).html(html);
        //sidestep el completly and getting jQuery to patch in the template
        $("#production-list").html(html);
        return this;
    }
});

jQuery(function () {
    var view = new ListView();
    //$("#production-list").html(view.render().el);
    view.render();
});

Working With Data

Production = Backbone.Model.extend({});


ListView = Backbone.View.extend({

    initialize: function () {
        this.template = $("#listTemplate");
    },
    events: {
        "click a:": "clickityClick"
    },
    clickityClick: function () {
        alert("I been clicked");
    },

    render: function () {
        var html = this.template.tmpl(this.model.toJSON());
        $(this.el).html(html);
        return this;
    }
});

jQuery(function () {
    var production = new Production({ name: "MVC3!" });
    var view = new ListView({model: production});
    $("#production-list").html(view.render().el);
});

image

pub/sub

binding/subscribing to the change event on this.clickityClick ** careful to use this inside function, otherwise js will look globally in the global window object.

Also setting model to a global variable so we can work with it:

image

Manually resetting the title to Peaches in chrome.

Bind our change event to render action in the view

Production = Backbone.Model.extend({});


ListView = Backbone.View.extend({

    initialize: function () {
        this.template = $("#listTemplate");
        this.model.bind("change", this.render);
        window.ViewModel = this.model;
    },
    events: {
        "click a:": "clickityClick"
    },
    clickityClick: function () {
        alert("I been clicked");
    },

    render: function () {
        var html = this.template.tmpl(this.model.toJSON());
        $(this.el).html(html);
        return this;
    }
});

jQuery(function () {
    var production = new Production({ name: "MVC3!" });
    listView = new ListView({model: production});
    $("#production-list").html(listView.render().el);
});

then in chrome:

listView.model.set({name:"EF"})

image

the this in this.model.toJSON()  keyword changes scope when fired outside of a view such as on a model or a collection

userscore library has a handy bindAll library which resets scope of this to the current view.

image

Production = Backbone.Model.extend({});


ListView = Backbone.View.extend({

    initialize: function () {
        _.bindAll(this, 'render');
        this.template = $("#listTemplate");
        this.model.bind("change", this.render);
        window.ViewModel = this.model;
    },
    events: {
        "click a:": "clickityClick"
    },
    clickityClick: function () {
        alert("I been clicked");
    },

    render: function () {
        var html = this.template.tmpl(this.model.toJSON());
        $(this.el).html(html);
        return this;
    }
});

jQuery(function () {
    var production = new Production({ name: "MVC3!" });
    listView = new ListView({model: production});
    $("#production-list").html(listView.render().el);
});

Backbone collections

What if we need more than 1 model.

Production = Backbone.Model.extend({});
Productions = Backbone.Collection.extend({
    model : Production,
    url: "/api/productions"
});

image

image

these are backbone models.

image

actual model

reset the view to use a collection instead of a model.

“reset” whenever data is firehosed into collection.

  • page is going to load
  • view will be instantiated with empty collection
  • el is going to be specified
  • fetch is going to be called
  • once fetch is completed, collection will fire its reset event
  • which will fire the render event
  • render will then push the templated data into el

image

productions.reset([{Title:"One"}, {Title:"Two"}])   - this will changed the data on the screen as event driven will fire.

Production = Backbone.Model.extend({});
Productions = Backbone.Collection.extend({
    model : Production,
    url: "/api/productions"
});
productions = new Productions();

ListView = Backbone.View.extend({

    initialize: function () {
        _.bindAll(this, 'render');
        this.template = $("#listTemplate");
        //this.model.bind("change", this.render);
        this.collection.bind("reset", this.render);
        //window.ViewModel = this.model;
    },
//    events: {
//        "click a:": "clickityClick"
//    },
//    clickityClick: function () {
//        alert("I been clicked");
//    },

    render: function () {
        //var html = this.template.tmpl(this.model.toJSON());
        var html = this.template.tmpl(this.collection.toJSON());
        $(this.el).html(html);
        return this;
    }
});

jQuery(function () {
    //var production = new Production({ name: "MVC3!" });
    //listView = new ListView({model: production});
    listView = new ListView({ collection: productions, el: "#production-list" });
    //$("#production-list").html(listView.render().el);
    productions.fetch();
});

Backbone router

24mins in.

using callback function success

Production = Backbone.Model.extend({});
Productions = Backbone.Collection.extend({
    model : Production,
    url: "/api/productions"
});
productions = new Productions();

ListView = Backbone.View.extend({

    initialize: function () {
        _.bindAll(this, 'render');
        this.template = $("#listTemplate");
        this.collection.bind("reset", this.render);
    },

    render: function () {
        var html = this.template.tmpl(this.collection.toJSON());
        $(this.el).html(html);
        return this;
    }
});

var ProductionAdmin = Backbone.Router.extend({
    initialize: function () {
        listView = new ListView({ collection: productions, el: "#production-list" });
    },
    routes: {
        "": "index",
        "edit/:id": "edit"
    },
    index: function () {
        listView.render();
    },
    edit: function (id) {
        alert("hello");
        //grab the model from the production
        $("#production-list").html("This id is " + id);
        //pass it to an editor
        //listView.render();
    }
});

jQuery(function () {
    productions.fetch({
        success: function () {
            alert("hello2");
            //create the router
            window.app = new ProductionAdmin();
            Backbone.history.start();
        },
        error: function () {
            //display a nice error page
            alert("error");
        }
    });
});

editing: app.navigate("edit/1",true)

image

#allows back button to work, and hyperlinking back to a certain page to work..all with ajax.  Power of backbone!

Working with Data

<a href="#" id="${ID}">${Title}</a>

not using this.. instead HTML5 data storage:

image

Production = Backbone.Model.extend({});

Production = Backbone.Model.extend({
    idAttribute : "ID"
});

Productions = Backbone.Collection.extend({
    model : Production,
    url: "/api/productions"
});
productions = new Productions();

ListView = Backbone.View.extend({

    initialize: function () {
        _.bindAll(this, 'render');
        this.template = $("#listTemplate");
        this.collection.bind("reset", this.render);
    },
    events: {
        "click a": "showForm"
    },
    showForm: function (evt) {
        //get tje ID that was clicked
        var id = $(evt.currentTarget).data('production-id');
        //navigate
        app.navigate("edit/" + id, true);
        return false;
    },

    render: function () {
        var html = this.template.tmpl(this.collection.toJSON());
        $(this.el).html(html);
        return this;
    }
});


FormView = Backbone.View.extend({
    initialize: function () {
        _.bindAll(this, 'render');
        this.template = $("#productionFormTemplate");
    },

    render: function () {
        var html = this.template.tmpl(this.model.toJSON());
        $(this.el).html(html);
        return this;
    }
});


var ProductionAdmin = Backbone.Router.extend({
    initialize: function () {
        listView = new ListView({ collection: productions, el: "#production-list" });
        formView = new FormView({ el: "#production-form" });
    },
    routes: {
        "": "index",
        "edit/:id": "edit"
    },
    index: function () {
        listView.render();
    },
    edit: function (id) {
        listView.render();
        $(formView.el).empty();
        //grab the model from the production
        var model = productions.get(id);
        formView.model = model;
        formView.render();
        //$("#production-list").html("This id is " + id);
        //pass it to an editor
        //listView.render();
    }
});

jQuery(function () {
    productions.fetch({
        success: function () {
            //create the router
            window.app = new ProductionAdmin();
            Backbone.history.start();
        },
        error: function () {
            //display a nice error page
            alert("error");
        }
    });
});

@{
    ViewBag.Title = "Productions";
    Layout = "~/Views/Shared/_AdminLayout.cshtml";
}

<script type = "text/javascript" src="http://ajax.aspnetcdn.com/ajax/jquery.templates/beta1/jquery.tmpl.min.js"></script>
@Assets.Script("underscore-min.js")
@Assets.Script("backbone-min.js")
@Assets.Script("production-admin.js")

<script id="listTemplate" type="text/html">
<li>
    <a href="#" data-production-id="${ID}">${Title}</a>
</li>
</script>

<script id="productionFormTemplate" type="text/html">
    <form id="productionForm">
        <input type="text" id="Title" name="Title" value="${Title}" />
    </form>
</script>

<div class="column span-5 colborder">
    <div id="production-list"></div>
</div>

<div class="column span-14">
    <div id="production-form"></div>
</div>

 

Form Binding

we want to bind the form to the model.. we can .. using events.  will wire ourselves.. people working on modelbinders.

image

showing the model being changed by the view.

formView.model.get("Title")

this should be automatic – use Derik Baily’s modelbinding.

Helpers:  copy existing Form.cshtml and rename to Template.cshtml

Nice button CSS, with jQuery template helper html.

image

nice form using form.css wired through the layout page.

image

date picker

image

just had to wire up the datepicker in formview, which wires us the jQueryUI datepicker.  jqueryUI is wired up through _adminlayout.

FormView = Backbone.View.extend({
    initialize: function () {
        _.bindAll(this, 'render');
        this.template = $("#productionFormTemplate");
    },

    events: {
        "change input": "updateModel",
        "submit #productionForm": "save"
    },
    save: function () {

        return false;
    },
    updateModel: function (evt) {
        var field = $(evt.currentTarget);
        var data = {};
        data[field.attr('ID')] = field.val();
        this.model.set(data);
    },
    render: function () {
        var html = this.template.tmpl(this.model.toJSON());
        $(this.el).html(html);
        $(".datepicker").datepicker();
        return this;
    }
});

saving back to server:

image

reason we’re using IIS Express… cassini doesn’t always work with put.

rails like conventions

image

 

image

so data has been passed back to server via the formView.model.save() call or button press.

Production = Backbone.Model.extend({});

Production = Backbone.Model.extend({
    idAttribute: "ID",
     url: function () {
        return this.isNew() ? "/api/productions/create" : "/api/productions/edit/" + this.get("ID");
    },
});

Productions = Backbone.Collection.extend({
    model : Production,
    url: "/api/productions"
});
productions = new Productions();

ListView = Backbone.View.extend({

    initialize: function () {
        _.bindAll(this, 'render');
        this.template = $("#listTemplate");
        this.collection.bind("reset", this.render);
    },
    events: {
        "click a": "showForm"
    },
    showForm: function (evt) {
     
        //get the ID that was clicked
        var id = $(evt.currentTarget).data('production-id');
        //navigate
        app.navigate("edit/" + id, true);
        return false;
    },

    render: function () {
        var html = this.template.tmpl(this.collection.toJSON());
        $(this.el).html(html);
        return this;
    }
});


FormView = Backbone.View.extend({
    initialize: function () {
        _.bindAll(this, 'render');
        this.template = $("#productionFormTemplate");
    },

    events: {
        "change input": "updateModel",
        "submit #productionForm": "save"
    },
    save: function () {
        this.model.save(
            this.model.attributes,
            {
                success: function (model, response) {
                    model.collection.trigger("itemSaved", model);
                },
                error: function (model, response) {
                    model.trigger("itemError", "There was a problem saving " + model.get("Title"));
                }
            }
        );
        return false;
    },
    updateModel: function (evt) {
        var field = $(evt.currentTarget);
        var data = {};
        data[field.attr('ID')] = field.val();
        this.model.set(data);
    },
    render: function () {
        var html = this.template.tmpl(this.model.toJSON());
        $(this.el).html(html);
        $(".datepicker").datepicker();
        return this;
    }
});


var ProductionAdmin = Backbone.Router.extend({
    initialize: function () {
        listView = new ListView({ collection: productions, el: "#production-list" });
        formView = new FormView({ el: "#production-form" });
    },
    routes: {
        "": "index",
        "edit/:id": "edit"
    },
    index: function () {
        listView.render();
    },
    edit: function (id) {
        listView.render();
        $(formView.el).empty();
        //grab the model from the production
        var model = productions.get(id);
        formView.model = model;
        formView.render();
        //$("#production-list").html("This id is " + id);
        //pass it to an editor
        //listView.render();
    }
});

jQuery(function () {
    productions.fetch({
        success: function () {
            //create the router
            window.app = new ProductionAdmin();
            Backbone.history.start();
        },
        error: function () {
            //display a nice error page
            alert("error");
        }
    });
});

MVC3 modelbinders did their job and title in the incoming json is title in our p object.

but we’re not using statically typed objects.. can’t access via FormCollection (as it isn’t a post).. so have coded around:

Multiple types were found that match the controller named

http://ediblecode.com/blog/dot-net/asp-net-mvc/fixing-multiple-types-were-found-that-match-the-controller-named

image

ControllerBuilder.Current.DefaultNamespaces.Add("VidPub.Web.Controllers");

Date Problem

Wrote and app to test:

namespace MassiveDateTest {
    class Program {
        static void Main(string[] args) {
            // test dates in massive for NZ locale
           

            //do an insert
            var table2 = new Productions();
            //do it up - the new ID will be returned from the query
            // fails with
            // exec sp_executesql N'INSERT INTO Productions (Title,CreatedOn)
            //VALUES (@0,@1)',N'@0 nvarchar(4000),@1 nvarchar(4000)',@0=N'Buck Fify Stuff',@1=N'30/08/2011'
   
            var newID = table2.Insert(new { Title = "Buck Fify Stuff", CreatedOn = "21/08/2011" });
            Console.WriteLine(newID);

            var table = new Productions();
            //grab all the productions
            var productions = table.All();
            foreach (var item in productions) {
                // coming through as 29/08/2011 12:00:00
                Console.WriteLine(item.Title + " " + item.CreatedOn);
            }

            // this works
            //var newID = table2.Insert(new { Title = "Buck Fify Stuff", CreatedOn = "08/30/2011" });
            
        }
    }

    public class Productions : DynamicModel {
         public Productions() : base("VidPub", "Productions", "ID") {}
    }
}

and massive:

public virtual DbCommand CreateInsertCommand(object o) {
            DbCommand result = null;
            var expando = o.ToExpando();
            var settings = (IDictionary<string, object>)expando;
            var sbKeys = new StringBuilder();
            var sbVals = new StringBuilder();
            var stub = "INSERT INTO {0} ({1}) \r\n VALUES ({2})";
            result = CreateCommand(stub, null);
            int counter = 0;
            foreach (var item in settings) {
                sbKeys.AppendFormat("{0},", item.Key);
                sbVals.AppendFormat("@{0},", counter.ToString());

                // DM hack dates
                if (item.Key == "CreatedOn" || item.Key == "UpdatedOn" || item.Key == "ReleasedOn") {
                    string fulldate = item.Value.ToString();
                    string[] dateArray = fulldate.Split('/');
                    string day = dateArray[0];
                    string month = dateArray[1];
                    string year = dateArray[2];

                    string dateInUSFormat = month + "/" + day + "/" + year;
                    result.AddParam(dateInUSFormat);
                } else
                    result.AddParam(item.Value);
                counter++;
            }

so got it writing a British date from C# and transforming into US in the insert code.

put into main project and got it working for

image

but time comes in on save:

image

create not working for some reason. **datepicker is in US format already.. need to set to UK

| | #