backbone-base-view
Advanced tools
Comparing version 2.0.0-beta.6 to 2.0.0
{ | ||
"name": "backboneBaseView", | ||
"version": "2.0.0-beta.6", | ||
"version": "2.0.0", | ||
"description": "Baseview is a extended backbone view with convenient methods for manipulating subviews and events.", | ||
@@ -5,0 +5,0 @@ "homepage": "https://github.com/dbrekalo/backbone-base-view", |
(function(root, factory) { | ||
/* istanbul ignore next */ | ||
if (typeof define === 'function' && define.amd) { | ||
@@ -13,30 +14,37 @@ define(['jquery', 'backbone', 'underscore'], factory); | ||
var root = this, | ||
viewCounter = 0, | ||
variableInEventStringRE = /{{(\S+)}}/g, | ||
parseEventString = function(eventString, context) { | ||
var variableInEventStringRE = /{{(\S+)}}/g; | ||
var specialSelectors = { | ||
'window': window, | ||
'document': window.document | ||
}; | ||
var parseEventVariables = function(eventString, context) { | ||
return eventString.replace(variableInEventStringRE, function(match, namespace) { | ||
return eventString.replace(variableInEventStringRE, function(match, namespace) { | ||
var isInCurrentContext = namespace.indexOf('this.') === 0, | ||
current = isInCurrentContext ? context : root, | ||
pieces = (isInCurrentContext ? namespace.slice(5) : namespace).split('.'); | ||
var isInCurrentContext = namespace.indexOf('this.') === 0, | ||
current = isInCurrentContext ? context : window, | ||
pieces = (isInCurrentContext ? namespace.slice(5) : namespace).split('.'); | ||
for (var i in pieces) { | ||
current = current[pieces[i]]; | ||
if (typeof current === 'undefined') { | ||
throw new Error('Undefined variable in event string'); | ||
} | ||
for (var i in pieces) { | ||
current = current[pieces[i]]; | ||
if (typeof current === 'undefined') { | ||
throw new Error('Undefined variable in event string'); | ||
} | ||
} | ||
return current; | ||
return current; | ||
}); | ||
}); | ||
}; | ||
}; | ||
var BaseView = Backbone.View.extend({ | ||
constructor: function() { | ||
constructor: function(options) { | ||
if (this.assignOptions) { | ||
var defaults = _.result(this, 'defaults'); | ||
this.options = this.assignOptions === 'deep' ? $.extend(true, {}, defaults, options) : _.extend({}, defaults, options); | ||
} | ||
Backbone.View.apply(this, arguments); | ||
@@ -48,36 +56,42 @@ this.events && this.setupEvents(); | ||
delegatedEvents: true, | ||
parseEventVariables: true, | ||
assignOptions: false, | ||
setupEvents: function(eventsMap) { | ||
var eventNamespace = this.ens = this.ens || '.' + this.cid, | ||
events = eventsMap || this.events, | ||
self = this, | ||
specialSelectors = { | ||
'window': window, | ||
'document': window.document | ||
}; | ||
var eventsProvider = eventsMap || this.events; | ||
var eventList = typeof eventsProvider === 'function' ? eventsProvider.call(this) : eventsProvider; | ||
var self = this; | ||
_.each(typeof events === 'function' ? events.call(this) : events, function(handler, eventString) { | ||
if (eventList) { | ||
eventString = parseEventString(eventString, self); | ||
var eventNamespace = this.ens = this.ens || '.' + this.cid; | ||
var isOneEvent = eventString.indexOf('one:') === 0, | ||
splitEventString = (isOneEvent ? eventString.slice(4) : eventString).split(' '), | ||
eventName = splitEventString[0] + eventNamespace, | ||
eventSelector = splitEventString.slice(1).join(' '), | ||
$el = self.$el; | ||
_.each(eventList, function(handler, eventString) { | ||
if (specialSelectors[eventSelector]) { | ||
$el = self['$' + eventSelector] = self['$' + eventSelector] || $(specialSelectors[eventSelector]); | ||
eventSelector = undefined; | ||
} else if (!self.delegatedEvents) { | ||
(self.elementsWithBoundEvents = self.elementsWithBoundEvents || []).push($el = $el.find(eventSelector)); | ||
eventSelector = undefined; | ||
} | ||
if (self.parseEventVariables) { | ||
eventString = parseEventVariables(eventString, self); | ||
} | ||
$el[isOneEvent ? 'one' : 'on'](eventName, eventSelector, function() { | ||
(typeof handler === 'function' ? handler : self[handler]).apply(self, arguments); | ||
var isOneEvent = eventString.indexOf('one:') === 0, | ||
splitEventString = (isOneEvent ? eventString.slice(4) : eventString).split(' '), | ||
eventName = splitEventString[0] + eventNamespace, | ||
eventSelector = splitEventString.slice(1).join(' '), | ||
$el = self.$el; | ||
if (specialSelectors[eventSelector]) { | ||
$el = self['$' + eventSelector] = self['$' + eventSelector] || $(specialSelectors[eventSelector]); | ||
eventSelector = undefined; | ||
} else if (!self.delegatedEvents) { | ||
(self.elementsWithBoundEvents = self.elementsWithBoundEvents || []).push($el = $el.find(eventSelector)); | ||
eventSelector = undefined; | ||
} | ||
$el[isOneEvent ? 'one' : 'on'](eventName, eventSelector, function() { | ||
(typeof handler === 'function' ? handler : self[handler]).apply(self, arguments); | ||
}); | ||
}); | ||
}); | ||
} | ||
@@ -88,2 +102,49 @@ return this; | ||
addDismissListener: function(listenerName, options) { | ||
var self = this; | ||
if (!listenerName) { | ||
throw new Error('Dismiss listener name not speficied'); | ||
} | ||
options = $.extend({$el: this.$el}, options); | ||
this.$document = this.$document || $(document); | ||
this.ens = this.ens || '.' + this.cid; | ||
this.dismissListeners = this.dismissListeners || {}; | ||
if (!this.dismissListeners[listenerName]) { | ||
this.dismissListeners[listenerName] = function(e) { | ||
if (e.keyCode === 27 || (!$(e.target).is(options.$el) && !$.contains(options.$el.get(0), e.target))) { | ||
self[listenerName].call(self); | ||
} | ||
}; | ||
this.$document.on('click' + this.ens + ' keyup' + this.ens, this.dismissListeners[listenerName]); | ||
} | ||
return this; | ||
}, | ||
removeDismissListener: function(listenerName) { | ||
if (!listenerName) { | ||
throw new Error('Name of dismiss listener to remove not specified'); | ||
} | ||
if (this.dismissListeners && this.dismissListeners[listenerName]) { | ||
this.$document.off('click keyup', this.dismissListeners[listenerName]); | ||
delete this.dismissListeners[listenerName]; | ||
} | ||
return this; | ||
}, | ||
removeEvents: function() { | ||
@@ -103,5 +164,7 @@ | ||
}); | ||
this.elementsWithBoundEvents = null; | ||
delete this.elementsWithBoundEvents; | ||
} | ||
delete this.dismissListeners; | ||
} | ||
@@ -155,3 +218,3 @@ | ||
return this.views && !!this.views[view.cid]; | ||
return this.views && Boolean(this.views[view.cid]); | ||
@@ -243,3 +306,3 @@ }, | ||
var deferred = $.when.apply(root, resources); | ||
var deferred = $.when.apply($, resources); | ||
doneCallback && deferred.done(_.bind(doneCallback, this)); | ||
@@ -272,7 +335,18 @@ failCallback && deferred.fail(_.bind(failCallback, this)); | ||
BaseView.prototype.undelegateEvents = BaseView.prototype.removeEvents; | ||
BaseView.prototype.delegateEvents = BaseView.prototype.setupEvents; | ||
_.extend(BaseView.prototype, { | ||
undelegateEvents: BaseView.prototype.removeEvents, | ||
delegateEvents: BaseView.prototype.setupEvents | ||
}); | ||
_.each(['appendTo', 'prependTo', 'insertBefore', 'insertAfter'], function(methodName) { | ||
BaseView.prototype[methodName] = function(selector) { | ||
this.$el[methodName](selector instanceof BaseView ? selector.$el : selector); | ||
return this; | ||
}; | ||
}); | ||
return BaseView; | ||
})); |
@@ -1,1 +0,1 @@ | ||
!function(a,b){"function"==typeof define&&define.amd?define(["jquery","backbone","underscore"],b):"object"==typeof module&&module.exports?module.exports=b(require("jquery"),require("backbone"),require("underscore")):a.BaseView=b(a.jQuery,a.Backbone,a._)}(this,function(a,b,c){var d=this,e=/{{(\S+)}}/g,f=function(a,b){return a.replace(e,function(a,c){var e=0===c.indexOf("this."),f=e?b:d,g=(e?c.slice(5):c).split(".");for(var h in g)if(f=f[g[h]],"undefined"==typeof f)throw new Error("Undefined variable in event string");return f})},g=b.View.extend({constructor:function(){b.View.apply(this,arguments),this.events&&this.setupEvents()},delegatedEvents:!0,setupEvents:function(b){var d=this.ens=this.ens||"."+this.cid,e=b||this.events,g=this,h={window:window,document:window.document};return c.each("function"==typeof e?e.call(this):e,function(b,c){c=f(c,g);var e=0===c.indexOf("one:"),i=(e?c.slice(4):c).split(" "),j=i[0]+d,k=i.slice(1).join(" "),l=g.$el;h[k]?(l=g["$"+k]=g["$"+k]||a(h[k]),k=void 0):g.delegatedEvents||((g.elementsWithBoundEvents=g.elementsWithBoundEvents||[]).push(l=l.find(k)),k=void 0),l[e?"one":"on"](j,k,function(){("function"==typeof b?b:g[b]).apply(g,arguments)})}),this},removeEvents:function(){var a=this.ens;return a&&(this.$el&&this.$el.off(a),this.$document&&this.$document.off(a),this.$window&&this.$window.off(a),this.elementsWithBoundEvents&&(c.each(this.elementsWithBoundEvents,function(b){b.off(a)}),this.elementsWithBoundEvents=null)),this},addView:function(a,b){return this.views=this.views||{},this.views[a.cid]=a,a.model&&(this.viewsWithModel=this.viewsWithModel||{},this.viewsWithModel[a.model.cid]=a),b&&(this.viewsGroups=this.viewsGroups||{},this.viewsGroups[b]=this.viewsGroups[b]||{},this.viewsGroups[b][a.cid]=a),this.listenToOnce(a,"afterRemove detachView",function(){delete this.views[a.cid],a.model&&this.viewsWithModel&&delete this.viewsWithModel[a.model.cid],b&&this.viewsGroups&&this.viewsGroups[b]&&delete this.viewsGroups[b][a.cid]}),a},getGroupViews:function(a){return this.viewsGroups&&this.viewsGroups[a]?c.values(this.viewsGroups[a]):[]},hasView:function(a){return this.views&&!!this.views[a.cid]},detachView:function(){return this.trigger("detachView"),this},attachToView:function(a,b){return this.detachView(),a.addView(this,b),this},removeViews:function(a){return a?this.viewsGroups&&this.viewsGroups[a]&&(c.invoke(this.viewsGroups[a],"remove"),delete this.viewsGroups[a]):(this.views&&c.invoke(this.views,"remove"),delete this.views,delete this.viewsWithModel,delete this.viewsGroups),this},getViewByModel:function(a){return this.viewsWithModel&&this.viewsWithModel[a.cid]},removeViewByModel:function(a){return this.viewsWithModel&&this.viewsWithModel[a.cid]&&this.viewsWithModel[a.cid].remove(),this},addDeferred:function(a){return this.deferreds=this.deferreds||[],c.indexOf(this.deferreds,a)<0&&this.deferreds.push(a),a},abortDeferreds:function(){return this.deferreds&&c.each(this.deferreds,function(a){"object"==typeof a&&a.state&&"pending"===a.state()&&(a.abort?a.abort():a.reject())}),delete this.deferreds,this},when:function(b,e,f){c.each(b=c.isArray(b)?b:[b],function(a){this.addDeferred(a)},this);var g=a.when.apply(d,b);return e&&g.done(c.bind(e,this)),f&&g.fail(c.bind(f,this)),g},remove:function(){return this.trigger("beforeRemove"),this.removeEvents().abortDeferreds().removeViews(),b.View.prototype.remove.call(this),this.trigger("afterRemove"),this},setElement:function(a){return this._setElement(a),this}});return g.prototype.undelegateEvents=g.prototype.removeEvents,g.prototype.delegateEvents=g.prototype.setupEvents,g}); | ||
!function(a,b){"function"==typeof define&&define.amd?define(["jquery","backbone","underscore"],b):"object"==typeof module&&module.exports?module.exports=b(require("jquery"),require("backbone"),require("underscore")):a.BaseView=b(a.jQuery,a.Backbone,a._)}(this,function(a,b,c){var d={window:window,document:window.document},e=function(a,b){return a.replace(/{{(\S+)}}/g,function(a,c){var d=0===c.indexOf("this."),e=d?b:window,f=(d?c.slice(5):c).split(".");for(var g in f)if(void 0===(e=e[f[g]]))throw new Error("Undefined variable in event string");return e})},f=b.View.extend({constructor:function(d){if(this.assignOptions){var e=c.result(this,"defaults");this.options="deep"===this.assignOptions?a.extend(!0,{},e,d):c.extend({},e,d)}b.View.apply(this,arguments),this.events&&this.setupEvents()},delegatedEvents:!0,parseEventVariables:!0,assignOptions:!1,setupEvents:function(b){var f=b||this.events,g="function"==typeof f?f.call(this):f,h=this;if(g){var i=this.ens=this.ens||"."+this.cid;c.each(g,function(b,c){h.parseEventVariables&&(c=e(c,h));var f=0===c.indexOf("one:"),g=(f?c.slice(4):c).split(" "),j=g[0]+i,k=g.slice(1).join(" "),l=h.$el;d[k]?(l=h["$"+k]=h["$"+k]||a(d[k]),k=void 0):h.delegatedEvents||((h.elementsWithBoundEvents=h.elementsWithBoundEvents||[]).push(l=l.find(k)),k=void 0),l[f?"one":"on"](j,k,function(){("function"==typeof b?b:h[b]).apply(h,arguments)})})}return this},addDismissListener:function(b,c){var d=this;if(!b)throw new Error("Dismiss listener name not speficied");return c=a.extend({$el:this.$el},c),this.$document=this.$document||a(document),this.ens=this.ens||"."+this.cid,this.dismissListeners=this.dismissListeners||{},this.dismissListeners[b]||(this.dismissListeners[b]=function(e){27!==e.keyCode&&(a(e.target).is(c.$el)||a.contains(c.$el.get(0),e.target))||d[b].call(d)},this.$document.on("click"+this.ens+" keyup"+this.ens,this.dismissListeners[b])),this},removeDismissListener:function(a){if(!a)throw new Error("Name of dismiss listener to remove not specified");return this.dismissListeners&&this.dismissListeners[a]&&(this.$document.off("click keyup",this.dismissListeners[a]),delete this.dismissListeners[a]),this},removeEvents:function(){var a=this.ens;return a&&(this.$el&&this.$el.off(a),this.$document&&this.$document.off(a),this.$window&&this.$window.off(a),this.elementsWithBoundEvents&&(c.each(this.elementsWithBoundEvents,function(b){b.off(a)}),delete this.elementsWithBoundEvents),delete this.dismissListeners),this},addView:function(a,b){return this.views=this.views||{},this.views[a.cid]=a,a.model&&(this.viewsWithModel=this.viewsWithModel||{},this.viewsWithModel[a.model.cid]=a),b&&(this.viewsGroups=this.viewsGroups||{},this.viewsGroups[b]=this.viewsGroups[b]||{},this.viewsGroups[b][a.cid]=a),this.listenToOnce(a,"afterRemove detachView",function(){delete this.views[a.cid],a.model&&this.viewsWithModel&&delete this.viewsWithModel[a.model.cid],b&&this.viewsGroups&&this.viewsGroups[b]&&delete this.viewsGroups[b][a.cid]}),a},getGroupViews:function(a){return this.viewsGroups&&this.viewsGroups[a]?c.values(this.viewsGroups[a]):[]},hasView:function(a){return this.views&&Boolean(this.views[a.cid])},detachView:function(){return this.trigger("detachView"),this},attachToView:function(a,b){return this.detachView(),a.addView(this,b),this},removeViews:function(a){return a?this.viewsGroups&&this.viewsGroups[a]&&(c.invoke(this.viewsGroups[a],"remove"),delete this.viewsGroups[a]):(this.views&&c.invoke(this.views,"remove"),delete this.views,delete this.viewsWithModel,delete this.viewsGroups),this},getViewByModel:function(a){return this.viewsWithModel&&this.viewsWithModel[a.cid]},removeViewByModel:function(a){return this.viewsWithModel&&this.viewsWithModel[a.cid]&&this.viewsWithModel[a.cid].remove(),this},addDeferred:function(a){return this.deferreds=this.deferreds||[],c.indexOf(this.deferreds,a)<0&&this.deferreds.push(a),a},abortDeferreds:function(){return this.deferreds&&c.each(this.deferreds,function(a){"object"==typeof a&&a.state&&"pending"===a.state()&&(a.abort?a.abort():a.reject())}),delete this.deferreds,this},when:function(b,d,e){c.each(b=c.isArray(b)?b:[b],function(a){this.addDeferred(a)},this);var f=a.when.apply(a,b);return d&&f.done(c.bind(d,this)),e&&f.fail(c.bind(e,this)),f},remove:function(){return this.trigger("beforeRemove"),this.removeEvents().abortDeferreds().removeViews(),b.View.prototype.remove.call(this),this.trigger("afterRemove"),this},setElement:function(a){return this._setElement(a),this}});return c.extend(f.prototype,{undelegateEvents:f.prototype.removeEvents,delegateEvents:f.prototype.setupEvents}),c.each(["appendTo","prependTo","insertBefore","insertAfter"],function(a){f.prototype[a]=function(b){return this.$el[a](b instanceof f?b.$el:b),this}}),f}); |
@@ -1,2 +0,3 @@ | ||
/* jshint node: true */ | ||
var attire = require('attire'); | ||
module.exports = function(grunt) { | ||
@@ -6,5 +7,2 @@ | ||
npmPackage: grunt.file.readJSON('package.json'), | ||
bowerPackage: grunt.file.readJSON('bower.json'), | ||
uglify: { | ||
@@ -18,6 +16,3 @@ min: { | ||
ext: '.min.js' | ||
}], | ||
options: { | ||
} | ||
}] | ||
} | ||
@@ -37,41 +32,14 @@ }, | ||
jshint: { | ||
eslint: { | ||
options: { | ||
'jshintrc': '.jshintrc' | ||
configFile: '.eslintrc.js' | ||
}, | ||
all: ['src', 'Gruntfile.js'] | ||
target: ['src/**/*.js', 'Gruntfile.js', 'test/index.js'] | ||
}, | ||
jscs: { | ||
options: { | ||
config: '.jscsrc' | ||
}, | ||
scripts: { | ||
files: { | ||
src: ['src/**/*.js', 'Gruntfile.js'] | ||
} | ||
} | ||
}, | ||
includereplace: { | ||
dist: { | ||
options: { | ||
globals: { | ||
repositoryUrl: '<%= npmPackage.repository.url %>', | ||
npmRepositoryName: '<%= npmPackage.name %>', | ||
bowerRepositoryName: '<%= bowerPackage.name %>' | ||
}, | ||
prefix: '{{ ', | ||
suffix: ' }}' | ||
}, | ||
src: 'demo/index.html', | ||
dest: 'index.html' | ||
} | ||
}, | ||
watch: { | ||
jsFiles: { | ||
readme: { | ||
expand: true, | ||
files: ['src/**/*.js', 'Gruntfile.js'], | ||
tasks: ['jshint', 'jscs', 'copy', 'uglify'], | ||
files: ['README.md'], | ||
tasks: ['buildDemo'], | ||
options: { | ||
@@ -81,6 +49,6 @@ spawn: false | ||
}, | ||
demoFiles: { | ||
jsFiles: { | ||
expand: true, | ||
files: ['demo/**/*.html'], | ||
tasks: ['includereplace'], | ||
files: ['src/**/*.js'], | ||
tasks: ['eslint', 'uglify', 'copy'], | ||
options: { | ||
@@ -103,7 +71,42 @@ spawn: false | ||
grunt.registerTask('buildDemo', function() { | ||
var done = this.async(); | ||
attire.buildDemo({ | ||
file: 'README.md', | ||
dest: 'index.html', | ||
title: 'Backbone base view', | ||
description: 'Baseview is a extended backbone view with convenient methods for manipulating subviews and events.', | ||
canonicalUrl: 'http://dbrekalo.github.io/backbone-base-view/', | ||
githubUrl: 'https://github.com/dbrekalo/backbone-base-view', | ||
userRepositories: { | ||
user: 'dbrekalo', | ||
onlyWithPages: true | ||
}, | ||
author: { | ||
caption: 'Damir Brekalo', | ||
url: 'https://github.com/dbrekalo', | ||
image: 'https://s.gravatar.com/avatar/32754a476fb3db1c5a1f9ad80c65d89d?s=80', | ||
email: 'dbrekalo@gmail.com', | ||
github: 'https://github.com/dbrekalo', | ||
twitter: 'https://twitter.com/dbrekalo' | ||
}, | ||
afterParse: function($) { | ||
$('p').first().remove(); | ||
$('a').first().parent().remove(); | ||
}, | ||
inlineCss: true, | ||
}).then(function() { | ||
done(); | ||
grunt.log.ok(['Demo builded']); | ||
}); | ||
}); | ||
require('load-grunt-tasks')(grunt); | ||
grunt.registerTask('default', ['watch']); | ||
grunt.registerTask('build', ['jshint', 'jscs', 'uglify', 'copy', 'includereplace']); | ||
grunt.registerTask('default', ['build', 'watch']); | ||
grunt.registerTask('build', ['eslint', 'uglify', 'copy', 'buildDemo']); | ||
}; |
{ | ||
"name": "backbone-base-view", | ||
"version": "2.0.0-beta.6", | ||
"version": "2.0.0", | ||
"description": "Baseview is a extended backbone view with convenient methods for manipulating subviews and events.", | ||
@@ -13,3 +13,9 @@ "main": "src/baseView.js", | ||
"watch": "grunt build && grunt watch", | ||
"build": "grunt build" | ||
"build": "grunt build", | ||
"watch:test": "node ./node_modules/karma/bin/karma start --browsers PhantomJS", | ||
"test": "node ./node_modules/karma/bin/karma start --single-run --browsers PhantomJS", | ||
"coveralls": "cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage", | ||
"bump:patch": "grunt bump:patch", | ||
"bump:minor": "grunt bump:minor", | ||
"bump:major": "grunt bump:major" | ||
}, | ||
@@ -23,15 +29,28 @@ "keywords": [ | ||
"devDependencies": { | ||
"grunt": "^0.4.5", | ||
"grunt-bump": "^0.7.0", | ||
"grunt-cli": "^0.1.13", | ||
"grunt-contrib-copy": "^0.6.0", | ||
"grunt-contrib-jshint": "^0.10.0", | ||
"grunt-contrib-uglify": "^0.6.0", | ||
"grunt-contrib-watch": "^0.6.1", | ||
"grunt-include-replace": "^3.2.0", | ||
"grunt-jscs": "^1.5.0", | ||
"load-grunt-tasks": "^0.6.0" | ||
"attire": "^1.3.2", | ||
"chai": "^3.5.0", | ||
"coveralls": "^2.11.15", | ||
"grunt": "^1.0.1", | ||
"grunt-bump": "^0.8.0", | ||
"grunt-cli": "^1.2.0", | ||
"grunt-contrib-copy": "^1.0.0", | ||
"grunt-contrib-uglify": "^2.0.0", | ||
"grunt-contrib-watch": "^1.0.0", | ||
"grunt-eslint": "^19.0.0", | ||
"istanbul-instrumenter-loader": "^2.0.0", | ||
"karma": "^1.4.1", | ||
"karma-chrome-launcher": "^2.0.0", | ||
"karma-coverage-istanbul-reporter": "^0.2.3", | ||
"karma-mocha": "^1.3.0", | ||
"karma-phantomjs-launcher": "^1.0.2", | ||
"karma-sourcemap-loader": "^0.3.7", | ||
"karma-spec-reporter": "0.0.26", | ||
"karma-webpack": "^2.0.2", | ||
"load-grunt-tasks": "^3.5.2", | ||
"mocha": "^3.2.0", | ||
"mocha-loader": "^1.1.0", | ||
"webpack": "^2.2.1" | ||
}, | ||
"dependencies": { | ||
"backbone": "^1.2.3", | ||
"backbone": "^1.3.3", | ||
"jquery": ">=1.11", | ||
@@ -38,0 +57,0 @@ "underscore": "^1.8.3" |
191
README.md
@@ -1,7 +0,33 @@ | ||
#Backbone base view | ||
Extended backbone view with convenient methods for manipulating subviews and events. | ||
Every view that extends baseView is bootstrapped to act as collection view. | ||
# Backbone base view | ||
[![Build Status](https://travis-ci.org/dbrekalo/backbone-base-view.svg?branch=master)](https://travis-ci.org/dbrekalo/backbone-base-view) | ||
[![Coverage Status](https://coveralls.io/repos/github/dbrekalo/backbone-base-view/badge.svg?branch=master)](https://coveralls.io/github/dbrekalo/backbone-base-view?branch=master) | ||
[![NPM Status](https://img.shields.io/npm/v/backbone-base-view.svg)](https://www.npmjs.com/package/backbone-base-view) | ||
###events | ||
```javascript | ||
Backbone view extension with enhanced event handling and convenient helpers for handling sub-views. | ||
Compose view components with simple and easy to use api. | ||
Weighs less than 3KB. | ||
BaseView extends backbone view events functionality so you can bind one time events, | ||
inject variables into event strings and setup global window and document listeners that will be properly unbound once view is removed. | ||
Enables easy composition of views with simple parent-child and model binding api that keeps you safe from memory leaks. | ||
## Examples and api | ||
### assignOptions: false|true|'deep' | ||
If defined user passed options will be merged with defaults and written to viewInstance.options. False by default. | ||
```js | ||
var View = BaseView.extend({ | ||
assignOptions: true, | ||
defaults: {test: 1}, | ||
initialize: function() { | ||
console.log(this.options); // outputs {foo:'bar', test: 1} | ||
} | ||
}); | ||
var view = new View({foo:'bar'}); | ||
``` | ||
--- | ||
### events | ||
Define one time events, inject variables and add window and document listeners. | ||
```js | ||
events: { | ||
@@ -15,62 +41,139 @@ 'click .selector': 'handler', | ||
``` | ||
###addView | ||
```javascript | ||
addView(view, group) | ||
--- | ||
### delegatedEvents: true|false | ||
View event handlers are delegated to instance element by default. Set to false to bind directly to elements found via event string selector. | ||
```js | ||
var View = BaseView.extend({ | ||
delegatedEvents: false, | ||
events: { | ||
'click .selector': 'handler' | ||
} | ||
}); | ||
var view = new View({foo:'bar'}); | ||
``` | ||
Adds target view to current view. If optional group is provided adds view to subview group. | ||
--- | ||
###hasView | ||
```javascript | ||
hasView(view) | ||
### addDismissListener(listenerName) | ||
When escape key is pressed or something outside of view.$el is clicked view.listenerName will be invoked. | ||
```js | ||
... | ||
open: function() { | ||
this.$el.addClass('active'); | ||
this.addDismissListener('close'); | ||
} | ||
close: function() { | ||
this.$el.removeClass('active'); | ||
this.removeDismissListener('close'); | ||
} | ||
... | ||
``` | ||
Check if target view is subview. | ||
--- | ||
###getGroupViews | ||
```javascript | ||
getGroupViews(group) | ||
### removeDismissListener(listenerName) | ||
Use to remove dismiss listeners. See example above. | ||
--- | ||
### addView(view, groupName) | ||
Use to setup parent-child view relationship. Child view is added to parent view group if groupName is specified. | ||
```js | ||
... | ||
initialize: function() { | ||
this.collection.each(function(model) { | ||
this.addView(new ChildView, 'itemList'); | ||
}, this); | ||
} | ||
... | ||
``` | ||
Get group subviews as array | ||
--- | ||
###detachView | ||
```javascript | ||
detachView() | ||
### getGroupViews(groupName) | ||
Retrieve child views stored in parent group as array. | ||
```js | ||
... | ||
render: function() { | ||
_.each(this.getGroupViews('itemList'), function(subView) { | ||
subView.render(); | ||
}); | ||
} | ||
... | ||
``` | ||
Detach view from parent registry | ||
--- | ||
###attachToView | ||
```javascript | ||
attachToView(view, group) | ||
### removeViews(viewGroup) | ||
Removes all sub views. If viewGroup is specified removes only group views. | ||
```js | ||
parentView.removeViews('itemList'); | ||
``` | ||
Detach view from parent registry and attach to another view. | ||
--- | ||
###removeViews | ||
```javascript | ||
removeViews(group) | ||
### remove() | ||
Does extended cleanup and triggers "beforeRemove" and "afterRemove" events on view instance. | ||
```js | ||
view.remove(); | ||
``` | ||
Removes all subviews recursively. If group is specified removes only views which belong to subview group. | ||
--- | ||
###remove | ||
```javascript | ||
remove() | ||
### getViewByModel(model) | ||
Get parent sub-view by providing it's model instance. | ||
```js | ||
var childView = parentView.getViewByModel(model); | ||
``` | ||
Does extended cleanup and triggers "beforeRemove" and "afterRemove" events. | ||
--- | ||
###getViewByModel | ||
```javascript | ||
getViewByModel(model) | ||
### removeViewByModel(model) | ||
Close sub-view by providing it's model instance. | ||
```js | ||
parentView.removeViewByModel(model); | ||
``` | ||
Get subview by providing it's model instance. | ||
--- | ||
###removeViewByModel | ||
```javascript | ||
removeViewByModel(model) | ||
### hasView(childView) | ||
Check if parent view has child sub-view. | ||
```js | ||
console.log(parentView.hasView(childView)); | ||
``` | ||
Close subview by providing it's model instance. | ||
--- | ||
###when | ||
```javascript | ||
when(resources, doneCallback, failCallback) | ||
### detachView() | ||
Detach view from parent sub-view registry. | ||
```js | ||
childView.detachView(); | ||
``` | ||
--- | ||
### attachToView(parentView, group) | ||
Detach view from parent sub-view registry and attach to another view. | ||
```js | ||
childView.attachToView(parentView) | ||
``` | ||
--- | ||
### appendTo(view) | ||
Append view.$el to another view.$el. Other available methods are 'prependTo', 'insertBefore', 'insertAfter'. | ||
```js | ||
view.appendTo(parentView); | ||
``` | ||
--- | ||
### when(resources, doneCallback, failCallback) | ||
Shortcut for $.when with default context set to view instance for all callbacks. | ||
Additionally adds all deferreds to view instance deferreds stack so effective cleanup can be performed on view removal. | ||
Accepts resources as single or array of deferreds. | ||
## Installation | ||
Backbone base view is packaged as UMD library so you can use it in CommonJS and AMD environment or with browser globals. | ||
```bash | ||
npm install backbone-base-view --save | ||
``` | ||
```js | ||
// with bundlers | ||
var BaseView = require('backbone-base-view'); | ||
// with browser globals | ||
var BaseView = window.BaseView; | ||
``` |
(function(root, factory) { | ||
/* istanbul ignore next */ | ||
if (typeof define === 'function' && define.amd) { | ||
@@ -13,30 +14,37 @@ define(['jquery', 'backbone', 'underscore'], factory); | ||
var root = this, | ||
viewCounter = 0, | ||
variableInEventStringRE = /{{(\S+)}}/g, | ||
parseEventString = function(eventString, context) { | ||
var variableInEventStringRE = /{{(\S+)}}/g; | ||
var specialSelectors = { | ||
'window': window, | ||
'document': window.document | ||
}; | ||
var parseEventVariables = function(eventString, context) { | ||
return eventString.replace(variableInEventStringRE, function(match, namespace) { | ||
return eventString.replace(variableInEventStringRE, function(match, namespace) { | ||
var isInCurrentContext = namespace.indexOf('this.') === 0, | ||
current = isInCurrentContext ? context : root, | ||
pieces = (isInCurrentContext ? namespace.slice(5) : namespace).split('.'); | ||
var isInCurrentContext = namespace.indexOf('this.') === 0, | ||
current = isInCurrentContext ? context : window, | ||
pieces = (isInCurrentContext ? namespace.slice(5) : namespace).split('.'); | ||
for (var i in pieces) { | ||
current = current[pieces[i]]; | ||
if (typeof current === 'undefined') { | ||
throw new Error('Undefined variable in event string'); | ||
} | ||
for (var i in pieces) { | ||
current = current[pieces[i]]; | ||
if (typeof current === 'undefined') { | ||
throw new Error('Undefined variable in event string'); | ||
} | ||
} | ||
return current; | ||
return current; | ||
}); | ||
}); | ||
}; | ||
}; | ||
var BaseView = Backbone.View.extend({ | ||
constructor: function() { | ||
constructor: function(options) { | ||
if (this.assignOptions) { | ||
var defaults = _.result(this, 'defaults'); | ||
this.options = this.assignOptions === 'deep' ? $.extend(true, {}, defaults, options) : _.extend({}, defaults, options); | ||
} | ||
Backbone.View.apply(this, arguments); | ||
@@ -48,36 +56,42 @@ this.events && this.setupEvents(); | ||
delegatedEvents: true, | ||
parseEventVariables: true, | ||
assignOptions: false, | ||
setupEvents: function(eventsMap) { | ||
var eventNamespace = this.ens = this.ens || '.' + this.cid, | ||
events = eventsMap || this.events, | ||
self = this, | ||
specialSelectors = { | ||
'window': window, | ||
'document': window.document | ||
}; | ||
var eventsProvider = eventsMap || this.events; | ||
var eventList = typeof eventsProvider === 'function' ? eventsProvider.call(this) : eventsProvider; | ||
var self = this; | ||
_.each(typeof events === 'function' ? events.call(this) : events, function(handler, eventString) { | ||
if (eventList) { | ||
eventString = parseEventString(eventString, self); | ||
var eventNamespace = this.ens = this.ens || '.' + this.cid; | ||
var isOneEvent = eventString.indexOf('one:') === 0, | ||
splitEventString = (isOneEvent ? eventString.slice(4) : eventString).split(' '), | ||
eventName = splitEventString[0] + eventNamespace, | ||
eventSelector = splitEventString.slice(1).join(' '), | ||
$el = self.$el; | ||
_.each(eventList, function(handler, eventString) { | ||
if (specialSelectors[eventSelector]) { | ||
$el = self['$' + eventSelector] = self['$' + eventSelector] || $(specialSelectors[eventSelector]); | ||
eventSelector = undefined; | ||
} else if (!self.delegatedEvents) { | ||
(self.elementsWithBoundEvents = self.elementsWithBoundEvents || []).push($el = $el.find(eventSelector)); | ||
eventSelector = undefined; | ||
} | ||
if (self.parseEventVariables) { | ||
eventString = parseEventVariables(eventString, self); | ||
} | ||
$el[isOneEvent ? 'one' : 'on'](eventName, eventSelector, function() { | ||
(typeof handler === 'function' ? handler : self[handler]).apply(self, arguments); | ||
var isOneEvent = eventString.indexOf('one:') === 0, | ||
splitEventString = (isOneEvent ? eventString.slice(4) : eventString).split(' '), | ||
eventName = splitEventString[0] + eventNamespace, | ||
eventSelector = splitEventString.slice(1).join(' '), | ||
$el = self.$el; | ||
if (specialSelectors[eventSelector]) { | ||
$el = self['$' + eventSelector] = self['$' + eventSelector] || $(specialSelectors[eventSelector]); | ||
eventSelector = undefined; | ||
} else if (!self.delegatedEvents) { | ||
(self.elementsWithBoundEvents = self.elementsWithBoundEvents || []).push($el = $el.find(eventSelector)); | ||
eventSelector = undefined; | ||
} | ||
$el[isOneEvent ? 'one' : 'on'](eventName, eventSelector, function() { | ||
(typeof handler === 'function' ? handler : self[handler]).apply(self, arguments); | ||
}); | ||
}); | ||
}); | ||
} | ||
@@ -88,2 +102,49 @@ return this; | ||
addDismissListener: function(listenerName, options) { | ||
var self = this; | ||
if (!listenerName) { | ||
throw new Error('Dismiss listener name not speficied'); | ||
} | ||
options = $.extend({$el: this.$el}, options); | ||
this.$document = this.$document || $(document); | ||
this.ens = this.ens || '.' + this.cid; | ||
this.dismissListeners = this.dismissListeners || {}; | ||
if (!this.dismissListeners[listenerName]) { | ||
this.dismissListeners[listenerName] = function(e) { | ||
if (e.keyCode === 27 || (!$(e.target).is(options.$el) && !$.contains(options.$el.get(0), e.target))) { | ||
self[listenerName].call(self); | ||
} | ||
}; | ||
this.$document.on('click' + this.ens + ' keyup' + this.ens, this.dismissListeners[listenerName]); | ||
} | ||
return this; | ||
}, | ||
removeDismissListener: function(listenerName) { | ||
if (!listenerName) { | ||
throw new Error('Name of dismiss listener to remove not specified'); | ||
} | ||
if (this.dismissListeners && this.dismissListeners[listenerName]) { | ||
this.$document.off('click keyup', this.dismissListeners[listenerName]); | ||
delete this.dismissListeners[listenerName]; | ||
} | ||
return this; | ||
}, | ||
removeEvents: function() { | ||
@@ -103,5 +164,7 @@ | ||
}); | ||
this.elementsWithBoundEvents = null; | ||
delete this.elementsWithBoundEvents; | ||
} | ||
delete this.dismissListeners; | ||
} | ||
@@ -155,3 +218,3 @@ | ||
return this.views && !!this.views[view.cid]; | ||
return this.views && Boolean(this.views[view.cid]); | ||
@@ -243,3 +306,3 @@ }, | ||
var deferred = $.when.apply(root, resources); | ||
var deferred = $.when.apply($, resources); | ||
doneCallback && deferred.done(_.bind(doneCallback, this)); | ||
@@ -272,7 +335,18 @@ failCallback && deferred.fail(_.bind(failCallback, this)); | ||
BaseView.prototype.undelegateEvents = BaseView.prototype.removeEvents; | ||
BaseView.prototype.delegateEvents = BaseView.prototype.setupEvents; | ||
_.extend(BaseView.prototype, { | ||
undelegateEvents: BaseView.prototype.removeEvents, | ||
delegateEvents: BaseView.prototype.setupEvents | ||
}); | ||
_.each(['appendTo', 'prependTo', 'insertBefore', 'insertAfter'], function(methodName) { | ||
BaseView.prototype[methodName] = function(selector) { | ||
this.$el[methodName](selector instanceof BaseView ? selector.$el : selector); | ||
return this; | ||
}; | ||
}); | ||
return BaseView; | ||
})); |
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
80457
14
1086
2
179
23
1
Updatedbackbone@^1.3.3