brainbaking/content/wiki/code/javascript/frameworks.md

6.9 KiB

+++ title = "frameworks" draft = false tags = [ "code", "javascript", "frameworks" ] date = "2013-03-12" +++

Hoe objecten extenden

Zie [Uitleg over Javascript werking met inheritance]({{< relref "wiki/code/javascript/inheritance.md" >}}) en [code/javascript/inleiding]({{< relref "wiki/code/javascript/inleiding.md" >}}).

Prototype's Extend

Op de vorige wijze werkt bijvoorbeeld Prototype's Element.Extend of $ (naast de CSS Selector natuurlijk).

// ignoring Prototype's initialize() constructor method
var Poes = Class.create({
  miauw: function() { return "prr"; }
});

var Hond = Class.create({
  blaf: function() { return "WOOF"; }
});

var kat = new Poes();
Element.extend(kat, Hond);

assertEquals(kat.blaf(), new Hond().blaf()); // hoo lee sjit

De bovenstaande code wordt intern gebruikt om inheritance te faken.

Dat wil zeggen, een Hond die van een Poes afleidt, kan zo:

var Hond = Class.create(Poes, {...});

Van de eerste parameter worden gewoon alle functie pointers gekopiëerd. Boem.

Constructor overloading - bestaat natuurlijk niet, dus een manier om dat te omzeilen is de pointer $super gebruiken die de superklasse zijn constructor aanroept. Dit gebeurt ook niet automatisch. Initialize wordt dus nooit overschreven.

Hoe werkt dit?

Simpel: gebruik for(key in map.prototype) om te loopen over alle functie pointers. Copypasta. Done.

Dit kan ook gebruikt worden om bestaande functies uit te breiden! (zoals String, Array, ...)

Prototype's Pointer binding

Probleem: stel dat een klasse wordt opgemaakt met de bovenstaande methode. De this pointer verwijst standaard naar de array die binnen de scope valt van uw code, dus vanuit een functie kan men een andere in dezelfde map oproepen. MAAR this wordt overschreven wanneer de functie als closure wordt meegegeven. Common examples zijn bvb. bij onload, bij each, bij ajax calls als onsuccess/failure/... events, ...

Event.observe(window, "load", function() {
  klasse.doeIets(); // this binnenin doeIets() is nu window! BOOM
});

Om dit te omzeilen kan men eender welke functie pointer binnen eender welke method vervangen met bind. Vorige voorbeeld aangepast:

Event.observe(window, "load", function() {
  klasse.doeIets(); // this binnenin doeIets() is nu zoals verwacht klasse zelf.
}.bind(klasse));

Hoe werkt dit?

bind is een extentie van Object (zie extend boven), dus oproepbaar op eender welke extended method.

De rest van de magie is eigenlijk helemaal geen Prototype maar JS 1.3 methods apply() of call().

Bezie ze als reflection methods die als eerste argument de this pointer binnen pakken, en voor de rest bij call een lijst van argumenten (alles), en bij apply een array als 2de argument met alle argumenten (bvb arguments zelf). Dus:

function wow(one, two) {
  console.log(this); // default output: window
  return one + two; 
}

var arr = {
  c = "cc"
};

wow.apply(arr.c, [10, 2]); // prints "cc", output = 12
wow.call(arr.c, 10, 2); // same

apply(**this pointer**, **array argument**).

Echt "reflectie" is het niet, de functiepointer is nodig om apply op te roepen. Het echte werk laten we aan eval() over.

Interessant om te weten: curry werkt ook met apply om automatisch parameters in te vullen.

Binding en jQuery

jQuery's context verandert automatisch. Dit wil zeggen dat "this" binnen een bepaalde closure automatisch het object wordt waar bijvoorbeeld over geloopt wordt.

  1. Binnen de meeste callback methods ('success' van $.ajax bvb) is this het callback object
  2. Binnen each is this het ide element in de array waar over geloopt wordt

jQuery biedt niet by default ondersteuning om dit te veranderen met binding, behalve in sommige uitzonderlijke gevallen zoals $.ajax. Hier kan je een context aan meegeven, waardoor this in de context verandert. Bijvoobeeld:

function SomeClass() {
  this.doeIets = function() {
    $.ajax({
      url: "bla.json",
      dataType: "json",
      context: this,
      callback: function(data) {
        // this is nu de instantie van SomeClass dankzij context
        // anders zou this data zijn.
      }
  }
}

var someInstance = new SomeClass();
someInstance.doeIets();

JS Error Handling

Javascript heeft een globale event error handler, window.onerror. Die manueel overschrijven is natuurlijk niet de bedoeling.

in jQuery

$(window).error(function(){
  // do stuff!
});

Om broken images te hiden, bijvoorbeeld:

$("img").error(function(){
  $(this).hide();
});

JS Framework Conflicten

jQuery vs Prototype

each looping

De this pointer binnen de each loop van jQuery wordt gebruikt om naar het huidig element te refereren.

Prototype

["a", "b", "c"].each(function(item, index) {
  console.log("looping at #" + index + " with item " + item);
});

this is hier de window context. zie http://www.prototypejs.org/api/enumerable/each

jQuery

$(["a", "b", "c"]).each(function() {
  console.log("looping with item " + this);
});

Exact dezelfde each kan ook gebruikt worden zoals bij Prototype met extra argumenten.

Let wel op dat jQuery niet zelf aangemaakte Arrays wrapt/Extent, dat ge zelf doen met $(j)

Ook this binnen een jQuery each loop wordt niet extended maar is het DOM Element zelf.

Zie http://api.jquery.com/jQuery.each/

Mixed use - let op bij loops waarbij this overschreven wordt met Prototype's bind() method! Zie boven.

Data binding

Interessant artikel over Knockout JS vs Backbone:

JavascriptMVC

Zie

  1. http://www.ajaxvoices.com/aggregator/sources/39
  2. http://javascriptmvc.com/

Knockout JS

Zie http://knockoutjs.com/

Simplify dynamic javascript UI by applying the Model-View-viewModel pattern.

Nadeel: templates moeten geïnclude worden binnen dezelfde HTML pagina. Gelukkig genoeg zijn er andere template engines die verder bouwen op jQuery.tmpl:

https://github.com/ashbylane/Knockout.js-External-Template-Engine auto-include als de template naam naam.html is en in dezelfde dir leeft als uw main page.

Backbone JS

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

Stricter MVC, zien dat uw view opnieuw gerenderd wordt bij een update van het model:

model.bind("change", this.render);

Dit is meestal genoeg. Het grootste verschil tussen backbone en knockout is dat knockout data- attributen (HTML5) gebruikt, en alles in de view steekt, terwijl dit bij backbone meer apart steekt. (Backbone is explicieter en vereist ook wat meer boilerplating maar is meer gescheiden)

Andere handige JS frameworks

  • http:*documentcloud.github.com/underscore/ - *underscore JS// utility functies zoals Array.flatten ea.
  • http://www.sproutcore.com/