Exception error when I try to iterate over an array - arrays

I have a collection, Ages, which only holds one key: ages.
In it I store an array of strings like so:
['51', '24', '21', '19', '15']
I struggled a bit with how to iterate over it in the template but this is what I found worked. First the HTML:
<template name="ageFilter">
{{#each age}}
<li>
{{this}}
</li>
{{/each}}
</template>
And the helper:
Template.ageFilter.helpers({
age: function() {
return Ages.findOne().ages
}
})
This is a rather ugly solution that rests on my having only one object in my database (since I use findOne() but it's not a big problem, and it works. The template iterates over the array and outputs it.
There's only one problem: the browser console throws an error!
Exception in template helper: TypeError: Cannot read property 'ages' of undefined
Why is this and how can I get rid of it?

Maybe your age helper is first called before subscription to your Ages publication occurs, therefore the first call(s) to Ages.findOne() end(s) up returning nothing. You could check if your Ages document has been fetched correctly:
Template.ageFilter.helpers({
age: function() {
var ageItem = Ages.findOne();
return (ageItem ? ageItem.ages : null);
}
});
But that's just hacking around the issue. Best approach would be to make sure your template does not get rendered before subscription is done. You could use iron-router and put your subscriptions in waitOn for that. For example:
Router.route('home', {
path: '/',
waitOn: function() {
return Meteor.subscribe('ages');
}
});
Also, overall you might want to change your design and just put one age value per document in your Ages collection, stored in a key like value or something. It seems more logical. This way you could do:
<template name="ageFilter">
{{#each ages}}
<li>
{{value}}
</li>
{{/each}}
</template>
And your helper:
Template.ageFilter.helpers({
ages: function() {
return Ages.find().fetch();
}
});

Related

ReactJS - Listing all keys at once

I'm a beginner of ReactJS and I'm stuck trying to figure out why map only returns a single prop at a time.
In file1.jsx, I have an array that contains 3 objects:
var MainPanelOneData = [{"id":1,"shotviews":15080},{"id":2,"likes":12000},{"id":3,"comments":5100}];
File1.jsx also has a render function to extrapolate the data inside the array:
render: function() {
var ListMainPanelOne = MainPanelOneData.map(function(data) {
return <MainPanelOne key={data.key} shotviews={data.shotviews} likes={data.likes} comments={data.comments} />
});
In file2.jsx, I have this code to render the data object from file1.jsx:
render: function() {
return (
<div>
<span>{this.props.shotviews} shot views</span>
<span>{this.props.likes} likes</span>
<span>{this.props.comments} comments</span>
</div>
)
}
The result shows this:
15080 shot views likes comments
shot views12000 likes comments
shot views likes5100 comments
I'm guessing it repeats like this because it searches through one key at a time? If that's the case, how do I display all keys at the same time? Use indexing?
well your array of data doesnt have all the keys. each one of your objects in PanelOneData has ONE key and is missing the other two; additionally none of them have key called key. so youre making three MainPanelOne components, each with a single prop. the result of that map is this
[
<MainPanelOne key={null} shotviews={15080} likes={null} comments={null} />,
<MainPanelOne key={null} shotviews={null} likes={12000} comments={null} />,
<MainPanelOne key={null} shotviews={null} likes={null} comments={5100} />
]
which is an accurate display of what youre seeing
To get one line you might do something like this.
render: function() {
var ListMainPanelOne = MainPanelOneData.map(function(data) {
return <span key={data.id}> {data.shotviews} {data.likes} {data.comments} </span>
});

Changing the template data not refreshing the elements

I have searched and tried suggestions mentioned in various posts but no luck so far.
Here is my issue.
I have created a custom element <month-view id="month-view-element"></month-view> in my mainpage.html. Inside mainpage.html when this page is initially loaded i created a empty json object for all the 30days of a month and print a placeholder type cards in UI. Using the code below.
var json = [];
for(var x = 0; x < total; x++) {
json.push({'hours': 0, 'day': x+1, 'year': year});
}
monthView.month = json; //Doing this line. Prints out the desired empty cards for me in the UI.
created a month-view.html something like below:
<dom-module id='month-view'>
<template>
<template is="dom-repeat" items= "{{month}}">
<paper-card class="day-paper-card" heading={{item.day}}>
<div class="card-content work">{{item.work}}</div>
<div class="card-actions containerDay layout horizontal">
<div style="display:inline-block" class="icon">
<paper-icon-button icon="icons:done" data-hours = "8" data-day$="{{item.day}}" data-month$={{item.month}} data-year$={{item.year}} on-click="updateWorkHours"></paper-icon-button>
<paper-tooltip>Full day</paper-tooltip>
</div>
</div>
</paper-card>
</template>
</template>
<script>
Polymer({
is: "month-view",
updateWorkHours: function (e, detail) {
console.log(e);
this.fire('updateWorkHour', {day: e.target.dataHost.dataset.day,
month: e.target.dataHost.dataset.month,
year: e.target.dataHost.dataset.year,
hours: e.target.dataHost.dataset.work
});
}
});
</script>
</dom-module>
There is another file script.js which contains the function document.addEventListener('updateWorkHour', function (e) { // doStuff });. I use this function to make a call to a google client API. I created a client request and then do request.execute(handleCallback);
Once this call is passed i landed in handleCallback function. In this function i do some processing of the response data and save parts of data into json variable available in the file already. And once all processing is done i did something like below.
monthView.month = json;
But this above line is not refreshing my UI with the latest data. Is there anything I am missing? Any suggestions or anything i am doing incorrectly.
You need to use 'set' or 'notifyPath' while changing Polymer Object or Arrays in javascript for the databinding/obserers to work. You can read more about it in https://www.polymer-project.org/1.0/docs/devguide/data-binding.html#path-binding
In your case try below code
monthView.set('month',json);
Updated suggestions:
Wrap your script on main page with. This is required for non-chrome browsers.
addEventListener('WebComponentsReady', function() {})
This could be scoping issue. Try executing 'document.querySelector('#month-view-element');' inside your callback addWorkHoursCallBack. Also, Use .notifyPath instead of .set.

Observing nested properties in Ember controller

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")
});

How to get a single item from a GoInstant collection?

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 :)

Ember ArrayController binding to view

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/

Resources