I'm brand new to Ember.js, but well-versed in MVC and Backbone (even cocoa and smalltalk), but for some reason, Ember's insistence on its highly-obscured and inconsistent API is shielding me from understanding 1) binding and 2) how that relates to persisting records.
I have a view that has an input. I know that. I have a model that belongs to a controller. I know that. I don't understand how the view knows (or is supposed to know) about the controller, and I don't understand how the model gets the text I type into the view when I'm ready to push it to the backend (ostensibly with .save()). These are supposed to be boilerplate style things that Ember breathlessly abstracts away, but I find I'm having to go through the Ember.js source just to understand what the heck is happening.
I've read the docs, and their examples are so contrived as to be unusable (featuring only the most basic examples, which is pointless when we're talking about "ambitious web apps", which ember purports to be for).
It should be noted that we are using ember-data in house, and this particular route has multiple views, controllers and models.
So let's say you have an input in the index template:
<script type="text/x-handlebars">
{{input type="text" value=name}}
</script>
Ember automatically binds the App.IndexController to this template and the name property of the controller to the value property of the input field. As the value is changed in the input, it'll automatically update in the controller. When you're ready to save it in the model you can access the property in a action.
App.IndexController = Ember.ObjectController.extend({
actions: {
save:function() {
var name = this.get('name');
var model = this.store.createRecord('item');
item.set('name',name);
item.save();
}
}
});
This will trigger a post request to /items to store the item.
If you have a more specific example in mind, I can try to show you how that works.
Related
I have a two CRUD screens in AngularJS.
1) Separate html file for View, Add and Edit. View Controller, Add Controller and Edit Controllers also Separate. This structure creating more duplicate code in html and controller side.
2) Separate html file for View, Add. View Controller, Add Controller only Separate. During edit mode I'm using a Boolean in controller to find its in edit mode or not.
I'm new to AngularJS. Anybody clearly tell me pros and cons, which way is correct in AngularJS 'Separation Of Concerns' concept.
I'm not sure you're going to the good direction.
By separation of concern, what is intended is to split the code that is responsible for managing the view, and the code responsible to make calls to your server. Imagine you want to make CRUD around a person, I would do the following :
service :
angular.module('app').factory("personService", ["$http", function($http)]){
return {
create : create,
update: update,
remove : remove,
get: get
};
function create(person){
return http.post("person/create", person);
}
// other functions
}
Then, I would only have 1 controller for everything :
angular.module("app").controller("PersonController", ["personService", function(personService)]){
var self = this;
self.isUpdate = true; // Insert logic here
self.isCreate = false; // Insert logic here
init();
self.save = function(){
var promise = self.isCreate ?
personService.create(self.person)
:personService.update(self.person);
promise.then(function(result){
// Handle return of save;
});
};
function init(){
if (!self.isCreate){
personService.get(personId).then(function(result){
self.person = result.data.person;
});
}
}
}
And then I would have the following view :
<div ng-controller="personController as person">
<label>Name: </label>
<input type="text" ng-disabled="!person.isUpdate" ng-model="person.person.name" />
<button ng-click="person.isUpdate = !person.isUpdate;">Edit</button>
<!-- Edit : the code 'person.isUpdate = !person.isUpdate;' could also be into a controller's function (like the save function) -->
<button ng-click="person.save()" ng-if="person.isUpdate">Save</save>
</div>
I also recommend you to read this : https://github.com/johnpapa/angular-styleguide
Separation of concerns means that you have a well defined structure of your application: the data model in the application is decoupled from the business and presentation logic. It is the base of the MVC pattern, which defines the view, the controller and the model.
This separation makes the code maintainable and easy to test.
Follow this guidance how to architect your Angular application.
The model should:
Include the domain data;
Implement the management of the domain data (query, edit, delete, storing mechanism, REST implementation, http fetching);
Expose an API that makes possibly the model usage in controller or other service;
The model should not:
Provide the details on how the domain data is managed (all REST implementation, http calls should be encapsulated in the model);
Contain logic that transforms the model based on user interaction (it is controller's role);
Contain logic for displaying data to the user (this is the view’s job);
A controller should:
Contain the logic required to initialize the scope;
Contain the logic/behaviors required by the view to present data from the scope;
Contain the logic/behaviors required to update the scope based on user interaction;
A controller should not:
Contain logic that manipulates the DOM (that is the job of the view);
Contain logic that manages the persistence of data (that is the job of the model);
Manipulate data outside of the scope;
A view should:
Contain the logic and markup required to present data to the user
A view should not:
Contain complex logic (this is better placed in a controller);
Contain logic that creates, stores, or manipulates the domain model.
This guidance is taken from this awesome book, which I recommend for any starting AngularJS developer.
Related to your example, in my opinion you should:
Create a controller and a template for editing and adding; Depending on model's isNew property, you can apply editing or adding action;
Create a controller and a template for viewing the model;
Optionally, if you have a collection of models, you can create a new controller and view also.
However it depends on the amount of logic behind the model. If the model is trivial, probably you can implement everything in a single controller and view. But it's rare.
I am researching on emberjs latly. I have a question on how do views communicate with each other. I know views do not communicate so the question could be either Route or Controller (view model).
Coming from other frameworks and languages I used a pattern which is called pubsub to propagate the information that a certain information has changed and other views (who are interested in this information) would subscribe and could update if the event/message was sent.
Is this also a pattern which applies to the emberjs philosophy? Or how would you update different parts on the UI based on changes in an other part?
A (very) simple example could be:
-> Application
-> Navigation which contains a Link to the posts route with a number of current posts (count)
-> Posts route would have a list of post with details (and add,delete,etc) actions
Now when I create a post it would be added to the list and how should I updated the post count in the navigation?
Obviously this could/should be also applicable for more complex examples.
As a note:
At the moment I have also a server route for the stats. So the navigation and its posts count will be fetched from the server and its not bound to the length of the post array...
What is the best way to handle something like this with emberjs?
As stated in http://emberjs.com/guides/views/ , Views are basically used to handle user events and create reusable components. However some useful facts are,
1.The contents that a View's template will present are based on the model and properties that the corresponding Controller holds.
2.A View can access it's corresponding Controller as well as any other associated controllers (via the needs property http://emberjs.com/guides/controllers/dependencies-between-controllers/) and the data that they hold.
3.If Views share a model then they will be updated whenever the model changes. For this to occur there are a few cases, one of the most common is inserting views through the {{view}} helper.(http://emberjs.com/guides/views/inserting-views-in-templates/)
4.If Views or their corresponding controllers are observing or are bound to common properties then they will also be updated (http://emberjs.com/guides/object-model/classes-and-instances/).
With these facts in mind this particular problem could be tackled based on the requirements of the specific system. For example some options could be (accompanied with rough examples),
If all your data/posts are available when executing crud actions then binding to the enumeration of posts (http://emberjs.com/guides/object-model/computed-properties-and-aggregate-data/) of the Posts Controller could update a computed property of the associated Navigation controller that displays the total posts.
http://emberjs.jsbin.com/huqoq/1/edit
If data is lazily loaded, e.g. in pages, then having the Navigation controller observe the changes on the Posts Controller enumeration of posts, could trigger a request to the server to retrieve the total number of posts or update a counter value depending on the action (create/delete).
http://emberjs.jsbin.com/jomes/1/edit
The Navigation View could simply be inserted within the Posts View and share the same model and have a computed property displaying total posts.
http://emberjs.jsbin.com/xujap/1/edit
The Navigation View could be rendered via the {{render}} helper with the model of posts.(http://emberjs.com/guides/templates/rendering-with-helpers/)
http://emberjs.jsbin.com/gihep/1/edit
This is pretty straightforward in Ember.js. There are different ways of setting this up, but the basics are the following: Since Ember is an MVC framework, all of your views will have access to the same underlying data. As you make changes to the data Ember's data bindings will make sure all views that care about that change will be updates.
So specifically for the example you mentioned you could simply include {{ content.length }} in your template, which will display the number of items in your ArrayController (which is auto-generated by Ember, although you could override it if your wanted to add functionality) . Have a look at this jsFiddle.
Templates:
<script type="text/x-handlebars" data-template-name="application">
<h1>ember-latest jsfiddle</h1>
{{ render 'navigation' controllers.users.content }}
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="navigation">
<h2>Index Content ({{ content.length }}):</h2>
</script>
<script type="text/x-handlebars" data-template-name="index">
<ul>
{{#each}}
<li>{{firstName}} {{lastName}}</li>
{{/each}}
</ul>
<button {{action 'add'}}>Add</button>
</script>
Routes:
App.ApplicationRoute = Ember.Route.extend({
setupController: function() {
this.controllerFor('users').set('content', this.store.findAll('user'));
}
});
App.IndexRoute = Ember.Route.extend({
model: function() {
return this.store.findAll('user');
}
});
Controllers:
App.ApplicationController = Ember.Controller.extend({
needs: ['users'],
});
App.IndexController = Ember.ArrayController.extend({
actions: {
add: function() {
this.get('store').createRecord('user', {firstName: 'New', lastName: 'Person'});
}
}
});
As you click the button you'll see how the count automatically updates. Note that in this case we only have one route (to illustrate data bindings better), but this would work just as well if you had an index route (to list all posts) and a detail route (to show a single post).
Welcome to the magical world of Ember.js :)
I am trying to retrieve the underlying model object from a controller so that it can be persisted (I am not using ember-data). The obvious way would simply be:
controller.get('content');
But this doesn't work. The problem can be summed up as follows:
controller.set("content", model);
sets the content as expected and at this point
controller.get('content');
works as expected. But if I then decorate the controller with other properties eg.
controller.set('IamNotPartOfTheModel', false);
then suddenly the 'content' includes this new property. I would've expected the content to remain unchanged and the new property to only be applied to the controller itself. I understand the controller is a proxy for the model so for the most part they are treated as one and the same but surely they should still be separable when needed? The whole point of this pattern is to separate data that should be stored from data that is just temporary. Am I missing something?
To have your display specific properties out of the model, just specify them explicitly in the controller... Otherwise the controller acts as a proxy for its model... just have the property "iamNotPartOfTheModel" in your controller
App.IndexController = Ember.ObjectController.extend({
iamNotPartOfTheModel: null
})
Sample fiddle here
Your controller needs to interface to some kind of model. You can't separate the persisted model from the controller except by some kind of object reference. Even if you don't use ember-data, you'll still need to create objects which then plug into the content of the controller. Have a look at Evil Trout's blog for an implementation of ember without ember-data. Its a great starting point.
In the data-driven paradigm of Backbone, the backbone views/routers should subscribe to model changes and act based on the model change events. Following this principle, the application's views/routers can be isolated from each other, which is great.
However, there are a lot of changes in the states of the applications that are not persisted in the models. For example, in a to-do app, there could be buttons that lets you look at tasks that are "completed", "not completed", or "all". This is an application state not persisted in the model. Note that the completion state of any task is persisted, but the current filter in the view is a transient state.
What is a good way to deal with such application state? Using a plain, non-backboned state means that the views/routers cannot listen to the changes in this state, and hence become difficult to code in the data-driven paradigm.
Your buttons filter example can be properly solved using Model events.
I suppose your buttons handlers have access to the tasks Collection. Then filter the collection and trigger events over the selected Models like:
model.trigger( "filter:selected" )
or
model.trigger( "filter:un-selected" )
The ModelView can be listening to these events on its Model and acts accordingly.
This is following your requirements of respecting the not use or "attributes that are not persistent" like selected but I don't have any trauma to use special attributes even if they shouldn't be persistent. So I also suggest to modify the selected attribute of your Models to represent volatile states.
So in your buttons handlers filter the collection and modify the selected attribute in your Models is my preferred solution:
model.set( "selected", true )
You can always override Model.toJSON() to clean up before sync or just leave this special attributes to travel to your server and being ignored there.
Comment got long so I'll produce a second answer to compare.
First, I feel like "completed", "not completed" are totally item model attributes that would be persisted. Or maybe if your items are owned by many users, each with their own "completed" "not completed" states, then the item would have a completedState submodel or something. Point being, while #fguillen produced two possible solutions for you, I also prefer to do it his second way, having models contain the attributes and the button / view doing most of the work.
To me it doesn't make sense for the model to have its own custom event for this. Sounds to me like a filter button would only have to deal with providing the appropriate views. (Which items to show) Thus, I would just make the button element call a function that runs a filter on the collection more or less directly.
event: {
'click filterBtnCompleted':'showCompleted'
},
showCompleted: function(event) {
var completedAry = this.itemCollection.filter(function(item) {
return item.get('completed');
});
// Code empties your current item views and re-renders them with just filtered models
}
I tend to tuck away these kind of convenience filter functions within the collection themselves so I can just call:
this.ItemCollection.getCompleted(); // etc.
Leaving these attributes in your model and ignoring them on your server is fine. Although again, it does sound to me like they would be attributes you want to persist.
One more thing, you said that using plain non-backboned states sacrifices events. (Grin :-) Not so! You can easily extend any object to have Backbone.Event capabilities.
var flamingo = {};
_.extend(flamingo, Backbone.Events);
Now you can have flamingo trigger and listen for events like anything else!
EDIT: to address Router > View data dealings -------------------//
What I do with my router might not be what you do, but I pass my appView into the router as an options. I have a function in appView called showView() that creates subviews. Thus my router has access to the views I'm dealing with pretty much directly.
// Router
initialize: function(options) {
this.appView = options.appView;
}
In our case, it may be the itemsView that will need to be filtered to present the completed items. Note: I also have a showView() function that manages subviews. You might just be working directly with appView in your scenario.
So when a route like /items/#completed is called, I might do something like this.
routes: {
'completed':'completed'
},
completed: {
var itemsView = ItemCollectionView.create({
'collection': // Your collection however you do it
});
this.appView.showView(itemsView);
itemsView.showCompleted(); // Calls the showCompleted() from View example way above
}
Does this help?
I'm trying to figure out if Backbone.js is the right framework for my current project: a visualization app.
I have a number of questions:
1) State / Routing?
As this is not your typical RESTful app, but rather a visualization application with various chart types and settings for these charts, how do i maintain state in the URL?
Let's say my areaChart model has a number of defaults like this:
AreaChartModel = Backbone.Model.extend({
defaults: {
selectedCountries: [],
year: 1970,
stacked: false
},
initialize: function(){
[...]
}
});
On an update to the model I'd like to serialize some of these attributes so that I can bookmark the specific state: chartApp.html#!year=1970&stacked=false etc.
And vice-versa, when initing the app with this state, how do I "deparam" the url state and set the model? Can I use Backbone's intrinsic routing?
2) Controller and coupling?
It seems as Backbone has a pretty tight view-model coupling?
Is this really how I should bind for example my areaChartView to the model?
AreaChartView = Backbone.View.extend({
initialize: function(){
areaChartModel.bind("change:year", this.render);
}
});
Isn't this normally the role of the controller?
3) Continuation: Model vs. Controller?
Given this scenario:
A change in the "Sidebar" should trigger a sequence of functions:
1) "New data for the current selection should be loaded"
2) "Based on this data, the scales in the Visualization view should be updated"
3) "The visualization view should be rendered"
Where should I place these functions and how can I create an event in the model that I trigger when the state is stable? (i.e. when all the functions have been invoked and it's time to set the view states?)
1) I would use Backbone.js native routing as much as possible using “:params” and “*splats” , read more. You could fit all your queries into the Backbone.js routing but I would personally sacrifice certain things in favor of intuitive UI buttons
e.g. I would have the default as a line bar and you can't preset this with the URL but to change to a stacked graph would be a simple click of a button.
I would probably stray from ever using ? and & in my URL's. I might come back to this point later as it is interesting.
2) Your example is fine and you just need to remember Backbone.js MVC terminology doesn't correlate to traditional MVC.
Backbone Views are essentially the Controller in traditional MVC.
Backbone Controllers are simply a way of routing inside a framework.
The templating engine you use with Backbone.js is the traditional MVC view.
3) Still writing
Regarding question #3, I would create a Model and a View for the slider.
Then I would associate the triggering of the change event on the model to some function in the view that updates the graph's view (like changing the scales). Something like:
var Slider = Backbone.Model.extend({})
var SliderView = Backbone.View.extend({
initialize: function() {
this.model.bind('change', this.render);
}
render: function() {
// load data, change scales, etc.
}
});
var slider = new Slider();
var slider_view = new SliderView({ model: slider });
Maybe a good idea would be to put the bindings in a parent view, that would then dispatch to sub-views, coordinating their work.
Do sit down for a while and consider if maintaining the entire state is at all a good idea ? The key motivations for having url-based state management is being able to support browser based navigation buttons and being able to bookmark a page. In a visualization app, your data would probably change every moment. This is not something you want to persist in your app-url. Do you really want that when a user bookmarks your app and comes back to it three days later - he sees the visualization for three days old data ? For your scenario, assuming I have not misunderstood your requirements, I would recommend to keep the data state in your model itself.
Also regarding synchronization of views with model data, Yes you can code all the binding logic on your own. In that case your View class will take care of setting up the bindings on the first render. And upon subsequent calls to render, which can be invoked in response to any change event in the model, will refresh the DOM/canvas where the visualization is present.
Probably you should be look forward to a plugin for data-synchronization that takes care of much of boilerplate for you. This page lists some of the data-binding extensions available. Orchestrator is another solution that I have been working on, which might be helpful in this regard.