Related
I would like to understand how to have a nice organisation in my angular project.
[see code below]
Does it makes sense to have the getFireList function into the Factory ? Or should i put it into the controller ?
Does the "class" Fire makes sense ? Should i remove it ? Should i move it to the controller ? Should i move it the the factory ?
If you see anything wrong in this code i'm really interested to learn more.
For now, i've got this :
A class "Fire" to create new object of type Fire.
function Fire (p_power) {
// ATTRIBUTES
this.id = null;
this.power = p_power;
this.position = {
x: null,
y: null
}
// GETTERS/SETTERS
// id
this.getId = function() {
return this.id;
}
this.setId = function(p_id) {
this.id = p_id;
}
// power
this.getPower = function() {
return this.power;
}
this.setPower = function(p_power) {
this.power = p_power;
}
// position
this.getPosition = function() {
return this.position;
}
this.setPosition = function(p_position) {
this.position = p_position;
}
// METHODS
this.increasePower = function(p_plus) {
this.power += p_plus;
}
this.decreasePower = function(p_minus) {
this.power -= p_minus;
}
}
A controller
simuApp.controller('FireController', function($scope, FireFactory) {
// ...
});
And a factory
simuApp.factory('FireFactory', function() {
return {
fire_list: [],
getFireList : function() {
return $http.get(site_url+'fire/fireList').
then(
function(success) {
var data = success.data;
var fires = [];
var fire_tmp;
for (i=0 ; i<data.length ; i++) {
fire_tmp = new Fire( data[i].power );
fire_tmp.setId( data[i].idFire );
fires.push( fire_tmp );
}
fire_list = fires;
return fire_list;
}, function(err) {
// ...
}
);
}
}
});
Thanks for your help.
First, let's get the terminology right. .factory is a method to register a function that generates an instance of the service - hence "factory". What it generates, though, is a singleton service instance.
So, the service you create would be more properly named as FireSvc (as opposed to FireFactory), whereas the function that creates it could have the word "factory" in it (although, in the case below, that function name is not really needed - it could just be an anonymous function):
.factory("FireSvc", function FireSvcFactory(){
});
It is a good practice to use a Service to abstract away any domain/business logic from the controller. Keep the controller thin, responsible only to define the ViewModel, and react to events by changing the ViewModel or invoking functions on the Model.
So, having FireSvc.getFireList() makes sense.
Now, whether the list is a collection of plain objects, or instances of Fire is completely independent of Angular and is entirely up to you. In any case, it is too broad of a topic to discuss in a SO answer.
I have a basic application using Backbone.js that is not making PUT calls (updating model). From the front-end, I calling a models save function doesn't make a PUT call; however, if I replace it with destroy, it does make a DELETE call to the back-end. Anyone have any idea what might be the issue? The function that is not firing a PUT request is the saveTask function.
App.Views.Task = Backbone.View.extend({
template: _.template("<label>ID:</label><input type='text' id='taskId' name='id' value='<%= _id %>' disabled /><br><label>Title:</label><input type='text' id='title' name='title' value='<%= title %>' required/><br><label>Content:</label><input type='text' id='content' name='content' value='<%= content %>'/><br><button class='save'>Save</button>"),
events: {
"change input":"change",
"click .save":"saveTask"
},
render: function(eventName){
$(this.el).html(this.template(this.model.toJSON()));
//console.log(this.generateTemplate());
return this;
},
change: function(event){
var target = event.target;
console.log('changing ' + target.id + ' from: ' + target.defaultValue + ' to: ' + target.value);
change[target.name] = target.value;
this.model.set(change);*/
},
saveTask: function(){
this.model.set({
title:$("#title").val(),
content:$("#content").val()
});
if(this.model.isNew()){
App.taskList.create(this.model);
} else {
this.model.save({});
}
}
});
If your model is new, then at the time you save it it will fire a post method.
If your model however is not new and you are updating it, it will fire a PUT.
if this is not working for you it may be because your model does not have an id property, in case you are using an id with a different name, for example taskID, then in your model you have to set the idAttribute to taskID so backbone uses this property as the Id and everything will be normal.
like this:
var Task= Backbone.Model.extend({
idAttribute: "taskId"
});
here is the link to the documentation on Idattibute
http://backbonejs.org/#Model-idAttribute
also another problem could be the {} in your save call
try just
this.model.save();
instead of
this.model.save({});
I believe model is always expecting options parameter and also probably the callbacks
this.model.save(null, {
success: function (model, response) {
//
},
error: function () {
//
}
});
If you look at Backbone src, you will notice that too...
======
// Set a hash of model attributes, and sync the model to the server.
// If the server returns an attributes hash that differs, the model's
// state will be `set` again.
save: function (key, val, options) {
var attrs, method, xhr, attributes = this.attributes;
// Handle both `"key", value` and `{key: value}` -style arguments.
if (key == null || typeof key === 'object') {
attrs = key;
options = val;
} else {
(attrs = {})[key] = val;
}
options = _.extend({
validate: true
}, options);
// If we're not waiting and attributes exist, save acts as
// `set(attr).save(null, opts)` with validation. Otherwise, check if
// the model will be valid when the attributes, if any, are set.
if (attrs && !options.wait) {
if (!this.set(attrs, options)) return false;
} else {
if (!this._validate(attrs, options)) return false;
}
// Set temporary attributes if `{wait: true}`.
if (attrs && options.wait) {
this.attributes = _.extend({}, attributes, attrs);
}
// After a successful server-side save, the client is (optionally)
// updated with the server-side state.
if (options.parse === void 0) options.parse = true;
var model = this;
var success = options.success;
options.success = function (resp) {
// Ensure attributes are restored during synchronous saves.
model.attributes = attributes;
var serverAttrs = model.parse(resp, options);
if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs);
if (_.isObject(serverAttrs) && !model.set(serverAttrs, options)) {
return false;
}
if (success) success(model, resp, options);
model.trigger('sync', model, resp, options);
};
wrapError(this, options);
method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update');
if (method === 'patch') options.attrs = attrs;
xhr = this.sync(method, this, options);
// Restore attributes.
if (attrs && options.wait) this.attributes = attributes;
return xhr;
},
In my case it fails due to validations.As i save the model it validates all the attributes of the model and the collection which i am using for listing interface doesn't required all the attributes of the model.
I was facing the same issues and search in Google and found your question and read the solution and comments.Than i realize that in updated backbone specifications it is mentioned that when model.save() executes before model requests,it first call validate and if validate succeeds than it will go ahead other wise fails, and that is the reason why it doesn't showing any network request in chrome debugger network tab.
I have write the solution for the case which i am facing,other might be facing different issues.
Backbone's sync function is what I wound up using. You have to pass in 'update' as the first parameter (the 'method' parameter).
I am in the process of getting a simple drawing view created using Easeljs inside of a Backbonejs view. I am having an issue with scope, in my listener events, for example:
this.stage.addEventListener("stagemousedown", this.handleMouseDown);
I started to started doing this to get around my scope issue
var self = this;
this.stage.addEventListener("stagemousedown", function(){
var foo = self.bar;
});
However, this seems sloppy, especially since the sample code I am porting (http://www.createjs.com/#!/EaselJS/demos/drawing), has nested levels of eventListeners.
SketchView = Backbone.View.extend({
initialize: function(){
this.canvas;
this.stage;
this.drawingCanvas;
this.oldPt;
this.oldMidPt;
this.title;
this.color;
this.stroke;
this.colors;
this.index;
},
beforeRender : function (){
this.template = _.template( tpl.get(this.templateFile) );
},
render: function(eventName) {
$(this.el).html(this.template(this.model));
return this;
},
//add in UI
afterRender : function (){
this.createUI();
},
createUI: function() {
this.canvas = document.getElementById("demoCanvas");
this.index = 0;
this.colors = ["#828b20", "#b0ac31", "#cbc53d", "#fad779", "#f9e4ad", "#faf2db", "#563512", "#9b4a0b", "#d36600", "#fe8a00", "#f9a71f"];
//Create a stage by getting a reference to the canvas
this.stage = new createjs.Stage(this.canvas);
this.stage.autoClear = false;
this.stage.enableDOMEvents(true);
createjs.Touch.enable(this.stage);
createjs.Ticker.setFPS(24);
this.stage.addEventListener("stagemousedown", this.handleMouseDown);
this.stage.addEventListener("stagemouseup", this.handleMouseUp);
this.title = new createjs.Text("Click and Drag to draw", "36px Arial", "#777777");
this.title.x = 300;
this.title.y = 200;
this.stage.addChild(this.title);
this.stage.addChild(this.drawingCanvas);
this.stage.update();
},
handleMouseDown: function (event) {
if (this.stage.contains(this.title)) { this.stage.clear(); this.stage.removeChild(this.title); }
this.color = this.colors[(this.index++)%this.colors.length];
this.stroke = Math.random()*30 + 10 | 0;
this.oldPt = new createjs.Point(this.stage.mouseX, this.stage.mouseY);
this.oldMidPt = this.oldPt;
this.stage.addEventListener("stagemousemove" , this.handleMouseMove);
},
handleMouseMove: function (event) {
var midPt = new createjs.Point(this.oldPt.x + this.stage.mouseX>>1, this.oldPt.y+this.stage.mouseY>>1);
this.drawingCanvas.graphics.clear().setStrokeStyle(this.stroke, 'round', 'round').beginStroke(this.color).moveTo(midPt.x, midPt.y).curveTo(this.oldPt.x, this.oldPt.y, this.oldMidPt.x, this.oldMidPt.y);
this.oldPt.x = this.stage.mouseX;
this.oldPt.y = this.stage.mouseY;
this.oldMidPt.x = midPt.x;
this.oldMidPt.y = midPt.y;
this.stage.update();
},
handleMouseUp: function (event) {
this.stage.removeEventListener("stagemousemove" , this.handleMouseMove);
}
});
An unrelated question, is should I be instantiating my variables in the initialize function? I am still new to Backbone, and trying to figure out best practices.
Backbone requires the Underscore library, and I strongly recommend becoming familiar with it : it has a lot of great stuff. In particular it has a _.bind method that you can use like so:
this.stage.addEventListener("stagemousedown", _(function(){
var foo = this.bar; // "this" will be correct now thanks to bind
}).bind(this);
It also has a related _.bindAll method, which can be used (often inside of an initialize function) to bind an object's methods to it, like so:
initialize: function() {
_(this).bindAll('handleMouseDown'); // handleMouseDown's this will be correct
this.stage.addEventListener("stagemousedown", this.handleMouseDown);
}
However, you can avoid all of this and let Backbone do the binding for you if you just take advantage of its event handling:
Backbone.View.extend({
events: {mousedown: 'handleMouseDown'}
handleMouseDown: function() {
// this will be bound correctly
As for your question about instantiating variables inside initialize, the answer is ... probably, but it's hard to say without knowing your specific context. In general, initialize should do anything that needs to happen when the Collection/Model/View gets created, so if initializing variables falls under that then sure.
I'm using Backbone to manage the state of an HTML form. The Model's role is to handle validation. The View's role is to wrap the HTML form and respond to the change or error events emitted by the model.
Backbone seems to only emit change events when the given field is actually valid. This is causing some really unexpected behavior that makes me thing that I'm doing this wrong.
Here is a summary of what I'm doing:
1. Initial load serializes the form and injects it into the model
2. When an error event is emitted, I generate error nodes next to the invalid field.
3. When a change event is emitted, I remove the error notes next to the (now valid) field.
When a page is rendered with an initially valid form, and a user invalidates a field, the message is displayed as expected; however, the model never updates the field internally. Thus when the user corrects the error, a change event is never emitted.
Example: Initially valid
When a page is rendered with an initially invalid form, things appear to be working fine... but this is only because the model's initial attributes are empty. Correcting the field makes the messages disappear, but if you change it again to an invalid state, the message never disappears.
Example: Initially invalid
What am I doing wrong? Perhaps there's another approach I should be using instead?
My Model
var Foo = Backbone.Model.extend({
validate: function(attr) {
var errors = {};
if (_.isEmpty(attr)) return;
if (attr.foo && attr.foo != 123) {
errors.foo = ['foo is not equal to 123'];
}
if (attr.bar && attr.bar != 456) {
errors.bar = ['bar is not equal to 456'];
}
return _.isEmpty(errors) ? undefined : errors;
}
});
My View
FooForm = Backbone.View.extend({
events: {
'change :input': 'onFieldChange'
},
initialize: function(options) {
this.model.on('error', this.renderErrors, this);
this.model.on('change', this.updateFields, this);
// Debugging only
this.model.on('all', function() {
console.info('[Foo all]', arguments, this.toJSON())
});
this.model.set(this.serialize());
},
onFieldChange: function(event) {
var field = event.target,
name = field.name,
value = field.value;
this.model.set(name, value);
},
renderErrors: function(model, errors) {
_.each(errors, function(messages, fieldName) {
var el = $('#' + fieldName),
alert = $('<div/>').addClass('error');
el.parent().find('.error').remove();
_.each(messages, function(message) {
alert.clone().text(message).insertAfter(el);
});
});
},
updateFields: function(model, options) {
if (!options || !options.changes) return;
_.each(_.keys(options.changes), function(fieldName) {
var el = $('#' + fieldName);
el.parent().find('.error').remove();
});
},
serialize: function() {
var raw = this.$el.find(':input').serializeArray(),
data = {},
view = this;
$.each(raw, function() {
// Get the model's field name from the form field's name
var name = this.name;
if (data[name] !== undefined) {
if (!data[name].push) {
data[name] = [data[name]];
}
data[name].push(this.value || '');
}
else {
data[name] = this.value || '';
}
});
return data;
}
});
You can't validate individual field using native Backbone validation.
In my app I use this validation plugin: https://github.com/thedersen/backbone.validation
Then in your model you add validation rules per each field (it's optional, so you don't need to add this to all models):
var NewReview = Backbone.Model.extend({
initialize: function() {
/* ... */
},
validation: {
summary: {
required: true,
minLength: 10
},
pros: {
required: true,
minLength: 10
},
cons: {
required: true,
minLength: 10
},
overall: function(value) {
var text = $(value).text().replace(/\s{2,}/g, ' ');
if (text.length == 0) text = value;
if (text.length < 20) return "Overall review is too short";
},
rating: {
range: [0.5, 5]
},
product_id: {
required: true
}
}
});
Than in views or elsewhere you can validate either entire model or individual fields:
if (this.model.validate()) { ... }
or
if (this.model.isValid("summary")) { ... }
I want a setup/teardown method to be called before and after a route is fired in my Backbone.js router, respectively. Has anyone created an elegant way of doing this?
_.wrap is not a solution, if You have for example 20 routes you have to wrap them all.
But you can do this with metaprogramming
class Backbone.FlexRouter extends Backbone.Router
route: (route, name, handler) ->
super route, name, ->
#trigger "route:before"
handler()
#trigger "route:after"
UPD: I believe in JS it should be something like this (but I didn't tested it)
var rp = Backbone.Router.prototype
rp.routeWithoutEvents = rp.route
rp.route = function(route, name, handler){
var that = this
this.routeWithoutEvents(route, name, function(){
that.trigger("route:before")
handler()
that.trigger("route:after")
})
}
Have you considered _.wrap?
Here is the simple one, overriding the Backbone.Router itself
(function () {
_.extend(Backbone.Router.prototype, Backbone.Events, {
route: function (route, name, callback) {
if (!_.isRegExp(route)) route = this._routeToRegExp(route);
if (!callback) callback = this[name];
Backbone.history.route(route, _.bind(function (fragment) {
var args = this._extractParameters(route, fragment);
if (this.before && _.isFunction(this.before)) {
this.before(fragment);
}
callback && callback.apply(this, args);
this.trigger.apply(this, ['route:' + name].concat(args));
if (this.after && _.isFunction(this.after)) {
this.after(fragment);
}
Backbone.history.trigger('route', this, name, args);
}, this));
return this;
}
});
}).call(this);
Focus on the lines
if (this.before && _.isFunction(this.before)) {
this.before(fragment);
}
AND
if (this.after && _.isFunction(this.after)) {
this.after(fragment);
}
You can modify the lines according to your needs
And here is the client code using the new Backbone.Router class
var appRouter = Backbone.Router.extend({
routes: {},
before: function(){
//your code here
return true;
}
});
Alexey's answer is almost right, but there are a few subtle things that are missing.
class ApplicationRouter extends Backbone.Router
route: (route, name, callback = null) ->
callback = #[name] if ! callback
super route, name, ->
#trigger 'route:before'
result = callback && callback.apply(#, arguments)
#trigger 'route:after'
return result
This plugin does what you want. It works with 0.5.3. I'm not certain if it works with 0.9.1 yet or not.
https://github.com/angelo0000/backbone_filters
I came across this problem earlier and I thought I would share my solution for inserting "middleware" into the Backbone routing flow. The goal was to reroute users to various flows depending on some condition, e.g., feature flags, session handling, etc..
Backbone.ProtectedRouter = Backbone.Router.extend({
/*
* Subclass of Router that monkeypatches route in order to protect certain
* routes.
*
* If you want to add a protected route, add it to the protectedRoutes
* object in this form:
* route: { method: fn, assertion: fn, args: [args..] }
*
* * method => the method to call if the assertion is true (the route should
* be protected in the given scenario)
*
* * assertion => the function that decides whether or not the route
* should be rendered
*
* * args => the arguments to be passed to method
*/
route: function(route, name, handler) {
var _this = this;
Backbone.Router.prototype.route(route, name, function(){
var boundHandler = _.bind(handler, _this),
attrs, method, args, dfd;
attrs = _.has(_this.protectedRoutes, route) ? _this.protectedRoutes[route] : null;
if ( attrs && !attrs.assertion() ) {
// In this scenario my flows all return Deferreds
// you can make this event based as well.
dfd = _this[attrs.method].apply(_this, attrs.args.concat([route]));
dfd.then(boundHandler);
} else
boundHandler.apply(_this, arguments);
});
}
});
From there you can simply extend the Backbone.ProtectedRouter with a protectedRoutes hash as so:
var router = Backbone.ProtectedRouter.extend({
protectedRoutes: {
'home': {
assertion: function() { return is_logged_in; },
method: 'renderLogin',
args: ['some_arg']
}
},
routes: {
'home': 'renderHome'
},
...
});
In this scenario, if a request is made for the home route and is_logged_in is false, the renderLogin method is invoked and passed 'some_arg'. After the flow, renderLogin would return a resolved Deferred that causes the original handler (renderHome) to be invoked.
I hope this helps. I'm very open to suggestions as well! :)
I came across this need recently (to check user is authenticated). Unfortunately Backbone doesn't give us a before/after event, so you will need to overwrite or extend the Router.route. Doesn't feel very clean since you have to copy from source code and edit there, but is the only way I found. Below Backbone default code (1.0.0) and marked my custom code:
Backbone.Router.prototype.route = function(route, name, callback) {
if (!_.isRegExp(route)) route = this._routeToRegExp(route);
if (_.isFunction(name)) {
callback = name;
name = '';
}
if (!callback) callback = this[name];
// here my custom code
callback = _.wrap(callback, _.bind(function(cb) {
if (name == 'login' || sessionModel.authenticated()) {
_.bind(cb, this)();
} else {
this.navigate('login', {trigger: true});
}
}, this));
// finish my custom code
var router = this;
Backbone.history.route(route, function(fragment) {
var args = router._extractParameters(route, fragment);
callback && callback.apply(router, args);
router.trigger.apply(router, ['route:' + name].concat(args));
router.trigger('route', name, args);
Backbone.history.trigger('route', router, name, args);
});
return this;
};
Notice _.wrap and _.bind so this is the one that you would expect when using the router. Otherwise I was getting a "this is undefined" error.
ethnagnawl and Alexey are both correct; _.wrap is the right solution but if you have a bunch of routes and write them in the normal backbone fashion it'll be a pain. I realized you can do this:
var Pages = {}
Pages.loginPage = function(){ ... }
Pages.mainPage = function(){ ... }
Instead of defining your route handlers directly in Router.extend, load them into an object and then do this:
_.map(Pages,function(func,name){
Pages[name] = _.wrap(func,function(funky){
// Save original arguments
var args = Array.prototype.slice.call(arguments,1);
// Do stuff before the route
funky(args);
// Do stuff after the route
});
});
This also makes it pretty easy to check for the function name if you need to treat a subset of them differently or something. Then, because it's just an object, you can do this:
var myRouter = Backbone.Router.extend({
routes: ... /* as usual */
}).extend(Pages);
And you're done.
One nice advantage of this is it involves no messing with the Backbone prototypes, so even if a version update changes something it won't bite you.
after doing lot more manipulations. i came to a solution that i have given below......
Here ur original root function ...
route: function(route, name, callback) {
if (!_.isRegExp(route)) route = this._routeToRegExp(route);
if (_.isFunction(name)) {
callback = name;
name = '';
}
if (!callback) callback = this[name];
var router = this;
Backbone.history.route(route, function(fragment) {
var args = router._extractParameters(route, fragment);
callback && callback.apply(router, args);
router.trigger.apply(router, ['route:' + name].concat(args));
router.trigger('route', name, args);
Backbone.history.trigger('route', router, name, args);
});
return this;
}
Now look at this code & change the "route" function to your original Backbone.js...
route: function(route, name, callback) {
if (!_.isRegExp(route)) route = this._routeToRegExp(route);
if (_.isFunction(name)) {
callback = name;
name = '';
}
if (!callback) callback = this[name];
var router = this;
Backbone.history.route(route, function(fragment) {
// takes matched route & fragment as like 'route1'
var args = router._extractParameters(route, fragment);
// extracts arguments if exists
// here yours self invoking function or other function starts....
(function(){
// do something
if ( true ) // condition satisfies then route to the given Route
{
callback && callback.apply(router, args);
}
else{
name='route2'; // change name of route
window.location.hash = 'route2';
callback= function(){
// optional callback if u want
}
callback && callback.apply(router, args); // route to ur custome Route
}
})();
});
return this;
}
----- Thank You --------
Love 2 write Dirty Codes !
#xy....
Here is a JavaScript version that works with what I've got;
var rp = Backbone.Router.prototype;
rp.routeWithoutEvents = rp.route;
rp.route = function(route, name, callback) {
if (!callback) callback = this[name];
this.routeWithoutEvents(route, name, function() {
this.before.apply(this);
callback.apply(this,arguments);
this.after.apply(this);
});
};
It's based on Alexey Petrushin's and Jonathan Tran's solutions.
I could not find an easy way to intercept the routing event before the route handler is called.
My solution is to extend the Router component, adding a registerBeforeRouting method and editing the route method (I took it from the Backbone 1.0 and it worked, YMMV with different Backbone versions).
Before the router is created:
var rp = Backbone.Router.prototype;
rp.registerBeforeRouting = function (callback) {
this._beforeRoutingCallback = callback;
};
rp.route = function (route, name, callback) {
if (!_.isRegExp(route)) route = this._routeToRegExp(route);
if (_.isFunction(name)) {
callback = name;
name = '';
}
if (!callback) callback = this[name];
var router = this;
Backbone.history.route(route, function(fragment) {
var args = router._extractParameters(route, fragment);
// Edit starts here
// This will trigger the callback previously set
if (typeof router._beforeRoutingCallback === 'function') {
router._beforeRoutingCallback();
}
// Edit stops here.
callback && callback.apply(router, args);
router.trigger.apply(router, ['route:' + name].concat(args));
router.trigger('route', name, args);
Backbone.history.trigger('route', router, name, args);
});
return this;
}
Then, during router initialization:
this.registerBeforeRouting(function() {
console.log("Hello world");
});
I tried the aforementioned approaches, and they somehow just didn't work for me (probably for my lack of in depth understanding of neither backbone, nor javascript in general).
I did manage to do the trick in some other manner, if that's of any interest to anyone out there:
What I actually end up doing was simply extending the View and overriding the render function just one time.
MyApp.BindedView = Backbone.View.extend({
_realRender : null,
initialize : function(){
//validating user is logged in:
if(Backbone.history.fragment != 'login' && !myUser.authenticated())
{
console.log('not authorized, redirecting');
var self = this;
this._realRender = this.render;
this.render = function(route,name,callback){
appRouter.navigate('login');
self.render = self._realRender;
}
return;
}
this.delegateEvents();
}
});
The execute method was added to be overridden for this purpose. See this example extracted from the backbonejs homepage:
var Router = Backbone.Router.extend({
execute: function(callback, args, name) {
if (!loggedIn) {
goToLogin();
return false;
}
args.push(parseQueryString(args.pop()));
if (callback) callback.apply(this, args);
}
});