i'm developping a sample single web-app and i discover Chaplin.js.
I had implemented a simple login form, and create in application.js (as given example) in intiMediator method:
Chaplin.mediator.user = new User();
and subscribe for login event in loginController to redirect to internal pages when event is catched
Chaplin.mediator.subscribe('login', this.test);
finally when user submit login form in loginView i'd wrote
Chaplin.mediator.user.set({"name":this.model.get("name"),"age":this.model.get("age")});
Chaplin.mediator.publish('login',this.model);
And all went ok until i click on browser back button to return to login form. When resubmit the form neither mediator.user has an user obj nor the controller has any subscripion.
Has anyone any tips?
Tks
As stated in the Chaplin.js docs "If you want to give local pub/sub functionality to a class, take a look at the EventBroker."
Adding pub/sub to a class:
In your User class, you can mixin to add pub/sub functionality by using Underscore's _.extend:
module.exports = class User
_.extend #prototype, Chaplin.EventBroker
constructor: (options) ->
#subscribeEvent 'login', #test
# ...
Persist data between routes:
Now, persist data across routes is another story and you can approach that in many ways depending on your needs. For example, you may know that you can persist a Chaplin.View and its related data using Chaplin.Composer
Also, you have plenty of useful tools to keep track of the disposal process. For example you have beforeControllerDispose event that is emitted before current controller is disposed.
You could subscribe this event to keep some desired data persisted between routes with the help of a static class. For example, subscribe your Controller to the beforeControllerDispose event:
# in Controller..
#subscribeEvent 'beforeControllerDispose', #beforeDispose
# ...
beforeDispose: ->
MyStaticClass.user = #user
# ... you can do some extra cleanup here.
# useful for unbinding stuff that you know it will not be garbage collected/disposed
And then, use Chaplin.Controller.beforeAction method to ensure that your object is available from the very begining. beforeAction, as its names indicates, is automatically called by Chaplin before calling the action (index, show..) of your actual route/endpoint, so you can use it to pre-assign some data:
# in Controller..
beforeAction: ->
#user = MyStaticClass.user
# ...
index: ->
# #user is available here
Related
What I did is:
When a menu item is clicked, a action will be done, like deleting a user, sending emails to a group, etc. To this end, for each menu item, I define a ui-router state, and use the state url to activate the state via sref. I thought that a menu action is just a UI component for user to let users to do something, which is just a state of UI.
I was advised that I was using ui-router in a wrong way as a state url can not identify an action. For example, to delete a group of users, the state url can not tell you what group of users have been deleted.
In short, I agree with your manager while being an angular newbie myself. Angular routes are designed for managing different views of your app. I.e. define a route and corresponding view template for each view. If you add application logic into the routes, your application structure gets quickly a mess and difficult to keep clear.
To me it is much more natural that the views are managed by the routes, and each action in each view is handled by the controller of that view. If the actions grow "big", then it is worth refactoring parts of the controller into separate services. If you require some sort of "dynamic HTML" depending on the action, e.g. bootstrap modals are handy for doing that within the current view (see http://angular-ui.github.io/bootstrap/).
E.g. in my current project, I don't actually manually edit the routes at all but let yeoman angular generator to do that for me free of charge - i.e. I instantiate each new view in my dev.env using the following command (more info on this from https://github.com/yeoman/generator-angular)
yo angular:route myNewView
More info on angular philosophy can be read from angular documentation for developers: https://docs.angularjs.org/guide/concepts
You should probably be doing this actions via a method on $scope.
$scope.deleteItem = function (items) {
Service.delete(items);
};
// which is the same as:
$scope.deleteItem = Service.delete;
<a ng-click="deleteItem(item)">Delete This Item</a>
Having it in the URL just seems wrong. I mean what does that look like? www.mysite.com/delete/users?
I'm trying to trigger some events at initialize in my views. They work if I back and forth in the browser or if I load a second view, but not at first loading, i.e. when the user lands in the view.
My setup is as follows:
I create an event aggregator at router.js like:
socketEvents: _.extend({}, Backbone.Events)
Then I pass the aggregator to each view like:
index: function() {
this.changeView(new IndexView({socketEvents:this.socketEvents}));
},
Then in index.js I do something like so:
initialize: function(options) {
this.socketEvents = options.socketEvents;
this.socketEvents.trigger('one:event');
},
As I said, if I refresh the page doesnt work, if I move to another view and hit back in the browser, the event is triggered.
Why is this happenning? How can I solve this?
Thanks
Editing after first comment:
There are not errors I can/know how to track. At initialize() Either it just does seamlessly the whole transaction: trigger and listenTo, or it completely ignores it at initialize(). Order of execution is as follows: I boot the application with require, from there it loads a class with the router and socket.io client. Then it iniatilizes the socket.io client and uses the router event aggregator. Then it checks if the user is logged, if it is, it triggers an router.event saying the user is logged in. At the socket.io client, there is an event.Dispatcher binding the logged in to initiating the socket. Finally the user is redirector to a index view. Then it happens as I described above.
From my understanding, the differences is the callback functions to events on an AppRouter should exist in the Controller, instead of the same Router object. Also there is a one-to-one relationship between such AppRouter & Controllers, all my code from Router now moves to Controller, I don't see too much point of that? So why use them? I must be missing something?
The way I see it is to separate concerns:
the controller actually does the work (assembling the data, instanciating the view, displaying them in regions, etc.), and can update the URL to reflect the application's state (e.g. displayed content)
the router simply triggers the controller action based on the URL that has been entered in the address bar
So basically, if you're on your app's starting page, it should work fine without needing any routers: your actions (e.g. clicking on a menu entry) simply fire the various controller actions.
Then, you add on a router saying "if this URL is called, execute this controller action". And within your controller you update the displayed URL with navigate("my_url_goes_here"). Notice you do NOT pass trigger: true.
For more info, check out Derick's blog post http://lostechies.com/derickbailey/2011/08/28/dont-execute-a-backbone-js-route-handler-from-your-code/ (paragraph "The “AHA!” Moment Regarding Router.Navigate’s Second Argument")
I've also covered the topic in more length in the free preview of my book on Marionette. See pages 32-46 here: http://samples.leanpub.com/marionette-gentle-introduction-sample.pdf
I made some override for the router. And currently use it in this way (like Chaplin):
https://gist.github.com/vermilion1/5525972
appRoutes : {
// route : controller#method
'search' : 'search#search'
'*any' : 'common#notFound'
},
initialize : function () {
this.common = new Common();
this.search = new Search();
}
When a save, or create is tossed towards the server, the server responds with a new randomly created object. The object can be one of many different Classes, and Backbone responds to these differentiating objects and loads a relative view.
I can only seem to figure this logic out on bootstrap, as no view has been loaded yet, so I can based on what information I am randomly receiving from the server, bootstrap and navigate to that specific route.
However, I am stuck on trying to figure out how to do this when I save an object, and receive my return data.
Here's my code broken down.
The information is saved.
#model.save(#model.toJSON(),
I have a listenener waiting for this save :
constructor: (options) ->
super(options)
#model.bind 'change:verb', _.chooser, options
_.maestra_chooser is a mixin I have in a utility belt :
_.mixin
_chooser : (item) =>
console.log item
Something to note here. The variable item is unfortunately, the same #model that was just saved. No new data there.
What I'm hoping for item to be is the new variable data from the server, so that I can take that data, see what kind of data it is, and then route to the relevant view.
This is where I believe I'm also making an architecturally unsound idea. But for reasons I don't understand enough to explain.
Does anyone know where I can access the return data from the server and appropriately navigate my app to that respective route?
Additional Information
This is how I bootstrap it appropriately :
window.router = new Project.Routers.QuestionsRouter(
{
words: #{ #words.to_json.html_safe }
});
Backbone.history.start();
router.navigate("#{#words.kind_of?(Array) ? "bar" : "foo"}", {trigger: true, replace: true})
The change event is only ever going to give you the model and the value that changed...
You can pass a success callback to your save:
#model.save(#model.toJSON(), success: (model, resp) ->
# do whatever with resp
)
where resp will contain the raw response from the server and model will contain the server side state of your model.
You can also bind to your model's sync event as mentioned in the comments:
#model.bind 'sync', _.masetra_chooser, options
the sync callback is called with arguments: model, resp and options where options is the set of options passed to save.
https://github.com/documentcloud/backbone/blob/9a12b7640f07839134e979b66df658b70e6e4fe9/backbone.js#L383
Not really sure why you are expecting to get data back from a save that'll change your page though. Seems a bit odd.
What type of data are you expecting to receive after a save that wouldn't be in your model?
In a backbone application what is the best practice regarding when a model is fetched? I can see the following possibilities.
View calls the model fetch method
Some other JavaScript code calls the fetch model? if so when and what structure would this code have? is this the missing controller concept in Backbone?
A few best practives:
1 Collections and models that are necessary from the very first milliseconds of the app's life should be 'bootstrapped' in place (so there shouldn't be need to fetch them to gain access to vital data)
So when the user is served the correct pages from the server, the models and collections should be already in place (nice example from backbone.js docs)
var ExampleCollection = new Backbone.Collection();
ExampleCollection.reset(<%= #your_collection_data.to_json() %>); // Or whatever your server-side language requires you to do, this is a ruby example
2 The rest can be fetched just in time
The models and collections that aren't needed at the moment your app is initialized can be fetched whenever you feel like it, but I think that the logical time to do that is when the user expresses intent to use those models. E.g. user presses a button to open a view that needs some model/collection -> fetch that collection, user wants to clear unsaved changes from a model -> fetch that model from the server to get the last saved status of the model, and so forth. Usually the place where the fetching is bound to happen is the view that 'owns' the model/collection, because it relays the users actions to the model and displays the model's state to the user.
But like it was said, Backbone.js isn't strict about when a model or collection should be fetched. It can be done anywhere in the app, just make sure you do it only when it's necessary.
Hope this helps!
If you want to be standard, your view must render one time when initialize and listen for the change event of the Model and re render the view every time that model changes, that is all. (regarding what does View needs to do when fetch is completed)
And for call the model.fetch() if you follow the standard that I said, no matters where the fetch is called your view will be updated.
Some people could have a module named load in the view where do something like this:
load : function(){
this.model.fetch();
}
Others could do external fetch call, like this:
var myModel = new YourModel();
var myView = new SomeView( {model : model} );
//Probably you could render with the default data in the while model is fetched
myView.render();
model.fetch();