I'm a beginner with Backbone.js and i have trouble to make a one to many relationschip between two Backbone models, and showing this data into a html tabel row.
I'm working with a machine that can have multiple orders. the data will look like :
machine 10 has orders 1,2,5,9.
machine 14 has orders 3,4,6.
The machine and orders are coupled with a FK machine_id in order.
I'm trying to make the TR element with the first element TH to be the machine, and on the second/third TH i wanna show the orders that are belong to a machine.
So the questions i ask are :
how do i make this one to many relationship, within my backbone model.
how do i make this TR element within my underscore template to show orders that belong to a machine.
Below are the models for Order and Machine :
app.Machine = Backbone.Model.extend({
defaults : {
machine_id : "",
status : "",
description : "",
},
});
app.Order = Backbone.Model.extend({
defaults: {
order_id: "",
description: "",
amount: "",,
begin_date:"",
end_date:"",
machine_id: ""
},
});
app.MachineList = Backbone.Collection.extend({
model: app.Machine
});
Views:
app.MachineView = Backbone.View.extend({
className: 'MachineRow',
template: _.template($('#Machine_Template').html()),
render: function(){
this.$el.html(this.template(this.model.attributes));
return this;
}
});
app.MachineListView = Backbone.View.extend({
el: '#Machine',
initialize: function(initialMachine){
this.collection = new app.MachineList(initialMachine);
this.render();
},
render: function(){
this.collection.each(function(item){
this.renderMachine(item);
filterOrder(item.get('machine_id'));
}, this);
},
renderMachine: function(item){
var machineView = new app.MachineView({
model: item
});
$(machineView.render().el).appendTo(this.$el)
}
});
HTML code : will look something like this??
<script id="machine_template" type="text/template">
<th>
<%= machine_id %> //etc
</th>
<th>
<%= order_id %> //etc
</th>
<th>
<%= order_id %> //etc
</th>
</script>
Assuming you have a collection of orders called orders, you can do something like:
let renderData = machineList.map(machine -> {
return {
machine: machine,
orders: orders.where({machine_id: machine.get('id')}
}
});
this should give you a structure similar to
[{
machine: machineModel1,
orders: [orderModel1, orderModal2]
},{
machine: machineModel2,
orders: [orderModel3, orderModal4]
}]
You can now pass this to your template function which iterates over each entry and renders it.
I see you already have established one to many relationship by referencing your machine_id in your app.Order model
and as for your 2nd question,
how do i make this TR element within my underscore template to show
orders that belong to a machine.
I don't see the <tr></tr> in your template.
In backbone, template is just a dumb html component where logic is discouraged. All your logic will reside in your views, ideally you may have a collection for your orders and query your orders collection by the machine_id field.
Related
From reading a textbook called "Developing Backbone.js Applications" by Addy Osmani and trying out a practical example on it, I can see that all elements of the Backbone.Collection class can have elements of different types. A class extending Backbone.Collection can also hold different types of elements, like all elements don't have to be consistent.
Let me show you the code I'm using for my example:
var ToDosCollection = new Backbone.Collection();
ToDosCollection.add([
{ title: 'go to Jamaica.', completed: false },
{ task: "aldvjalkdgj", level: 3, timeDue: "6:00 PM Today" }
]);
console.log('Collection size: ' + ToDosCollection.length);
ToDosCollection.reset([
{ slogan: "Curse you, Sephiroth.", population: 5500,},
{ project: "Final Fantasy X" },
{ amount: 500 }
]);
// Above logs 'Collection reset.'
console.log('Collection size: ' + ToDosCollection.length);
alert(JSON.stringify(ToDosCollection.get(0)));
var GitHubRepository = Backbone.Model.extend({
defaults: {
nameOfRepository : "",
owner : "",
members : [],
dateCreated : "",
commits: 0,
additions: 0,
deletions: 0,
},
initialize : function(){
this.on("change", function() {
console.log("CHANGE DETECTED");
});
this.on("change:nameOfRepository", function(){
console.log("The name of the repository has been changed.");
});
this.on("change:owner", function(){
console.log("The owner of the repository has been changed.");
});
this.on("change:members", function(){
console.log("The members this repository belongs to have been changed.");
});
this.on("change:dateCreated", function(){
console.log("The date this repository was created has been changed.");
});
this.on("change:commits", function(){
console.log("The # of commits this repository has have been changed.");
});
this.on("change:additions", function(){
console.log("The # of additions this repository has have been changed.");
});
this.on("change:deletions", function(){
console.log("The # of deletions this repository has have been changed.");
});
}
});
var GitHubRepositoryCollection = Backbone.Collection.extend({
model: GitHubRepository
});
var newCollection = new GitHubRepositoryCollection();
newCollection.add(
{newObject: new GitHubRepository()},
{title: "Rabbits"},
{elementNumber: 5}
);
Am I understanding this correctly? Or, for some insight, how does this actually work? To me, I feel like I am making this analogous to an instance of the ArrayList in Java that supports Object objects.
EDIT: Considering that I have gotten at least one negative vote on this question, I would like to ask, "How does ItemView in Backbone.js work?"
In order to render itemview for each model in the collection i guess it is better to keep each model analogous.Otherwise you will have to build itemview for each model differently which will increase you overheads to write code which is not good because we are supposed to use backbone in order to render the views which are to be rendered for same data in same way.if you want to render different itemview it is always better to have different composite or collection views which will automate your task of rendering the itemview which eventually will reduce your overheads.
Coming to Ember from Rails, one of the places I'm struggling is trying to figure out Ember's definitions of models, views, and controllers.
I'm just testing out some sample Ember code. I'm getting my user events via the GitHub API, and I want to change the type name into something readable.
I have a jsbin here, but here's the gist:
App = Ember.Application.create();
App.IndexRoute = Ember.Route.extend({
model: function(){
return Ember.$.getJSON('https://api.github.com/users/thenickcox/events').then(function(data){
return data.splice(0,7);
});
}
});
I have a method that types a type and returns a string:
interpretType: function(type){
if (type === 'PushEvent') {
return 'Pushed';
}
return name;
}
In Rails, this would go on the model. But the only model here is the one that Ember created in memory by default (right?). So then I thought, it's something that each member of the array needs, because here's the view:
<h3> Some events</h3>
<ul>
{{#each}}
<li>I {{interpretType(type)}} to {{repo.name}}</li>
{{/each}}
</ul>
So is that something that goes on Ember.ArrayController? I tried that, like this:
App.IndexController = Ember.ArrayController.extend({
interpretType: function(type){
if (type === 'PushEvent') {
return 'Pushed';
}
return name;
}.property()
});
That just gave me an error. Where do I put this?
PS. So you don't have to look at the GitHub API, here's an example JSON object:
{
id: "1890853674",
type: "CreateEvent",
actor: {
id: 702327,
login: "thenickcox",
gravatar_id: "63f35d9e50dfd73281126b051a51668a",
url: "https://api.github.com/users/thenickcox",
avatar_url: "https://2.gravatar.com/avatar/63f35d9e50dfd73281126b051a51668a?d=https%3A%2F%2Fa248.e.akamai.net%2Fassets.github.com%2Fimages%2Fgravatars%2Fgravatar-user-420.png&r=x"
},
repo: {
id: 14463966,
name: "thenickcox/whiskey_taster",
url: "https://api.github.com/repos/thenickcox/whiskey_taster"
},
payload: {
ref: "master",
ref_type: "branch",
master_branch: "master",
description: "My first ember/rails app"
},
public: true,
created_at: "2013-11-17T09:00:17Z"
},
Here is an updated JSBin
Basically, the each can specify an itemController to decorate the model.
App.EventController = Ember.ObjectController.extend({
interpretType: function(){
var type = this.get('model.type');
if (type === 'PushEvent') {
type = 'Pushed';
}
return type;
}.property('model.type')
});
Handlebars doesn't have functions as you've written it, but since we are now using the event controller which wraps the single model, we just refer to interpretType to do the translation:
{{#each itemController='event'}}
<li>{{interpretType}} to {{repo.name}}</li>
{{/each}}
Put it inside an Ember.ObjectController
ArrayController's deal with methods related to the collection of data from the model, whereas ObjectController deals with methods related to the specific object.
I'm also learning Ember from a Rails background.
If you haven't already come across this, you will definetely want to check out ember-tools, it's a command line generator very similar to what we've got in rails. I cant imagine building an Ember app without something like it..
So I have the following setup.
On the main page, a list of generators is being displayed based on a list coming from a model using fixture data.
Now when one of the generator links is clicked, a new page is shown with some input fields that are dynamically generated based on that fixture data.
Until this point everything works perfectly.
Now when I change the input field's value in the generator page (after selecting one of the generators) to see the changes being updated in some sort of a preview div just below my input fields, it is easy. I can use {{generatorFields.0.value}} to bind the first input field, .1., and so on until I bind all of them.
But as you can imagine, each generator has its own format and its own input fields, and I want to create a new .hbs file for each and every one of them and then pass that file into the generator page to show the preview.
I solved 0.1% of the problem with a partial. In the generator.hbs file I entered {{partial "generator-1"}} and this loads my _generator-3.hbs file that contains that {{generatorFields.0.value}} bind, and it works. But that partial is not dynamic; I need to load a different partial each time I use a different generator. How can I achieve this?
How can I pass the partial name dynamically or load a template based on the model data that I have?
The code used so far is below:
idex.hbs looks like this:
<table class="table table-hover">
<thead>
<tr>
<th>#</th>
<th>Generator name</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{{#each model}}
<tr>
<td>{{id}}</td>
<td>{{title}}</td>
<td>{{#linkTo 'generator' this classNames="btn btn-mini pull-right"}}Create file{{/linkTo}}</td>
</tr>
{{/each}}
</tbody>
</table>
generator.hbs
{{#each generatorFields}}
<div class="row-fluid">
<div class="span4">{{name}}</div>
<div class="span8">{{view Ember.TextField valueBinding='value' class='span12' placeholder='Type value here…'}}</div>
</div>
{{/each}}
{{partial "generator-1"}}
_generator-1.hbs
<h1>Project: {{generatorFields.0.value}}</h1>
app.js
App.Store = DS.Store.extend({
revision: 13,
adapter: 'DS.FixtureAdapter'
});
App.Router.map(function () {
this.resource('index', { path: '/' });
this.resource('generator', {path: '/generator/:generator_id'});
});
App.IndexRoute = Ember.Route.extend({
model: function () {
return App.Generator.find();
}
});
App.Generator = DS.Model.extend({
title: DS.attr('string'),
templateName: DS.attr('string'),
generatorFields: DS.attr('generatorFields')
});
// Fixture data
DS.RESTAdapter.registerTransform('generatorFields', {
serialize: function(serialized) {
return Em.none(serialized) ? {} : serialized;
},
deserialize: function(deserialized) {
return Em.none(deserialized) ? {} : deserialized;
}
});
App.Generator.FIXTURES = [{
id: 1,
title: "test 1",
generatorFields: [
{id: 1, name: "name 1", value: ""}
],
templateName: "generator-1"
}, {
id: 2,
title: "test 2",
generatorFields: [
{id: 1, name: "name 1", value: ""},
{id: 2, name: "name 2", value: ""},
],
templateName: "generator-2"
}];
You can create a dynamic partial helper that uses the passed in name to render with the {{partial}} helper.
Ember.Handlebars.helper('dynPartial', function(name, options) {
return Ember.Handlebars.helpers.partial.apply(this, arguments);
});
Then use this dynamic partial, {{dynPartial}} instead.
{{#each item in controller}}
{{dynPartial item.templateName}}
{{/each}}
For a generator with templateName of generator-1. This would render with the partial _generator-1. Note that the name of the template's id/data-template-name must begin with an underscore.
You should be able to simply place your dynamic partial variable within the partial helper.
{{#each item in controller}}
{{partial item.templateName}}
{{/each}}
As #darshan-sawardekar pointed out if you have a generator with templateName of generator-1it would render the partial _generator-1.
While #Darshan's answer is simpler than the below and will work in many cases, I just ran into an issue where transitioning to a same route with a different model causes the partial to not re-render if the second model's partial name is the same as the first's (bug in ember?). Setting up a view that watches the model fixes this.
App.FooDynamicLayout = Ember.View.extend
rerenderOnModelChange: (->
#rerender()
).observes('model')
And call it with:
view App.FooDynamicLayout templateName=dynamicTemplateName model=model
#KamrenZ already mentioned this but I figured I'd cite chapter and verse for those looking into this. More recent versions of Ember gracefully accept bound property names and use them in the partial helper:
http://ember-doc.com/classes/Ember.Handlebars.helpers.html#method_partial
BOUND TEMPLATE NAMES The parameter supplied to partial can also be a
path to a property containing a template name, e.g.:
{{partial someTemplateName}}
The above example will look up the value of someTemplateName on the
template context (e.g. a controller) and use that value as the name of
the template to render. If the resolved value is falsy, nothing will
be rendered. If someTemplateName changes, the partial will be
re-rendered using the new template name.
I'm developing my first EmberJS app after following some tutorials as practice. It simply contains a list of 'tables', 'columns', and 'rows' similar to a database.
Link to the problematic page: http://www.kangarooelectronics.com/fakeDB/#/tables/edit/2
My issue is that when I go to remove a column I get:
Object # has no method 'deleteRecord'
As I understand this is due to the object I'm iterating through having no references to the controller because of the way I am constructing the array that I use to create my list.
Removing tables works fine, which are listed in the following fashion:
{{#each model itemController='TableList'}}
<a {{action removeTable this}}>Delete</a>
{{/each}}
I'm iterating through the columns via:
{{#each column in currentColumns itemController='TablesEdit'}}
<a {{action removeColumn column}}>Drop</a>
{{/each}}
Snippet from FIXTURES object:
FakeDB.Table.FIXTURES = [
{
id: 1,
name: 'Users',
columns: {
1:{'colId':1, 'name':'name'},
2:{'colId':2, 'name':'favorite color'},
3:{'colId':3, 'name':'phone number'}
},
// ...snip... //
I am getting 'currentColumns' via:
FakeDB.Table = DS.Model.extend({
name: DS.attr('string'),
columns: DS.attr('object'),
rows: DS.attr('object'),
currentColumns: function() {
var newColumns = $.map(this.get('columns'), function(k, v) {
return [k];
});
return newColumns;
}.property('columns'),
// ..snip.. //
Here you can see my problem... it's obvious that my 'column' isn't going to have any methods from my controller. I tried something like this:
FakeDB.Adapter = DS.FixtureAdapter.extend();
FakeDB.Adapter.map('FakeDB.Table', {
columns: {embedded: 'load'},
rows: {embedded: 'load'}
});
FakeDB.Columns = DS.Model.extend({
colId: DS.attr('integer'),
name: DS.attr('string')
});
FakeDB.Rows = DS.Model.extend({
colId: DS.attr('integer'),
name: DS.attr('string')
});
But I couldn't get {{#each column in columns}} to work with that.
Any suggestions? I'm going to read the docs again and will post back if I find a solution.
Thanks!
EDIT:
So I think I found another solution, but I'm still running into a little issue.
FakeDB.Table = DS.Model.extend({
name: DS.attr('string'),
columns: FakeDB.Columns.find().filter(function(item, index, self) {
if(item.tableID == 1) { return true; }
})
});
Still not sure what to replace 'item.tableID == 1' with so that I get items with the tableID referencing to the current page...
Columns are structured as...
FakeDB.Columns.FIXTURES = [
{
id: 1,
tableID: 1,
name: 'name'
},
// ...snip... //
But now I get:
assertion failed: Your application does not have a 'Store' property defined. Attempts to call 'find' on model classes will fail. Please provide one as with 'YourAppName.Store = DS.Store.extend()'
I am in fact defining a 'Store' property...
I'm developing my first EmberJS app after following some tutorials as practice. It simply contains a list of 'tables', 'columns', and 'rows' similar to a database.
Most databases do contain a list of tables, rows and columns. Most web applications contain a fixed set of tables with pre-defined columns and a dynamic list of rows. If this is your first ember app i would recommend starting with something that keeps you on the happy path.
I am in fact defining a 'Store' property...
True but ember is complaining because store is not available before ember app is initialized. Anything that accesses the store should be in a framework hook of some kind. It can't be used when defining your objects, which wouldn't make a lot of sense anyway.
Probably what you meant to do was make a computed property called columns like this:
FakeDB.Table = DS.Model.extend({
name: DS.attr('string'),
columns: function() {
FakeDB.Columns.find().filter(function(item, index, self) {
if(item.tableID == 1) { return true; }
})
}.property('')
});
I have the following model:
App.Checklist = DS.Model.extend({
name: DS.attr('string'),
checkitems: DS.hasMany('App.Checkitem', { embedded: true }),
remainingItemsCount: function() {
var checkitemsToCount = this.get('checkitems');
return checkitemsToCount.filterProperty('isDone', false).get('length');
}.property()
});
I want to display a list of checklists, with a count of the current checkitems remaining open for each list.
If I drop the following into a template, I get the correct output:
{{#each checklists}}
{{this.name}}
{{this.remainingItemsCount}}
{{/each}}
However, if a new checkitem is added to a checklist, the count does not go up.
BUT, if I change the remainingItemsCount computed property in the Checklist model so that it depends on checkitems.#each.done, then the count increments as new checkitems are added.
The problem is that once this dependency is added, the collection of child checkitems is wrong - it keeps repeating the first checkitem for the number of total checkitems (i.e,. if there are five items for which 'isDone' is false, and four for which 'isDone' is true, then the list count will appear as 9, and the first checkitem will be repeated 9 times).
What am I doing wrong?
UPDATE:
It turns out that adding the dependency to the remainingItemsCount property is causing ember-data to make a new call to the server.
Without the dependency, the following XHR requests are made upon page load:
GET http://localhost:3000/checklists
With the dependency, the following XHR requests are made upon page load:
GET http://localhost:3000/checklists
GET http://localhost:3000/checkitems
The last request comes with the following parameters, which seem to be a representation of the first checkitem, wrapped in an "ids" hash:
{"ids"=>
{"0"=>
{"id"=>"182",
"checklist_id"=>"4",
"title"=>
"Make sure list count automatically increments",
"is_done"=>"false"}},
"action"=>"index",
"controller"=>"checkitems"}
I wonder if this is because the checkitem model is defined with a belongsTo attribute?
App.Checkitem = DS.Model.extend({
title: DS.attr('string'),
isDone: DS.attr('boolean'),
checklist: DS.belongsTo('App.Checklist')
});
UPDATE 2
I'm still not certain why, but it's clear that adding the dependency to the property as follows...
remainingItemsCount: function() {
var checkitemsToCount = this.get('checkitems');
return checkitemsToCount.filterProperty('isDone', false).length;
}.property('checkitems.#each.isDone').cacheable()
...causes ember-data's built-in DS.RESTAdapter to call findMany. The findMany request should take an array of ids, but instead an array containing one entire checkitem object nested inside a hash with the key 0 is being passed to it.
SOLUTION
In the end, I traced the problem to the following observer deep inside ember-data:
dataDidChange: Ember.observer(function() {
var associations = get(this.constructor, 'associationsByName'),
data = get(this, 'data'), store = get(this, 'store'),
idToClientId = store.idToClientId,
cachedValue;
associations.forEach(function(name, association) {
if (association.kind === 'hasMany') {
cachedValue = this.cacheFor(name);
if (cachedValue) {
var ids = data.get(name) || [];
var clientIds = Ember.ArrayUtils.map(ids, function(id) {
return store.clientIdForId(association.type, id);
});
set(cachedValue, 'content', Ember.A(clientIds));
cachedValue.fetch();
}
}
}, this);
}, 'data')
By the time that observer got to the line return store.clientIdForId(association.type, id), the array ids was an array of checkitem objects, not an array of id integers. The fix was pretty simple: return store.clientIdForId(association.type, id.id) returns an array of id integers.
I created a JSFiddle from your description and couldn't reproduce your problem. I'm using Ember.js 0.9.6 and the latest build of ember-data, see http://jsfiddle.net/pangratz666/dGjyR/
Handlebars:
<script type="text/x-handlebars" data-template-name="checklist" >
{{#each checklists}}
{{this.name}}
remaining: {{this.remainingItemsCount}}
{{#each checkitems}}
{{view Ember.Checkbox valueBinding="isDone"}}
{{/each}}
<a {{action "addCheckitem"}} class="clickable">add item</a>
<hr/>
{{/each}}
</script>
JavaScript:
App = Ember.Application.create({});
App.Checkitem = DS.Model.extend({
isDone: DS.attr('boolean')
});
App.Checklist = DS.Model.extend({
name: DS.attr('string'),
checkitems: DS.hasMany('App.Checkitem', {
embedded: true
}),
remainingItemsCount: function() {
var checkitemsToCount = this.get('checkitems');
return checkitemsToCount.filterProperty('isDone', false).get('length');
}.property('checkitems.#each.isDone').cacheable()
});
App.store = DS.Store.create({
revision: 4
});
App.checklistsController = Ember.ArrayProxy.create({
content: App.store.find(App.Checklist)
});
Ember.View.create({
templateName: 'checklist',
checklistsBinding: 'App.checklistsController',
addCheckitem: function(evt) {
var checklist = evt.context;
checklist.get('checkitems').addObject(App.Checkitem.createRecord({
isDone: false
}));
}
}).append();
var checklist = App.Checklist.createRecord({
name: 'firstChecklist'
});
App.Checklist.createRecord({
name: 'secondChecklist'
});
checklist.get('checkitems').addObject(App.Checkitem.createRecord({
isDone: false
}));
checklist.get('checkitems').addObject(App.Checkitem.createRecord({
isDone: true
}));
checklist.get('checkitems').addObject(App.Checkitem.createRecord({
isDone: true
}));