From bfad7277ea37c2b198f0e6c3190f1175a77b95b2 Mon Sep 17 00:00:00 2001 From: Nicklas Gummesson Date: Tue, 7 Jun 2016 11:10:22 +0200 Subject: [PATCH] Migrate cdb.core.View form cartodb.js --- .gitignore | 2 +- lib/assets/node_modules/README.md | 4 + lib/assets/node_modules/backbone/core-view.js | 160 +++++++++++++++++ .../node_modules/backbone/core-view.spec.js | 163 ++++++++++++++++++ lib/build/files/browserify_files.js | 3 +- 5 files changed, 330 insertions(+), 2 deletions(-) create mode 100644 lib/assets/node_modules/README.md create mode 100644 lib/assets/node_modules/backbone/core-view.js create mode 100644 lib/assets/test/spec/node_modules/backbone/core-view.spec.js diff --git a/.gitignore b/.gitignore index 89ea40139d..703bfc5345 100644 --- a/.gitignore +++ b/.gitignore @@ -53,7 +53,7 @@ vendor/bundle/* vendor/cache/* .vagrant Vagrantfile -node_modules +/node_modules .grunt/* vendor/assets/javascripts/cartodb.* vendor/assets/stylesheets/cartodb.* diff --git a/lib/assets/node_modules/README.md b/lib/assets/node_modules/README.md new file mode 100644 index 0000000000..e3b7e8d5b0 --- /dev/null +++ b/lib/assets/node_modules/README.md @@ -0,0 +1,4 @@ +utilizes https://github.com/substack/browserify-handbook#how-node_modules-works, +to avoid annoying `require('../../../../../')` for both source and test environment + +Was migrated as-is from https://github.com/CartoDB/cartodb.js/blob/470399abb12b40d5476ab6bbfd792d95aa819f50/src/core/ on May 3rd 2016 diff --git a/lib/assets/node_modules/backbone/core-view.js b/lib/assets/node_modules/backbone/core-view.js new file mode 100644 index 0000000000..2df155acd3 --- /dev/null +++ b/lib/assets/node_modules/backbone/core-view.js @@ -0,0 +1,160 @@ +var _ = require('underscore'); +var Backbone = require('backbone'); + +/** + * NOTE! Migrated as-is from https://github.com/CartoDB/cartodb.js/blob/470399abb12b40d5476ab6bbfd792d95aa819f50/src/core/view.js 2016-06-03 + * Base View for all CartoDB views. + * DO NOT USE Backbone.View directly + */ +var View = Backbone.View.extend({ + classLabel: 'cdb.core.View', + + constructor: function (options) { + this.options = _.defaults(options, this.options); + this._models = []; + this._subviews = {}; + Backbone.View.call(this, options); + View.viewCount++; + View.views[this.cid] = this; + this._created_at = new Date(); + }, + + add_related_model: function (m) { + if (!m) throw new Error('added non valid model'); + this._models.push(m); + }, + + addView: function (v) { + this._subviews[v.cid] = v; + v._parent = this; + }, + + removeView: function (v) { + delete this._subviews[v.cid]; + }, + + clearSubViews: function () { + _(this._subviews).each(function (v) { + v.clean(); + }); + this._subviews = {}; + }, + + /** + * this methid clean removes the view + * and clean and events associated. call it when + * the view is not going to be used anymore + */ + clean: function () { + var self = this; + this.trigger('clean'); + this.clearSubViews(); + // remove from parent + if (this._parent) { + this._parent.removeView(this); + this._parent = null; + } + this.remove(); + this.unbind(); + // remove this model binding + if (this.model && this.model.unbind) this.model.unbind(null, null, this); + // remove model binding + _(this._models).each(function (m) { + m.unbind(null, null, self); + }); + this._models = []; + View.viewCount--; + delete View.views[this.cid]; + return this; + }, + + show: function () { + this.$el.show(); + }, + + hide: function () { + this.$el.hide(); + }, + + /** + * Listen for an event on another object and triggers on itself, with the same name or a new one + * @method retrigger + * @param ev {String} event who triggers the action + * @param obj {Object} object where the event happens + * @param obj {Object} [optional] name of the retriggered event + */ + retrigger: function (ev, obj, retrigEvent) { + if (!retrigEvent) { + retrigEvent = ev; + } + var self = this; + obj.bind && obj.bind(ev, function () { + self.trigger(retrigEvent); + }, self); + // add it as related model//object + this.add_related_model(obj); + }, + /** + * Captures an event and prevents the default behaviour and stops it from bubbling + * @method killEvent + * @param event {Event} + */ + killEvent: function (ev) { + if (ev && ev.preventDefault) { + ev.preventDefault(); + } + if (ev && ev.stopPropagation) { + ev.stopPropagation(); + } + }, + + /** + * Remove all the tipsy tooltips from the document + * @method cleanTooltips + */ + cleanTooltips: function () { + this.$('.tipsy').remove(); + } + +}, { + viewCount: 0, + views: {}, + + /** + * when a view with events is inherit and you want to add more events + * this helper can be used: + * var MyView = new core.View({ + * events: View.extendEvents({ + * 'click': 'fn' + * }) + * }) + */ + extendEvents: function (newEvents) { + return function () { + return _.extend(newEvents, this.constructor.__super__.events); + }; + }, + + /** + * search for views in a view and check if they are added as subviews + */ + runChecker: function () { + _.each(View.views, function (view) { + _.each(view, function (prop, k) { + if (k !== '_parent' && + view.hasOwnProperty(k) && + prop instanceof View && + view._subviews[prop.cid] === undefined) { + console.log('========='); + console.log('untracked view: '); + console.log(prop.el); + console.log('parent'); + console.log(view.el); + console.log(' '); + } + }); + }); + } +}); + +module.exports = View; diff --git a/lib/assets/test/spec/node_modules/backbone/core-view.spec.js b/lib/assets/test/spec/node_modules/backbone/core-view.spec.js new file mode 100644 index 0000000000..a08db0fb91 --- /dev/null +++ b/lib/assets/test/spec/node_modules/backbone/core-view.spec.js @@ -0,0 +1,163 @@ +var $ = require('jquery'); +var _ = require('underscore'); +var Backbone = require('backbone'); +var CoreView = require('backbone/core-view'); + +describe('backbone/core-view', function () { + var TestView; + var view; + + beforeEach(function () { + TestView = CoreView.extend({ + initialize: function () { + this.init_called = true; + }, + test_method: function () {} + }); + + CoreView.viewCount = 0; + view = new TestView({el: $('
')}); + }); + + it('should call initialize', function () { + expect(view.init_called).toEqual(true); + }); + + it('should increment refCount', function () { + expect(CoreView.viewCount).toEqual(1); + expect(CoreView.views[view.cid]).toBeTruthy(); + }); + + it('should decrement refCount', function () { + view.clean(); + expect(CoreView.viewCount).toEqual(0); + expect(CoreView.views[view.cid]).toBeFalsy(); + }); + + it('clean should remove view from dom', function () { + var dom = $('
'); + dom.append(view.el); + expect(dom.children().length).toEqual(1); + view.clean(); + expect(dom.children().length).toEqual(0); + }); + + it('clean should unbind all events', function () { + view.bind('meh', function () {}); + expect(_.size(view._events)).toEqual(1); + view.clean(); + expect(view._events).toEqual(undefined); + }); + + it('should unlink the view model', function () { + var called = false; + var new_view = new TestView({ el: $('
'), model: new Backbone.Model() }); + + spyOn(new_view, 'test_method'); + new_view.model.bind('change', new_view.test_method, new_view); + new_view.model.bind('change', function () { called = true; }); + + new_view.model.trigger('change'); + expect(called).toEqual(true); + expect(new_view.test_method).toHaveBeenCalled(); + expect(new_view.test_method.calls.count()).toEqual(1); + called = false; + new_view.clean(); + // trigger again + new_view.model.trigger('change'); + expect(called).toEqual(true); + expect(new_view.test_method.calls.count()).toEqual(1); + }); + + it('should unlink linked models', function () { + var called = false; + var model = new Backbone.Model(); + spyOn(view, 'test_method'); + model.bind('change', view.test_method, view); + model.bind('change', function () { called = true; }); + view.add_related_model(model); + + model.trigger('change'); + expect(called).toEqual(true); + expect(view.test_method).toHaveBeenCalled(); + expect(view.test_method.calls.count()).toEqual(1); + called = false; + view.clean(); + expect(_.size(view._models)).toEqual(0); + // trigger again + model.trigger('change'); + expect(called).toEqual(true); + expect(view.test_method.calls.count()).toEqual(1); + }); + + it('should add and remove subview', function () { + var v1 = new CoreView(); + view.addView(v1); + expect(view._subviews[v1.cid]).toEqual(v1); + expect(v1._parent).toEqual(view); + view.removeView(v1); + expect(view._subviews[v1.cid]).toEqual(undefined); + }); + + it('should remove and clean subviews', function () { + var v1 = new CoreView(); + spyOn(v1, 'clean'); + view.addView(v1); + expect(view._subviews[v1.cid]).toEqual(v1); + view.clean(); + expect(view._subviews[v1.cid]).toEqual(undefined); + expect(v1.clean).toHaveBeenCalled(); + }); + + it('subview shuould be removed from its parent', function () { + var v1 = new CoreView(); + view.addView(v1); + expect(view._subviews[v1.cid]).toEqual(v1); + v1.clean(); + expect(view._subviews[v1.cid]).toEqual(undefined); + }); + + it('extendEvents should extend events', function () { + var V1 = CoreView.extend({ + events: CoreView.extendEvents({ + 'click': 'hide' + }) + }); + var v1 = new V1(); + expect(v1.el.style.display).not.toEqual('none'); + v1.$el.trigger('click'); + expect(v1.el.style.display).toEqual('none'); + }); + + it('should retrigger an event when launched on a descendant object', function (done) { + var launched = false; + view.child = new TestView({}); + view.retrigger('cachopo', view.child); + view.bind('cachopo', function () { + launched = true; + }); + view.child.trigger('cachopo'); + setTimeout(function () { + expect(launched).toBeTruthy(); + done(); + }, 25); + }); + + it('should kill an event', function () { + var ev = { + stopPropagation: function () {}, + preventDefault: function () {} + }; + var ev2 = 'thisisnotanevent'; + + spyOn(ev, 'stopPropagation'); + spyOn(ev, 'preventDefault'); + + view.killEvent(ev); + view.killEvent(ev2); + view.killEvent(); + + expect(ev.stopPropagation).toHaveBeenCalled(); + expect(ev.preventDefault).toHaveBeenCalled(); + }); +}); diff --git a/lib/build/files/browserify_files.js b/lib/build/files/browserify_files.js index a733e864cf..b8a1aab77a 100644 --- a/lib/build/files/browserify_files.js +++ b/lib/build/files/browserify_files.js @@ -167,7 +167,8 @@ module.exports = { 'lib/build/source-map-support.js', // Add specs for browserify module code here: - 'lib/assets/test/spec/cartodb3/**/*.spec.js' + 'lib/assets/test/spec/cartodb3/**/*.spec.js', + 'lib/assets/test/spec/node_modules/**/*.spec.js' ], dest: '.grunt/cartodb3-specs.js' }