Note: I imagine that this will be a super easy question for anyone with Ember experience. Don't be daunted by the length of my question. Skip down to the bottom if you don't want to read all of the overhead.
Overhead
My company has a project coming up which requires the use of front-end technologies to accomplish what I would otherwise prefer to do with PHP.
We looked into a few different JavaScript frameworks, and the solution that we agreed upon was Ember.js.
I followed the TodoMVC tutorial on their website, and learned the very basics.
With this project, we will be using an AJAX request to pull in our data at the start of the application, and then put everything into fixtures.
I'm having difficulty figuring out how to pass multiple fixtures into my template at the same time. I started with adding two fixtures. Here are their data and definitions:
App.Students = DS.Model.extend({
first: DS.attr('string'),
last: DS.attr('string'),
classes: DS.hasMany('ClassGroup')
});
App.ClassGroup = DS.Model.extend({
className: DS.attr('string'),
isActive: DS.attr('number'),
students: DS.hasMany('Students',{ async: true })
});
App.ClassGroup.FIXTURES = [
{
id: 1,
className: 'Class 1',
isActive: 1,
students: [1, 2]
},
{
id: 2,
className: 'Class 2',
isActive: 0,
students: [2]
}
];
App.Students.FIXTURES = [
{
id: 1,
first: 'Student',
last: 'One',
classes: [1]
},
{
id: 2,
first: 'Student',
last: 'Two',
classes: [1, 2]
}
];
This is just a very simple implementation. The actual data will have dozens of relations, but I've simplified things for the purpose of learning this framework.
Here is how I am currently setting up my router:
App.IndexRoute = Ember.Route.extend({
model: function() {
return this.store.find('ClassGroup');
}
});
Doing it this way allows me to access the data via Handlebars by using {{#each model}} and then something like {{className}} to access it's data members. I am also able to jump to the students array by nesting another each statement like {{#each students}}.
However, I cannot figure out how to get more than one entry point into my data. For example, by passing ClassGroup via the router, I am unable to access the Students fixture by itself; I must first loop through ClassGroup, and from there, access a student.
Likewise, if I change my router to pass in the students fixture, I can loop through the students, and then jump to the classes via a nested each statement, but I lose the ability to simply loop through a list of all classes.
Is there a way that I can pass all of my fixtures into the template? Am I going about this the right way?
The Long Story Short
How can I pass ALL of my fixtures into the template at once, in such a way that I can access my students array or my classes array? How can I access said fixture data (i.e., if I want to display the first name of the student with ID 2, represented as students[2]['first'] in a language like PHP, how can this be done with handlebars)?
That's right, the Template only has access to what it's been passed by the Controller. In this case, since you don't explicitly set up the controller, and the model is an array, it'll be an array controller, hence you ability to do {{#each}} to iterate over the ClassGroups (you actually don't even need model). You haven't passed in the students array anywhere explicitly, nor created it in the controller, so you don't have access to it in the template. Fortunately, Ember has a setupController route hook which does exactly this kind of thing. In your example:
App.IndexRoute = Ember.Route.extend({
model: function() {
return this.store.find('ClassGroup');
},
setupController: function(controller, model){
this._super(controller, model);
controller.set('students', this.store.find('Students'));
}
});
Now you'll have a students property available on your controller and therefore your template.
Related
In my Ember app, I have a complex model that looks like below (kind of contains 2-dimensional array)
[
[
{
id: 'Section1_123',
label: 'abc'
},
{
id: 'Section1_456',
label: 'xyz'
}
]
],
[
[
{
id: 'Section2_123',
label: 'abc'
},
{
id: 'Section2_456',
label: 'xyz'
}
]
]
There are a lot of other attributes, but this is the overall structure.
Now my question is can I drill-down & find a specific object. It has unique ids (as shown in the example above)
So I need something like model.findBy(Id)
I then need to change/set some values for that object. Say I want to change the obj.label from 'abc' to 'abc_NEW'
Just to add, The main model is actually a simple JS array...but the inside objects (e.g. those with id: 'Section1_123', etc) are actually Ember objects
Most common approach to work with data in Ember is EmberData. And because the main credo of Ember is "convention over configuration" then a common way in Ember is the best way, in my opinion.
There are many ways how to deal with your data format. I would recommend to create model for each item:
import DS from 'ember-data';
export default DS.Model.extend({
label: DS.attr()
// other properties
});
Then you can make a custom serializer according this article. The goal is to convert your arrays to list of EmberData models.
After this you can use standard EmberData functions to work with data (including access by object id, of course).
I created a simple backbone project, where it fetches all books details and show it in UI. I am fetching all the books details from the model. not at all using collection something like this
var BookModel= Backbone.Model.extend({
initialize: function(){
this.fetchData();
},
fetchData: function(){
this.url= "/get/all_books";
this.fetch({
success: success_callback,
error: error_callback
})
}
});
This is working fine. But why do I have to use collections ? If I would have used collection it would be something like as follows
var BookModel= Backbone.Model.extend({
defaults:{
id:'',
name: '',
author: ''
}
});
var BookCollection= Backbone.Collection.extend({
model: BookModel,
initialize: function(){
this.fetchData();
},
fetchData: function(){
this.url= "/get/all_books";
this.fetch({
success: success_callback,
error: error_callback
})
}
});
The same effect. I don't understand why to use Collections in my case. please help me to understand this concept with my example why do I have to use collections here. I Googled a lot and could find the best answer.
Thanks
Imagine that you have two 2 routes:
/books
/books/:id
Now for getting a specific book you can send a request to /book/:id route, where :id is the id of the book.
GET /books/1
< { id: 1, title: 'On the Genealogy of Morality', ... }
Now what happens if you want to get all the books? You send a request to /books route.
GET /books
< [{ id: 1, title: '...', ... }, { id: 2, title: '...', ... }, ...]
Backbone follows the same principle. Model for a single book. Collection for many books. When you use a collection, Backbone creates one Model for each book. Using a Model for more than one item is wrong.
You said "Backbone creates one Model for each book.". at what step it creates?
It creates the models on the sync event, i.e. when the request for getting all the items is complete.
...how does it helps me. In my case I always fetch all books, not single book.
Backbone Collections always use Backbone Models. If you don't set the model property of the Collection explicitly, Backbone uses a normal Model, i.e. the model property of a Collection should always refer to a Model.
// https://github.com/jashkenas/backbone/blob/master/backbone.js#L782
// Define the Collection's inheritable methods.
_.extend(Collection.prototype, Events, {
// The default model for a collection is just a **Backbone.Model**.
// This should be overridden in most cases.
model: Model,
Consider a Model as an object and a Collection as an array of objects.
More than anything, dividing your data into logical units (models) and grouping them into categories (collections) make it easy for you to reason about, manipulate and change your data. Once you build something that is even just a tiny bit more complex than what you have built, this becomes a priority. The point isn't that you are getting some magic functionality that you couldn't otherwise get. It's all javascript after all. The point is that models and collections provide data-structuring that is helpful when building dynamic applications. In fact that's the whole point of backbone and MV* in general, to provide helpful tools and abstractions. Collections are a solution to a whole host of problems that you will run into later down the road, when you add even the tiniest bit of extra complexity to your app.
So, you ask why you have to use collections and I guess the answer that you already knew is, you don't have to use collections. In fact, it sounds like you don't need to use an MV* library at all.
The Problem
I'm wondering if there's a neat, performant way to bind an angular view to the existence of a particular item in an array. Basically, I have two controllers on one page. Controller A can delete or add items in an array that's injected by a service. I want Controller B, that's injected with the same service, to update its view when I do deletes or adds from Controller A.
The Problem With Lobsters
Say I'm creating a lobster dating site. I'd have two views, side by side:
The hottest lobster view shows a list of the hottest lobsters in the ocean. If any of these lobsters happens to be your friend, its list item will be highlighted and have a message saying that you're friends.
The lobster friends view is on the same page as the hottest lobster view. If I unfriend a lobster from the lobster friend view, (i.e. remove the lobster from the lobsterFriend array), the hottest lobster view should update accordingly, and stop highlighting the unfriended lobster.
I'd like a solution that will work with large numbers of lobsters.
The Setup
Disclaimer: Code in this question is just for illustration purposes. I'm not actually creating a dating site for lobsters.
I have an angular service that I'm injecting into two controllers. The service returns an array of objects.
lobsterFriendService, a service for managing lobster friends:
angular.module('lobsterDating')
.factory('lobsterFriendService', function($http) {
return {
// An array of lobsters
lobsterFriends: $http.get('lobsterApi/lobsterFriends/'),
addLobsterFriend: function (lobster) {
this.lobsterFriends.push(lobster);
$http.post('lobsterApi/lobsterFriends/', lobster);
},
deleteLobsterFriend: function (crustaceanId) {
this.lobsterFriends = this.lobsterFriends.filter(function (lobster) { return lobster.id !== crustaceanId; });
$http.delete('lobsterApi/lobsterFriends/', crustaceanId);
}
}
});
LobsterFriendsCtrl, a controller for the friends list, injected with lobsterFriendService:
angular.module('lobsterDating')
.controller('LobsterFriendsCtrl', ["lobsterFriendService", function(lobsterFriendService) {
$scope.removeFriend = function (lobsterId) {
lobsterFriendService.deleteLobsterFriend(lobsterId);
}
}]);
HottestLobsterCtrl, a controller for the hottest lobster page:
angular.module('lobsterDating')
.controller('HottestLobstersCtrl', ["lobsterFriendService", "hottestLobsters" function(lobsterFriendService, hottestLobsters) {
$scope.model = {
hottestLobsters: hottestLobsters
};
$scope.lobsterIsFriend = function (lobsterId) {
return lobsterFriendService.lobsterFriends.contains(lobsterId);
}
$scope.addLobsterFriend = function (lobster) {
lobsterFriendService.addLobsterFriend(lobster);
}
}]);
hottestLobsters.html, the view bound to HottestLobsterCtrl:
<div ng-repeat="lobster in model.hottestLobsters">
<div class="lobsterFriend" ng-if="lobsterIsFriend(lobster.id)">{{lobster.name}} is your friend ;)</div>
<div class="twoClawsUp" ng-if="lobsterIsFriend(lobster.id)" ng-click="addLobsterFriend(lobster)">Two claws up for {{lobster.name}}!</div>
</div>
Possible Solutions
Set up a watch on the lobsterFriends array. When it updates, we can update some property on the HottestLobsterCtrl controller, trigger a digest, etc. I think this would be rather expensive.
Emit an event whenever a lobster is friended or unfriended. This is a path I'd want to avoid going down if possible, as I'm already injecting a service with the actual object right there.
Use some sort of helper library that handles computed properties nicely.
???? Please help?
What you want to do is save the data in a single obj/array and when you update, simply change the value of that object and array instead of keeping two arrays. You can think of this as a master array. The reason for this is because obj/array gets passed by reference thus it doesn't matter which controller they are in, it will be the same data. Plunker Example
service('dataHolder', function(){
this.data = [{
name : 'Joe',
isFriend: false
},
{
name : 'Michelle',
isFriend: false
},
{
name : 'Adam',
isFriend: true
},
{
name : 'West',
isFriend: false
}]
})
In your example, if you find a way to combine hottestLobsters and LobsterFriends in a single object through a merge and control them through properties then there is no need to have any watch or broadcast . It will simply be like sharing the same scope.
I am getting a little deeper into my first functional app and need to better understand what it going on in my controller.
Here I have a controller that handles the action when a user clicks on an 'Option'. Looking at the this object raises a few questions:
What exactly is this? I would expect it to be an instance of my Option model, but it is missing some properties (like "identity: 'model: Option'").
If this is an instance of my Option model, why is the 'model' property undefined? Why doesn't it just know that?
What is this.content? It looks like some stuff is inside content (id and isSuppressed) and some is not (this.isSelected) - why is that?
Disclaimer: Though there aren't any presenting problems so far, there certainly could be errors in my ember app architecture.
Screenshot debugging controller:
Option Model & Controller
App.Option = Ember.Object.extend({
identity: 'model: Option',
id: '',
cost: '',
isSelected: false,
isSuppressed: false
});
App.OptionController = Ember.Controller.extend({
actions: {
toggleOption: function() {
this.set('isSelected', !this.get('isSelected'));
var id = this.get('content.id');
this.send('deselect', this.get('content.id'));
}
}
});
App.OptionsController = Ember.ArrayController.extend({
actions: {
deselect: function(exception) {
var opts = this.rejectBy('id', exception)
opts.setEach('isSuppressed', true);
}
}
});
It depends where this is, if your in the controller it's the controller. If your controller is an ObjectController/ArrayController it will proxy get/set calls down to the underlying model. content/model are the same thing in the context of the controller.
The properties rarely live directly on the instance, usually they are hidden to discourage accessing the properties without using the getters/setters.
In your code above there is a good chance your OptionController should be extending ObjectController. Unless the controller isn't backed by a model. If you use Ember.Controller.extend then it won't proxy getters/setters down to the model, it will store, retrieve properties from the controller itself.
The server responds with something on the form:
{'dates':
{'2013.05-17':
{'activities':
{'activity 1':
{time: 0, 'synced': false},
'activity 2':
{time: 5, 'synced': false},
'activity 3':...
},...
},
'2013.05-18':
{ ...}, ...},
'id': id}
I currently put everything in a single Backbone model, which doesn't seem like the proper way to do it. The examples I've read around the web all use very simple models where there's no nestled structures and the mapping is pretty simple e.g. {x: 1, y: 2} being mapped to a coordinate model and so on.
What's the "correct" way to map the above JSON structure to Backbones models/collections?
As Protostome mentions, Backbone Relational is good for this kind of thing.
However looking at the JSON data that you have used as an example, from my viewpoint you have only one model and collection as follows:
Activity Model
Activities Collection
Even though you have a nested set of data you could look at it in a different way which gives you a simple backbone model, for example:
var activity = {
id: "Activity 1"
time: 0,
synced: false,
date: "2013.05-17"
batchId: id // this corresponds to the id property in your example
}
This represent all of the data contained in your complex nested hierarchy more simply, and more importantly suited to the way Backbone works.
If you cannot alter what the server provides you, you could use the Underscore library functions (from memory _.map()) to map the JSON you receive into simple JSON objects ready for use with Backbone.
I am hoping that maybe you can simplify your design by thinking outside the the context of sticking with a hierarchy?