Skip to content

Commit

Permalink
Merge pull request #37 from ilyavolodin/no-view-onof-binding
Browse files Browse the repository at this point in the history
Rule no-view-onoff-binding (fixes #35)
  • Loading branch information
ilyavolodin committed Mar 2, 2015
2 parents 9273445 + d312ab8 commit a1ff082
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 1 deletion.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ Defaults are currently set to the following:
"no-silent": 1,
"no-view-collection-models": 2,
"no-view-model-attributes": 2,
"no-view-onoff-binding": 2,
"render-return": 2
```

Expand Down Expand Up @@ -118,4 +119,5 @@ If you are using custom models/view/collection bases you also have to specify ea
* [no-silent](docs/rules/no-silent.md)
* [no-view-collection-models](docs/rules/no-view-collection-models.md)
* [no-view-model-attributes](docs/rules/no-view-model-attributes.md)
* [no-view-onoff-binding](docs/rules/no-view-onoff-binding.md)
* [render-return](docs/rules/render-return.md)
43 changes: 43 additions & 0 deletions docs/rules/no-view-onoff-binding.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Prevent using on/off bindings inside views (no-view-onoff-binding)

Instead of using `on` and `off` methods to subscribe to model/collection events, use `listenTo` and `stopListening` methods.
If you remove a view that uses `on` binding to the model events, view is not completely removed from the memory, because
model still has reference to event handler function that lives inside the view. This can be avoided by using `off` before
removing a view. Or by using `listenTo` function.


## Rule Details

The following patterns are considered warnings:

```js

Backbone.View.extend({
initalize: function() {
this.model.on("change", this.render);
}
});

```

The following patterns are not warnings:

```js

Backbone.View.extend({
initialize: function() {
this.listenTo(this.model, "change", this.render);
}
});

```

## When Not To Use It

If you destroy your models more often then you destroy your views, then you should use `on` method instead of `listenTo`.
Or if you always use `off` method before destroying the view.


## Further Reading

[BackboneJS Documentation](http://backbonejs.org/#Events-listenTo)
2 changes: 2 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ module.exports = {
"no-silent": require("./lib/rules/no-silent"),
"no-view-collection-models": require("./lib/rules/no-view-collection-models"),
"no-view-model-attributes": require("./lib/rules/no-view-model-attributes"),
"no-view-onoff-binding": require("./lib/rules/no-view-onoff-binding"),
"render-return": require("./lib/rules/render-return")
},
rulesConfig: {
Expand All @@ -35,6 +36,7 @@ module.exports = {
"no-silent": 1,
"no-view-collection-models": 2,
"no-view-model-attributes": 2,
"no-view-onoff-binding": 2,
"render-return": 2
}
};
45 changes: 45 additions & 0 deletions lib/rules/no-view-onoff-binding.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* @fileoverview Prevent using on/off bindings inside views
* @author Ilya Volodin
* @copyright 2015 Ilya Volodin. All rights reserved.
*/
"use strict";

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

var helper = require("../backbone-helper.js");

module.exports = function(context) {

var backboneView = [];
var settings = context.settings || /* istanbul ignore next */ {};

//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------

return {
"CallExpression": function(node) {
backboneView.push(backboneView[backboneView.length - 1] || helper.isBackboneView(node, settings.backbone));
},
"CallExpression:exit": function(node) {
if (helper.isBackboneView(node, settings.backbone)) {
backboneView.pop();
}
},
"MemberExpression": function(node) {
if (backboneView[backboneView.length - 1] &&
node.object.type === "MemberExpression" &&
node.object.object.type === "ThisExpression" &&
(node.object.property.name === "model" || node.object.property.name === "collection")) {
if (node.property.name === "on") {
context.report(node, "Use listenTo instead of 'on'.");
} else if (node.property.name === "off") {
context.report(node, "Use stopListening instead of 'off'.");
}
}
}
};
};
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "eslint-plugin-backbone",
"version": "1.0.0",
"version": "1.0.1",
"description": "Eslint rules for Backbone.",
"main": "index.js",
"scripts": {
Expand Down
47 changes: 47 additions & 0 deletions tests/lib/rules/no-view-onoff-binding.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* @fileoverview Prevent using on/off bindings inside views
* @author Ilya Volodin
* @copyright 2015 Ilya Volodin. All rights reserved.
*/
"use strict";

//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------

var eslint = require("eslint").linter,
ESLintTester = require("eslint-tester");

//------------------------------------------------------------------------------
// Tests
//------------------------------------------------------------------------------

var eslintTester = new ESLintTester(eslint);
eslintTester.addRuleTest("lib/rules/no-view-onoff-binding", {

valid: [
"Backbone.View.extend({ initialize: function() { this.$el.on('click', this.render); } });",
"Backbone.Model.extend({ initialize: function() { this.collection.on('change', this.changeSomething); } });",
"Backbone.View.extend({ initialize: function() { this.listenTo(this.model, 'change', this.render); } });",
"Backbone.View.extend({ initialize: function() { this.model.attributes[0] = 'test'; } });"
],

invalid: [
{
code: "Backbone.View.extend({ initialize: function() { this.model.on('change', this.render); } });",
errors: [ { message: "Use listenTo instead of 'on'." } ]
},
{
code: "Backbone.View.extend({ initialize: function() { this.collection.on('change', this.render); } });",
errors: [ { message: "Use listenTo instead of 'on'." } ]
},
{
code: "Backbone.View.extend({ initialize: function() { this.model.off('change', this.render); } });",
errors: [ { message: "Use stopListening instead of 'off'." } ]
},
{
code: "Backbone.View.extend({ initialize: function() { this.collection.off('change', this.render); } });",
errors: [ { message: "Use stopListening instead of 'off'." } ]
}
]
});

0 comments on commit a1ff082

Please sign in to comment.