How do you get a single item from a GoInstant GoAngular collection? I am trying to create a typical show or edit screen for a single task, but I cannot get any of the task's data to appear.
Here is my AngularJS controller:
.controller('TaskCtrl', function($scope, $stateParams, $goKey) {
$scope.tasks = $goKey('tasks').$sync();
$scope.tasks.$on('ready', function() {
$scope.task = $scope.tasks.$key($stateParams.taskId);
//$scope.task = $scope.tasks.$key('id-146b1c09a84-000-0'); //I tried this too
});
});
And here is the corresponding AngularJS template:
<div class="card">
<ul class="table-view">
<li class="table-view-cell"><h4>{{ task.name }}</h4></li>
</ul>
</div>
Nothing is rendered with {{ task.name }} or by referencing any of the task's properties. Any help will be greatly appreciated.
You might handle these tasks: (a) retrieving a single item from a collection, and (b) responding to a users direction to change application state differently.
Keep in mind, that a GoAngular model (returned by $sync()) is an object, which in the case of a collection of todos might look something like this:
{
"id-146ce1c6c9e-000-0": { "description": "Destroy the Death Start" },
"id-146ce1c6c9e-000-0": { "description": "Defeat the Emperor" }
}
It will of course, have a number of methods too, those can be easily stripped using the $omit method.
If we wanted to retrieve a single item from a collection that had already been synced, we might do it like this (plunkr):
$scope.todos.$sync();
$scope.todos.$on('ready', function() {
var firstKey = (function (obj) {
for (var firstKey in obj) return firstKey;
})($scope.todos.$omit());
$scope.firstTodo = $scope.todos[firstKey].description;
});
In this example, we synchronize the collection, and once it's ready retrieve the key for the first item in the collection, and assign a reference to that item to $scope.firstTodo.
If we are responding to a users input, we'll need the ID to be passed from the view based on a user's interaction, back to the controller. First we'll update our view:
<li ng-repeat="(id, todo) in todos">
{{ todo.description }}
</li>
Now we know which todo the user want's us to modify, we describe that behavior in our controller:
$scope.todos.$sync();
$scope.whichTask = function(todoId) {
console.log('this one:', $scope.todos[todoId]);
// Remove for fun
$scope.todos.$key(todoId).$remove();
}
Here's a working example: plunkr. Hope this helps :)
Related
I'm having an issue with ngRepeat :
I want to display a list of students in two different ways. In the first one they are filtered by group, and in the second they are not filtered.
The whole display being quite complex, I use a ngInclude with a template to display each student. I can switch between view by changing bClasseVue, each switch being followed by a $scope.$apply().
<div ng-if="currentCours.classesOfGroup !== undefined"
ng-show="bClassesVue">
<div ng-repeat="group in currentCours.classesOfGroup">
<br>
<h2>Classe : [[group.name]]</h2>
<div class="list-view">
<div class="twelve cell"
ng-repeat="eleve in group.eleves | orderBy:'lastName'"
ng-include="'liste_eleves.html'">
</div>
</div>
</div>
</div>
<div class="list-view" ng-show="!bClassesVue">
<div class="twelve cell"
ng-repeat="eleve in currentCours.eleves.all"
ng-include="'liste_eleves.html'">
</div>
</div>
My problem happens when my list of students change (currentCours here). Instead of refreshing the ngRepeat, both lists concatenate, but only in the unfiltered view.
I tried adding some $scope.$apply in strategic places (and I synchronize my list for example) but it doesn't help.
EDIT : the function used to refresh currentCours in the controller. It's called when a "cours" is selected inside a menu.
$scope.selectCours = function (cours) {
$scope.bClassesVue = false;
$scope.currentCours = cours;
$scope.currentCours.eleves.sync().then(() => {
if ($scope.currentCours.classe.type_groupe === 1) {
let _elevesByGroup = _.groupBy($scope.currentCours.eleves.all, function (oEleve) {
return oEleve.className;
});
$scope.currentCours.classesOfGroup = [];
for(let group in _elevesByGroup) {
$scope.currentCours.classesOfGroup.push({
name: group,
eleves: _elevesByGroup[group]
});
}
$scope.bClassesVue = true;
}
});
utils.safeApply($scope);
};
Well, I found a workaround, but I still don't know why it didn't work, so if someone could write an explanation, I would be very thankful.
My solution was simply to open and close the template each time I switch between views.
I am using the AngularJS UI Sortable directive and I am trying to pull the data from my view into my controller and update/stop the sorting on every click. I am creating a blank array and then attaching the $scope.areas to the blank array. I am able to display the content through the ng-repeat. However, when I console.log(areas), I am getting an undefined.
VIEW
<div class="panel-body">
<ul ui-sortable="sortableOptions" ng-model="areas" class="uk-nestable">
<li data-item="{{area.label}}" data-item-id="{{area.order}}" ng-repeat="area in areas">
<div class="uk-nestable-item mainarea-blue">
<div class="uk-nestable-handle mainarea-text-white"></div>
<div data-nestable-action="toggle"></div>
<div class="list-white">{{area.label}}</div>
</div>
</li>
</ul>
</div>
CONTROLLER
//create a blank array
var tmpList = [];
//attaches ng-model scope to tmpList
$scope.areas = tmpList;
console.log(areas);
//changed old sort to new sort
$scope.sortingLog = [];
$scope.sortableOptions = {
//creates a log entry of the new update view
update: function(e, ui) {
var logEntry = tmpList.map(function(i){
return i.value;
}).join(', ');
//displays the update text and array
$scope.sortingLog.push('Update: ' + logEntry);
},
stop: function(e, ui) {
// this callback has the changed model
var logEntry = tmpList.map(function(i){
return i.value;
}).join(', ');
$scope.sortingLog.push('Stop: ' + logEntry);
}
};
My goal is to display the area.label in the correct sort order. For example; I have 3 unordered lists - floor, basement, kitchen and I want to change the order to basement, floor, kitchen. As I am changing the sort order it is updating and stop the sorts.
I am probably not doing the best job explaining myself so here is a similar codepen...http://codepen.io/thgreasi/pen/jlkhr.
I'm in the middle of a small project involving Ember. It's my very first time at working with this framework and it has not been an easy learning so far :(
Right now I'm having troubles dealing with nested arrays. What I want to do is pretty standard (at least it seems that way): I have items, item categories and category types (just a way to organize them).
The idea is that there are checkboxes (categories) that allow me to filter the items that are shown in the webpage. On the other hand, there are checkboxes (types) that allow me to check multiple catgories at a time.
In order to implement this I've defined a route (in which I retrieve all the data from these models) and a controller. Originally, I only had items and categories. In this context, I observe the changes in the filters (categories) like this: categories.#each.isChecked and then show the item selection. Unfortunately, now that the hierarchy is types->categories, is not possible to observe changes in categories in the same manner according to the docs:
Note that #each only works one level deep. You cannot use nested forms like todos.#each.owner.name or todos.#each.owner.#each.name.
I google a little bit but didn't find too much about it, so I right now I was thinking in using a custom view for categories (one that extends the Ember.Checkbox) and send an event to the controller whenever a category is checked or unchecked. Is more of a "manual" work and I guess is far from Ember's way of dealing with this type of things.
Is there a standard way of doing this?
Thanks in advance for any help.
One way of solving this would be to observe the category types and filter categories, the same way that the categories are being observed.
This is an example,
http://emberjs.jsbin.com/naqebijebapa/1/edit
(one to many relationships have been assumed)
hbs
<script type="text/x-handlebars" data-template-name="index">
<ul>
{{#each catType in catTypes}}
<li>{{input type="checkbox" checked=catType.isSelected }}{{catType.id}} - {{catType.name}}</li>
{{/each}}
</ul>
<hr/>
<ul>
{{#each cat in filteredCats}}
<li>{{input type="checkbox" checked=cat.isSelected }}{{cat.id}} - {{cat.name}}</li>
{{/each}}
</ul>
<hr/>
<ul>
{{#each item in filteredItems}}
<li>{{item.id}} - {{item.name}}</li>
{{/each}}
</ul>
</script>
js
App = Ember.Application.create();
App.Router.map(function() {
// put your routes here
});
App.CategoryType = Em.Object.extend({
id:null,
name:null,
isSelected:true
});
App.Category = Em.Object.extend({
id:null,
name:null,
type:null,
isSelected:false
});
var catTypeData = [
App.CategoryType.create({id:1,name:"type1"}),
App.CategoryType.create({id:2,name:"type2"}),
App.CategoryType.create({id:3,name:"type3"}),
App.CategoryType.create({id:4,name:"type4"})
];
var catData = [
App.Category.create({id:1,name:"cat1",type:catTypeData[1]}),
App.Category.create({id:2,name:"cat2",type:catTypeData[2]}),
App.Category.create({id:3,name:"cat3",type:catTypeData[0]})
];
var itemsData = [
{id:1,name:"item1",cat:catData[0]},
{id:2,name:"item2",cat:catData[0]},
{id:3,name:"item3",cat:catData[1]}
];
App.IndexRoute = Ember.Route.extend({
model: function() {
return Em.RSVP.hash({catTypes:catTypeData,cats:catData,items:itemsData});
}
});
App.IndexController = Ember.ObjectController.extend({
filteredItems:[],
filterItemsBasedOnCategory:function(){
var selectedCats = this.get("cats").filterBy("isSelected");
if(!Em.isEmpty(selectedCats))
this.set("filteredItems",this.get("items").filter(function(item){
return selectedCats.contains(item.cat);}));
else
this.set("filteredItems",[]);
}.observes("cats.#each.isSelected"),
filterCatsBasedOnCategoryType:function(){
var selectedCatTypes = this.get("catTypes").filterBy("isSelected");
if(!Em.isEmpty(selectedCatTypes))
this.set("filteredCats",this.get("cats").filter(function(cat){
var itContainsIt = selectedCatTypes.contains(cat.type);
if(!itContainsIt){
cat.set("isSelected",false);
}
return itContainsIt;
}));
else
this.set("filteredCats",[]);
}.observes("catTypes.#each.isSelected")
});
i know in Angular world it is better to bind data than manipulate dom elements. but i can't figure out a way to implement the 'in timeline, click a tweet, load replies, click another tweet load another replies' effects.
here is some code run into my thoughts:
<div class="tweet" ng-repeat="tweet in tweets">
<div class="tweet-content">{{tweet}}</div>
<a class="button" ng-click="loadreplay()">load reply</a>
<div class="reply-container">{{reply}}</div>
</div>
if i write controller like this
app.controller('Test', function($scope){
$scope.tweets = ["foo", "bar"];
$scope.loadreplay = function(){
$scope.reply = "reply";
}
});
then all {{reply}} fields will be filled with 'reply', so in this condition, is manipulate the dom elements the only resolution? or some more "angular" way?
Use a appropriate schema for your data/model. Considering that you would store not only the text but at least something like a ID you would use an object anyway. So think about something like this:
$scope.tweets = [
{ id:1, txt: 'foo' },
{ id:2, txt: 'bar' }
]
Then you could store the individual replies in that object as well:
$scope.loadreply = function(tweet) {
tweet.reply = 'Reply';
}
Note: In this function you could then also use the ID to e.g. fetch the tweets from the server like this:
$scope.loadreply = function(tweet) {
tweet.reply = LoadReplies(tweet.id);
}
You would then use the tweet specific reply attribute for display:
<div ng:repeat="tweet in tweets">
<div>{{tweet.txt}}</div>
<a ng:click="loadreply(tweet)">load reply</a>
<div>{{tweet.reply}}</div>
</div>
See this fiddle for a working demo: http://jsfiddle.net/XnBrp/
I've snipped out the init function which sets up the initials array.
This is an array of arays indexed as "A", "B", "C" etc.
Each of these contains station object that begin with that letter.
I have buttons that fire off setByInitial which copy the relevant initial array into content.
this.content.setObjects(this.initials[initial])
works fine and my view updates, but is horribly slow (150ms +) station objects are pertty big and there are over 3500 of them...
this.set("content",Ember.copy(this.initials[initial],true))
Is much fatser (around 3ms) updated the content aray (as can be seen with some logging to console), but does not cause the view to update.
this.set("content",this.initials[initial])
is even faster, but also does not update the view.
I've tried using arrayContentDidChange() etc. but can't get that to work either.
How do I inform the view that this dfata has changed if I use the faster method? Or is there another wau to do this?
App.StationListController = Ember.ObjectController.extend({
content : [],
initials : [],
setByInitial : function(initial)
{
// this.content.setObjects(this.initials[initial])
this.set("content",Ember.copy(this.initials[initial],true))
}
});
<script type="text/x-handlebars" id="stationList">
<ul>
{{#each content}}
<li>{{#linkTo "station" u}}{{n}}{{/linkTo}}</li>
{{/each}}
</ul>
</script>
Thanks to #mike-grassotti example I can see that what I was doing ought to work, but it still doesn't! As is often the case, what I have posted here is a simplification. My real app is not so straight forward...
My index template contain several views. Each view has it's own data and controller. So it seems it's something in that complexity which is breaking it. So, I've started with Mike's example and added just a little - in order to move towards what I really want - and promptly broken it!
I now have:
var App
= Ember.Application.create({})
App.Router.map(function() {
this.resource('index', {path: '/'});
this.resource('station', {path: '/:code/:name'});
this.resource('toc', {path: '/toc/:code/:name'});
});
App.Station = Ember.Object.extend({});
App.IndexController = Ember.ObjectController.extend({
needs : ["StationList"],
listStationsByInitial: function(initial)
{
this.get("controllers.StationList").listByInitial(initial)
}
});
App.StationListView = Em.View.extend({
stationsBinding : 'App.StationListController',
init : function()
{
console.log("view created",this.stations)
}
});
App.StationListController = Ember.ArrayController.extend({
content : [],
initials : [[{u:1,n:'Abe'},{u:2,n:'Ace'}],[{u:3, n:'Barb'},{u:4,n:'Bob'}],[{u:5,n:'Card'},{u:6,n:'Crud'}]],
init : function()
{
this.set("content",this.initials[0])
},
listByInitial : function(initial)
{
this.set("content",this.initials[initial])
console.log('StationListController',this.content);
}
});
and
t type="text/x-handlebars" data-template-name="application">
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="index">
<button {{action listStationsByInitial '0'}}>A</button>
<button {{action listStationsByInitial '1'}}>B</button>
{{#view App.StationListView controllerBinding="App.StationListController"}}
<ul>
{{#each stations}}
<li>{{#linkTo "station" u}}{{n}}{{/linkTo}}</li>
{{else}}
<li>No matching stations</li>
{{/each}}
</ul>
<button {{action listByInitial '2'}}>C</button>
{{/view}}}
</script>
Firstly, I no longer see the list of station rendered. Neither initially, nor on the click of a button.
I expected {{#with content}} to get the data from App.StationListController.content, but that didn't work. So, I created App.StationListView with a binding stationsBinding to that controller. Still no joy...
What am I doing wrong here?
Secondly, my function listStationsByInitial is called when I click button A or B. So I'd expect listByInitial (in StationListController) to be called when I click button C (since it's inside of the view where I've said to use StationListController). But instead I get an error:
error: assertion failed: The action 'listByInitial' did not exist on App.StationListController
Why doesn't that work?
I'm doubly frustrated here because I have already build a pretty large and complex Ember app (http://rail.dev.hazardousfrog.com/train-tickets) using 1.0.pre version and am now trying to bring my konwledge up-to-date with the latest version and finding that almost nothing I learned applies any more!
How do I inform the view that this dfata has changed if I use the faster method?
You should not have to inform the view, this is taken care of via bindings. I can't see anything in your example that would prevent bindings from updating automatically, and made a simple jsFiddle to demonstrate. Given the following, the list of stations is modified when user hits one of the buttons and view updates automatically:
App = Ember.Application.create({});
App.Router.map( function() {
this.route('stationList', {path: '/'});
this.route('station', {path: '/station/:station_id'});
});
App.Station = Ember.Object.extend({});
App.StationListController = Ember.ObjectController.extend({
content : [],
initials : [
[{u:1,n:'Abe'}, {u:2,n:'Ace'}], [{u:1,n:'Barb'}, {u:2,n:'Bob'}]
],
setByInitial : function(initial)
{
console.log('setByInitial', initial);
this.set("content",this.initials[initial])
}
});
<script type="text/x-handlebars" id="stationList">
<h2>Stations:</h2>
<button {{action setByInitial '0'}}>A</button>
<button {{action setByInitial '1'}}>B</button>
<ul>
{{#each content}}
<li>{{#linkTo "station" u}}{{n}}{{/linkTo}}</li>
{{/each}}
</ul>
</script>
See: http://jsfiddle.net/mgrassotti/7f4w7/1/