JavaScript Practices & Design Patterns

Post on 28-Jul-2015

49 views 1 download

transcript

JavaScript Practices & Design Patterns

Salcescu CristianV0.5.2

Function Overloadingfunction log(a,b){ if(arguments.length === 1){ if(typeof(a) === "number") $("#log").append("<br />number:").append(a); else $("#log").append("<br />string:").append(a); } else $("#log").append("<br />2 arguments:").append(a + " " + b); }

log(1);log("Hello!");log(1,2);

Method chaining

• a common syntax for calling multiple functions on the same object

$('#my-div').css('background', 'blue').height(100).slideUp(2000).slideDown(2000);

Timer Patterns

• setInterval/clearInterval• setTimeout/clearTimeout

- all JavaScript in a browser executes on a single thread. JavaScript is single threaded.

- Exception : Web Workers, but they cannot access the DOM.

- Two functions can't run at the same time.

Issues with setInterval

Recursive setTimeout Patternvar i = 1;function func() {

alert(i++);timer = setTimeout(func, 2000);

} var timer = setTimeout(func, 2000);

Throttle

- new function that will execute no more than once every delay milliseconds

_.throttle(fn, delay);

Debounce

- a function that will execute only once, for a group of sequential calls

_.debounce(fn, delay);

Template engine• Separates JS business logic from the html views• Libraries : Handlebars, Hogan

<div id="editContainer">Edit Loading...</div><script type="text/template" id="editTemplate"> <form> FName : <input type="text" value={{fname}} name="fname" /><br/> LName : <input type="text" value={{lname}} name="lname" /><br/> <button type="button" class="save">Save</button> </form></script>

function render(){ var person = {fname: "Mihai", lname: "Ionescu"}; var template = Handlebars.compile($('#editTemplate').html()); var html = template(person); $el.html(html); }

Publish–Subscribe Pattern

• publish–subscribe is a messaging pattern • Senders of messages are called publishers• Receivers of the messages are called subscribers• publishers do not program the messages to be sent

directly to specific receivers, they instead, publish messages without knowledge of what, if any, subscribers there may be.

• subscribers express interest in one or more message types, and only receive messages that are of interest, without knowledge of what, if any, publishers there are.

Publish–Subscribe Pattern

• jQuery$.event.trigger("itemSaved", data);$(document).on("itemSaved", onItemSaved);

- Amplifyamplify.publish( string topic, ... )amplify.subscribe( string topic, function callback )

- Angular$rootScope.$broadcast("itemSaved", data);$scope.$on("itemSaved", function(event, data) {});

Publish–Subscribe Patternvar EVENTS = { buttonClicked : "buttonClicked" };

(function(){ $(function(){ $("#btn").click(function() { amplify.publish( EVENTS.buttonClicked, { message: "Hi!" } ); }); }); }());

(function(){ amplify.subscribe( EVENTS.buttonClicked, function( data ) { alert( data.message ); });}());

Promises- A reference to an async call

function log(text){ $("#container").append(text).append("<br />");}function asynCall(time, text){ var deferred = $.Deferred(); setTimeout(function(){ log(text); deferred.resolve(); }, time); return deferred.promise();}

function asynCall1(){ return asynCall(1000, "1"); };function asynCall2(){ return asynCall(2000, "2"); };function asynCall3(){ return asynCall(3000, "3"); };

var promise1 = asynCall1();var promise2 = asynCall2();var promise3 = asynCall3();

promise1.done(function() { log("1 is done");});$.when(promise1, promise2, promise3).done(function() { log("all done");});

async Libraryasync.parallel([ function(callback){ setTimeout(function(){ callback(null, 'one'); }, 3000); }, function(callback){ setTimeout(function(){ callback(null, 'two'); }, 1000); }], function(err, results){ log(results[0]); log(results[1]); });

function log(text) { $("#log").append(text).append("<br />"); }

MV* Patterns

• MVC• MVVM

MVC

• Model – data, information to be displayed• View – the presentation, HTML template• Controller – manages the communication

between View and Model, encapsulates the business logic

MVVM

• Model – data, information to be displayed• View – the presentation, HTML template• The ViewModel can be considered a

specialized Controller that acts as a data converter. It changes Model information into View information, passing commands from the View to the Model. Behaviour/Business Logic is encapsulated here.

Frameworks

- Backbone- Knockout- Angular

- MVVM separation- Two-way Data Binding- Dependency Injection- Pub/Sub utility

Backbone - View<div id="listContainer">List Loading...</div><div id="editContainer">Edit Loading...</div><script type="text/template" id="editTemplate"> <form> FName : <input type="text" value={{fname}} name="fname" /><br/> LName : <input type="text" value={{lname}} name="lname" /><br/> <button type="button" class="save">Save</button> </form></script>

Backbone - ViewModelvar app = new Backbone.Marionette.Application();

var itemService = { save : function(data) { alert("APIService save : " + data); }}

app.module("editModule", function(editModule, app, Backbone, Marionette, $, _, itemService){ var person = {fname: "Mihai", lname: "Ionescu"}; var EditView = Backbone.View.extend({ el: '#editContainer', initialize: function(){ this.render(); }, render: function(){ var template = Handlebars.compile($('#editTemplate').html()); var html = template(person); this.$el.html(html); }, events: { "click .save" : "save" }, save: function() { var data = $("form", this.$el).serialize(); itemService.save(data); Backbone.trigger('itemSaved', data); } });

var editView = new EditView();}, itemService);

