Below is a very simple app which takes the model object from controllers and shows using handle bars. I am unable to access properties that is nested inside a property like {{foobar.foo.bar}}.
JavaScript
app = Ember.Application.create({
rootElement: '#foo'
});
app.IndexRoute = Ember.Route.extend({
setupController: function(controller) {
controller.set('foobar', foobar);
}
});
foobar = {
name: 'item name',
foo: {
bar: 'value',
id: 1
}
};
HTML/Handlebars
<div id="foo">
<p>Name: {{foobar.name}}</p>
<p>Name: {{foobar.foo.bar}}</p>
</div>
{{foobar.name}} works while {{foobar.foo.bar}} does not. Any ideas?
Your code looks alright to me. I tried in in a jsfiddle (you can play with it if you like) and everything's fine. You might have mispell a property or something like that.
Is it your real code or a simplification for StackOverflow purpose ?
In any case, for this kind of situation, I strongly encourage you to install the Emberjs Inspector Extension, which will help you target your issue by taking a look at each property.
Template :
<h2>Ember.js DEMO</h2>
<script id="index" type="text/x-handlebars">
INTERNAL : <br/>
{{foobar}} <br/>
{{foobar.name}} <br/>
{{foobar.foo}} <br/>
{{foobar.foo.bar}} <br/>
{{foobar.foo.id}} <br/><br/>
EXTERNAL : <br/>
{{foobarExt}} <br/>
{{foobarExt.name}} <br/>
{{foobarExt.foo}} <br/>
{{foobarExt.foo.bar}} <br/>
{{foobarExt.foo.id}} <br/>
</script>
Javascript :
App = Ember.Application.create();
App.Router.map(function () {
// put your routes here
});
App.IndexRoute = Ember.Route.extend({
setupController: function(controller) {
controller.set('foobar', {
name: 'item name',
foo: {
bar: 'value',
id: 1
}
});
controller.set('foobarExt', externalfoobar);
}
});
var externalfoobar = {
name: 'item name',
foo: {
bar: 'value',
id: 1
}
};
Related
I am new to angular
in the following controller i need to access the object store in my html. But it is not working. Any help
(function () {
'use strict';
angular.module('app').controller('BookController', ['$scope', function ($scope) {
$scope.book = {
id: 1,
name: 'Harry Potter',
author: 'J. K. Rowling',
stores: [
{ id: 1, name: 'Barnes & Noble', quantity: 3 },
{ id: 2, name: 'Waterstones', quantity: 2 },
{ id: 3, name: 'Book Depository', quantity: 5 }
]
};
}]);
});
<div ng-controller="BookController">
{{book.stores}}
</div>
You need to first invoke your anonymous function first using () after the final closing bracket and before the final semi-colon so that the last line looks like this: })();.
You should define angular module first and then amend it with the angular component like controller, service , factory, directive, filters, etc.
angular.module('app', [])
then add ng-app="app" on your page.
Markup
<div ng-app="app" ng-controller="BookController">
{{book.stores}}
</div>
Plunkr Here
Update
If suppose you have multiple store inside the stores object, and you want to show them on the html, then for that you could ng-repeat directive. It will repeat each element on html
<div ng-repeat="s in book.stores">
<span>{{s.name}}</span>
<input type="text" ng-model="s.name" />
<input type="numeric" ng-model="s.quantity" />
</div>
Updated Plunkr
I have a model, which will be related to a number of other models. Think of a stack overflow question, for example, where it is a question related to tags. The final Object might look as follows before a POST or a PUT:
{
id: 28329332,
title: "checkboxes that append to a model in Angular.js",
tags: [{
id: 5678,
name: "angularjs"
}, {
id: 890,
name: "JavaScript"
}]
}
So far, I have the following controller:
.controller('CreateQuestionCtrl',
function($scope, $location, Question, Tag) {
$scope.question = new Question();
$scope.page = 1;
$scope.getTags = function() {
Tag.query({ page: $scope.page }, function(data) {
$scope.tags = data;
}, function(err) {
// to do, error when they try to use a page that doesn't exist
})
};
$scope.create = function() {
$scope.question.$save(function(data) {
$location.path("/question/" + data.id);
});
};
$scope.$watch($scope.page, $scope.getTags);
}
)
So I display all of the tags, paginated, on the page. I want them to be able to select the given tags and append it to my model so that it can be saved.
How can I create a checkbox interface where it updates the $scope.question with the selected other models?
EDIT: think I might be part of the way there
<div class="checkbox" ng-repeat="tag in tags.objects">
<label><input
type="checkbox"
ng-change="setTag(tag.id)"
ng-model="tag"
> {{ tag.name }}
</div>
Then on the controller
$scope.setTag = function(id) {
Tag.get({id: id}, function(data) {
// don't know what now
})
}
Basically, it takes a directive to approach your goal Take a look at the plunker I wrote for you. As you can see, in the list of selected tags the text property of each tag is displayed, it means that the object structure is kept. In your case, you would bind the $scope.question.tags array as the collection attribute and each tag from the $scope.tags as the element attribute.
Here a codepen for multiple check-boxes bound to the same model.
HTML
<html ng-app="codePen" >
<head>
<meta charset="utf-8">
<title>AngularJS Multiple Checkboxes</title>
</head>
<body>
<div ng:controller="MainCtrl">
<label ng-repeat="tag in model.tags">
<input type="checkbox" ng-model="tag.enabled" ng-change="onChecked()"> {{tag.name}}
</label>
<p>tags: {{model.tags}}</p>
<p> checkCount: {{counter}} </p>
</body>
</html>
JS
var app = angular.module('codePen', []);
app.controller('MainCtrl', function($scope){
$scope.model = { id: 28329332,
title: "checkboxes that append to a model in Angular.js",
tags: [{
id: 5678,
name: "angularjs",
enabled: false
}, {
id: 890,
name: "JavaScript",
enabled: true
}]
};
$scope.counter = 0;
$scope.onChecked = function (){
$scope.counter++;
};
});
I found a great library called checklist-model worth mentioning if anyone is looking up this question. All I had to do was this, more or less:
<div class="checkbox" ng-repeat="tag in tags">
<label>
<input type="checkbox" checklist-model="question.tags" checklist-value="tags"> {{ tag.name }}
</label>
</div>
Found this on googling "directives for angular checkbox".
I've tried to create a very simple example of the issue I'm having with AngularJS. I've got a simple scope called testScope. I also have 2 other scopes (grouped1 and grouped2) that are derived from testScope that have been altered using a grouping function found in UnderscoreJs.
script.js
var app = angular.module('testScope', []);
app.controller('mainCtrl', function($scope) {
$scope.testScope = {
test1: {
data: [
{
field1: 'blah',
field2: 'blah blah'
},
{
field1: 'test',
field2: 'test test'
}
]
}
};
$scope.createEntry = function(newEntry) {
$scope.test1.data.push({field1: newEntry.field1, field2: newEntry.field2});
};
$scope.test1 = $scope.testScope['test1'];
$scope.grouped1 = _.groupBy($scope.test1, 'field1');
$scope.grouped2 = _.groupBy($scope.test1.data, 'field1');
});
index.html
<body ng-app="testScope" ng-controller="mainCtrl">
<form ng-submit="createEntry(newEntry)">
Field1: <input type="text" ng-model="newEntry.field1" />
Field2: <input type="text" ng-model="newEntry.field2" />
<input type="submit" />
</form>
data
<div> {{ test1 }} </div><br>
grouped1
<div>{{ grouped1 }}</div><br>
grouped2
<div>{{ grouped2 }}</div>
</body>
The problem is that when I modify my scope (using the form), test1 and grouped1 will update but grouped2 will not. Why doesn't grouped2 update and how do I get grouped2 to update when the scope changes?
Please see my example:
http://plnkr.co/edit/IN8lADekDBxDp1CNf8VG?p=preview
the reference that .groupBy($scope.test1.data, 'field1') creates changes each time $scope.test1.data changes1. Since $scope works based off of the reference, changing that allows the data to become stale or outdated.
To fix this, you can simply wrap the scope in a function. Such as this:
$scope.grouped2 = function() {return _.groupBy($scope.test1.data, 'field1');};
And then just change your reference in your html like so:
grouped2
<div>{{ grouped2() }}</div>
plunkr: here
I can't for the life of me figure out why this won't work. Maybe it's obvious, but I've been staring at it too long.
I want the {{input}} pre-populated with the value of Title, the label gets filled in correctly...
<ul>
{{#each}}
<li>
{{input type="text" value=Title }}
<label>{{Title}}</label>
</li>
{{/each}}
</ul>
here's a Gist
Here's the javascript:
window.App = Ember.Application.create({
LOG_TRANSITIONS: true
});
App.ApplicationAdapter = DS.FixtureAdapter.extend();
App.Router.map(function () {
this.resource('Kids', { path: '/' });
});
App.KidsRoute = Ember.Route.extend({
model: function () {
return this.store.find('Kid');
}
});
App.Kid = DS.Model.extend({
Title: DS.attr('string'),
Age: DS.attr('number')
});
App.Kid.FIXTURES = [
{
id: 0,
Age: 5,
Title: "Joe"
},
{
id: 1,
Age: 9,
Title: "Max"
}
];
There seems to be a little bug with {{input}}. The problem was that title is capitalized. I got it to work in this bin. I would recommend to start your attribute names always with a small letter. This is some kind of convention. Most often you will encounter this style opposed to yours.
These are the changes i made to get it to work:
1 - add an alias to title, which is not capitalized:
App.Kid = DS.Model.extend({
Title: DS.attr('string'),
Age: DS.attr('number'),
title : Ember.computed.alias("Title")
});
2 - use the uncapitalited version with the helper:
{{input type="text" value=title }}
I'm learning ember these days and I encountered a problem with link-to helper. If I use it to create a link for nested route it works fine (if click on the link, "active" class will be added to the element - as described in docs) until I reload the page. When I reload the page the content for nested rouse will be loaded to the {{outlet}} properly but link will lose its "active" class. What am I doing wrong?
JavaScript:
window.App = Ember.Application.create({ rootElement: '#app' });
App.Router.map(function () {
this.resource('notes', { path: '/' }, function () {
this.route('show', { path: '/:note_id' });
});
});
App.NotesRoute = Em.Route.extend({
model: function () {
return App.Note.find();
}
});
App.NotesShowRoute = Em.Route.extend({
model: function (params) {
return App.Note.find(params.note_id);
}
});
App.Note = Em.Object.extend();
App.Note.reopenClass({
find: function(id) {
var notes = [
{
id: 1,
title: 'abc',
text: 'lorem ipsum text 1111111'
},
{
id: 2,
title: 'def',
text: 'lorem ipsum text 2222222'
}
];
return id ? notes[parseInt(id) - 1] : notes;
}
});
HTML:
<div id="app" class="row">
<script type="text/x-handlebars">
<div class="col-md-2">
<h2>Tags</h2>
</div>
{{outlet}}
</script>
</div>
<script type="text/x-handlebars" data-template-name="notes">
<div class="col-md-3">
<h2>Notes</h2>
{{#each}}
{{#link-to 'notes.show' this}}{{title}}{{/link-to}}
{{/each}}
</div>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="notes/show">
<div class="col-md-7">
<h2>{{title}}</h2>
<p>{{text}}</p>
</div>
</script>
When you click a link-to, it passes the object to the new route. So the model lookup isn't called. So both the context of the show route and the linked object refer to the same object. So it will get marked as active.
However, when you refresh the page, you're doing the lookup twice, once in the NotesRoute model (which you loop over with each), and once in the NotesShowRoute model.
Javascript objects are reference types. Two plain javascript objects aren't considered equal, even if their content is the same. e.g. try typing this into your javascript console.
{ one: 1, two: 2} == {one: 1, two: 2}
So the object referred to in the link-to isn't the same as the model of the current route. So the equality check for the link being active won't work.
Quick solution is to stop the find from creating the object every time. e.g.
App.Note.reopenClass({
all: [
{
id: 1,
title: 'abc',
text: 'lorem ipsum text 1111111'
},
{
id: 2,
title: 'def',
text: 'lorem ipsum text 2222222'
}
],
find: function(id) {
return id ? this.all[parseInt(id) - 1] : this.all;
}
});
Another options is to roll some sort of identity map for your objects. Here is a blog post doing a much better example than I can of explaining it.
Note I haven't actually tested that code because I'm too lazy to create a jsbin. But let me know if it doesn't work.