From 782e8e7dcf2228330014dad73f3b292695b673af Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Wed, 12 Dec 2012 12:26:33 +0200 Subject: [PATCH] implement hooks for Class constructors (AOP-style), close #1123 --- spec/suites/core/ClassSpec.js | 86 ++++++++++++++++++++++++----------- src/core/Class.js | 22 +++++++++ src/map/Map.js | 11 ----- 3 files changed, 81 insertions(+), 38 deletions(-) diff --git a/spec/suites/core/ClassSpec.js b/spec/suites/core/ClassSpec.js index 414cd0dc..59bfb186 100644 --- a/spec/suites/core/ClassSpec.js +++ b/spec/suites/core/ClassSpec.js @@ -1,10 +1,10 @@ describe("Class", function() { - + describe("#extend", function() { var Klass, constructor, method; - + beforeEach(function() { constructor = jasmine.createSpy("Klass constructor"); method = jasmine.createSpy("Klass#bar method"); @@ -12,78 +12,78 @@ describe("Class", function() { Klass = L.Class.extend({ statics: {bla: 1}, includes: {mixin: true}, - + initialize: constructor, foo: 5, bar: method }); }); - + it("should create a class with the given constructor & properties", function() { var a = new Klass(); - + expect(constructor).toHaveBeenCalled(); expect(a.foo).toEqual(5); - + a.bar(); - + expect(method).toHaveBeenCalled(); }); - + it("should inherit parent classes' constructor & properties", function() { var Klass2 = Klass.extend({baz: 2}); - + var b = new Klass2(); - + expect(b instanceof Klass).toBeTruthy(); expect(b instanceof Klass2).toBeTruthy(); - + expect(constructor).toHaveBeenCalled(); expect(b.baz).toEqual(2); - + b.bar(); - + expect(method).toHaveBeenCalled(); }); - + it("should support static properties", function() { expect(Klass.bla).toEqual(1); }); - + it("should inherit parent static properties", function() { var Klass2 = Klass.extend({}); - + expect(Klass2.bla).toEqual(1); }); - + it("should override parent static properties", function() { var Klass2 = Klass.extend({statics: {bla: 2}}); - + expect(Klass2.bla).toEqual(2); }); - + it("should include the given mixin", function() { var a = new Klass(); expect(a.mixin).toBeTruthy(); }); - + it("should be able to include multiple mixins", function() { var Klass2 = L.Class.extend({ includes: [{mixin: true}, {mixin2: true}] }); var a = new Klass2(); - + expect(a.mixin).toBeTruthy(); expect(a.mixin2).toBeTruthy(); }); - + it("should grant the ability to include the given mixin", function() { Klass.include({mixin2: true}); - + var a = new Klass(); expect(a.mixin2).toBeTruthy(); }); - + it("should merge options instead of replacing them", function() { var KlassWithOptions1 = L.Class.extend({ options: { @@ -97,18 +97,50 @@ describe("Class", function() { foo3: 4 } }); - + var a = new KlassWithOptions2(); - + expect(a.options).toEqual({ foo1: 1, foo2: 3, foo3: 4 }); }); + + it("should add constructor hooks correctly", function () { + var spy1 = jasmine.createSpy("init hook 1"); + + Klass.addInitHook(spy1); + Klass.addInitHook('bar', 1, 2, 3); + + var a = new Klass(); + + expect(spy1).toHaveBeenCalled(); + expect(method).toHaveBeenCalledWith(1, 2, 3); + }); + + it("should inherit constructor hooks", function () { + var spy1 = jasmine.createSpy("init hook 1"), + spy2 = jasmine.createSpy("init hook 2"); + + Klass.addInitHook(spy1); + + var Klass2 = Klass.extend({}); + Klass2.addInitHook(spy2); + + var a = new Klass(); + + expect(spy1).toHaveBeenCalled(); + expect(spy2).not.toHaveBeenCalled(); + + var b = new Klass2(); + + expect(spy2).toHaveBeenCalled(); + expect(spy1.argsForCall.length).toBe(2); + }); }); // TODO Class.include // TODO Class.mergeOptions -}); \ No newline at end of file +}); diff --git a/src/core/Class.js b/src/core/Class.js index 94c4eb8a..e1c1b317 100644 --- a/src/core/Class.js +++ b/src/core/Class.js @@ -8,9 +8,16 @@ L.Class.extend = function (/*Object*/ props) /*-> Class*/ { // extended class with the new prototype var NewClass = function () { + + // call the constructor if (this.initialize) { this.initialize.apply(this, arguments); } + + // call all constructor hooks + for (var i = 0, len = this._initHooks.length; i < len; i++) { + this._initHooks[i].call(this); + } }; // instantiate class without calling constructor @@ -49,6 +56,9 @@ L.Class.extend = function (/*Object*/ props) /*-> Class*/ { // mix given properties into the prototype L.extend(proto, props); + // inherit constructor hooks + proto._initHooks = this.prototype._initHooks ? this.prototype._initHooks.slice() : []; + return NewClass; }; @@ -58,6 +68,18 @@ L.Class.include = function (props) { L.extend(this.prototype, props); }; +// merge new default options to the Class L.Class.mergeOptions = function (options) { L.extend(this.prototype.options, options); }; + +// add a constructor hook +L.Class.addInitHook = function (fn) { // (Function) || (String, args...) + var args = Array.prototype.slice.call(arguments, 1); + + var init = typeof fn === 'function' ? fn : function () { + this[fn].apply(this, args); + }; + + this.prototype._initHooks.push(init); +}; diff --git a/src/map/Map.js b/src/map/Map.js index 6c7e14da..458ca657 100644 --- a/src/map/Map.js +++ b/src/map/Map.js @@ -25,7 +25,6 @@ L.Map = L.Class.extend({ this._initContainer(id); this._initLayout(); - this._initHooks(); this._initEvents(); if (options.maxBounds) { @@ -681,16 +680,6 @@ L.Map = L.Class.extend({ } }); -L.Map.addInitHook = function (fn) { - var args = Array.prototype.slice.call(arguments, 1); - - var init = typeof fn === 'function' ? fn : function () { - this[fn].apply(this, args); - }; - - this.prototype._initializers.push(init); -}; - L.map = function (id, options) { return new L.Map(id, options); };