Backbone – Two-way data binding

var app = new Backbone.Marionette.Application();

var itemService = { save : function(data) { alert("APIService save : " + data); }}

app.module("editModule", function(editModule, app, Backbone, Marionette, $, _, itemService){ var personModel = new Backbone.Model({ fname: "Mihai", lname: "Ionescu" }); var EditView = Backbone.View.extend({ el: '#editContainer', model: personModel, bindings: { '[name=fname]': 'fname', '[name=lname]': 'lname' }, initialize: function(){ this.render(); }, render: function(){ this.stickit(); }, events: { "click .save" : "save" }, save: function() { var data = this.model.get("fname"); itemService.save(data); Backbone.trigger('itemSaved', data); } });

var editView = new EditView();}, itemService);

Knockout - View<div id="listContainer"><span data-bind="text: message">List Loading...</span></div><div id="editContainer"> <form> FName : <input type="text" name="fname" data-bind="value: fname" /><br/> LName : <input type="text" name="lname" data-bind="value: lname" /><br/> Full Name : <span data-bind="text: fullName"></span> <br /> <button type="button" class="save" data-bind='click: save' >Save</button> </form></div>

Knockout - ViewModelvar itemService = { save : function(data) { alert("APIService save : " + data); }}

var EditViewModel = function(person) { this.fname = ko.observable(person.fname); this.lname = ko.observable(person.lname); this.fullName = ko.computed(function() { return this.fname() + " " + this.lname(); }, this); this.save = function() { var data = this.fname(); itemService.save(data); ko.postbox.publish("itemSaved", data); }; };

Angular - View<div ng-app="app"><div id="listContainer" ng-controller="listCtrl" > <span ng-bind="message" >List Loading...</span></div><div id="editContainer" ng-controller="editCtrl" > <form> FName : <input type="text" name="fname" ng-model="person.fname" /><br/> LName : <input type="text" name="lname" ng-model="person.lname" /><br/> Full Name : <span ng-bind="fullName()" ></span> <br /> <button type="button" class="save" ng-click="save()" >Save</button> </form></div></div>

Angular - Controllervar app = angular.module("app", []);app.controller("editCtrl", ["$scope", "$rootScope", "itemService", function($scope,$rootScope, itemService){ var person = { fname: "Mihai", lname: "Ionescu"}; $scope.person = person; $scope.fullName = function() { return $scope.person.fname + " " + $scope.person.lname; }; $scope.save = function(){ var data = $scope.person.fname; itemService.save(data); $rootScope.$broadcast("itemSaved", data); }}]);

app.factory( 'itemService', function(){ return { save : function(data) { alert("APIService save : " + data) } }});

Modules

- Split the HTML page in UI components (Views)- Create JS Controllers for every View. Follow the

Module Pattern when defining the JS Controller- All DOM- centric code related to a View should

stay in its Controller. – Comunicate between Controllers using the

Publish-Subscribe Pattern– Don’t create or update global objects. All required

service libraries should be passed in

View<div id="listContainer">List Loading...</div><div id="editContainer">Edit Loading...</div><script type="text/template" id="editTemplate"> <form> FName : <input type="text" value={{fname}} name="fname" /><br/> LName : <input type="text" value={{lname}} name="lname" /><br/> <button type="button" class="save">Save</button> </form></script>

Logicvar services = {};services.itemService = (function(){ function save(data) { alert("APIService save : " + data); }; return { save : save }}());

(function($, services){ "use strict"; var $el, el = "#editContainer"; function initElements(){ $el = $(el); } function render(){ var person = {fname: "Mihai", lname: "Ionescu"}; var template = Handlebars.compile($('#editTemplate').html()); var html = template(person); $el.html(html); } function initEvents(){ $(".save", $el).on("click", save); } function save() { var data = $("form", $el).serialize(); services.itemService.save(data); $.event.trigger("itemSaved", data); }

$(function(){ initElements(); render(); initEvents(); }); }(jQuery, services));

Dependency Management• angular• ng-di library

app.controller("editCtrl", ["$scope", "$rootScope", "itemService", function($scope,$rootScope, itemService){

…}]);

app.factory( 'itemService', function(){ return {

… }});

jQuery Plugin- Create reusable UI components

<div class="show-more"> <button class="btn-toggle">Toggle</button> <div class="content"> text text<br /> text text<br /> text text<br /> </div></div><div class="show-more"> <button class="btn-toggle">Toggle</button> <div class="content"> text text<br /> text text<br /> text text<br /> </div></div>

jQuery Plugin$.fn.showMore = function() { var $set = this, $el; function createToggleHendler($el){ return function(){ $(".content", $el).toggle(); } } $set.each(function() { $el = $(this); $(".btn-toggle", $el).on("click", createToggleHendler($el)); }); return $set; };

$(function() { $(".show-more").showMore();});

angular Directive- encapsulate all DOM manipulation and create reusable UI components

var app = angular.module('myapp', []);

app.directive('showMore', function() { function createToggleHendler($el){ return function(){ $(".content", $el).toggle(); } } return { restrict: 'C', link : function($scope, element, attrs) { $(".btn-toggle", element).on("click", createToggleHendler(element)); } };});

ResourcesPluralsight - Large Scale JavaScript on Client and ServerPluralsight – JavaScript Design Patterns