Parsing ember-leaflet coordinates from json using ember models - javascript

I am new to ember and ember-leaflet.js. I am trying to feed data (via an ajax call to a json file) to both my handlebars template and my ember-leaflet map. With my current setup, the data reaches my handlebars template just fine, but doesn't render the coordinates data to the ember-leaflet map.
I am using the two examples listed below as my guides, but have hit a wall because of my lack of experience with ember. Can anyone point me in the right direction please?
Ajax and ember example
Partial example of what I'm trying to accomplish
Handlebars template:
<script type="text/x-handlebars" data-template-name="application">
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="index">
{{view App.MapView id="map"}}
<div id="blog">
<ul>
{{#each item in model}}
<li>{{item.headline}}</li>
{{/each}}
</ul>
</div>
</script>
Ember:
App = Ember.Application.create();
App.Router.map(function() {
// put your routes here
});
App.IndexRoute = Ember.Route.extend({
model: function(){
return App.Item.all();
}
});
App.Item = Ember.Object.extend();
App.Item.reopenClass({
all: function() {
return $.getJSON("js/data/test_e.json").then(function(response) {
var items = [];
response.features.forEach( function (data) {
items.push( App.Item.create(data) );
});
return items;
});
}
});
App.MarkerCollectionLayer =
EmberLeaflet.MarkerCollectionLayer.extend({
locationBinding: 'controller.item.center'});
App.MapView = EmberLeaflet.MapView.extend({
childLayers: [
EmberLeaflet.DefaultTileLayer,
App.MarkerCollectionLayer]
});
App.IndexController =
Ember.Controller.extend({});
JSON file:
{
"features": [
{
"headline": "Docker, the Linux container runtime: now open-source",
"center" : [40.714, -74.000]
},
{
"headline": "What's Actually Wrong with Yahoo's Purchase of Summly",
"center" : [40.714, -73.989]
}
]
}

The main fix needed here is the locationBinding in the MarkerCollectionLayer. The location binding needs to be in the MarkerLayer class. Furthermore, you need to use the EmberLeaflet.computed functions to convert simple lat lng arrays to a Leaflet LatLng object. See this example:
App.MarkerCollectionLayer = EmberLeaflet.MarkerCollectionLayer.extend({
content: Ember.computed.alias('controller'),
itemLayerClass: EmberLeaflet.MarkerLayer.extend({
location: EmberLeaflet.computed.latLngFromLatLngArray('content.center'),
})
});
Check out this JSFiddle with a full working example: http://jsfiddle.net/xALu4/2/

Related

Can autorun's reactive computation be dependent on part of a collection?

I'd like to create a template in Meteor that has a Tracker.autorun which exclusively runs when part of a document changes --- but not when other parts of the document change.
So here is sample code using a minimongo collection and template.autorun
parent.html
{{#each items}}
{{> child}}
{{/each}}
child.html
<div>{{title}}</div>
<p>{{description}}</p>
Minimongo Collection
LocalProject.findOne() output:
"items": [
{
"title": "hi",
"description": "test"
},
{
"title": "hi 2",
"description": "test 2"
},
],
"otherstuff:{//etc}
child.js
Template.child.onRendered(function(){
this.autorun(function() {
var data = Template.currentData();
doSomething(data.title,data.description)
});
});
addnewitem.js
LocalProject.update(currentEditingProjectID,{ $push: { 'items': newItem }},function(error,result){
if(error){console.log(error)}
});
The problem is, whenever I run addnewitem.js, all of my Template.child autoruns execute even though their reactive data source (Template.currentData()) has not changed unless it was the specific item I updated. Similarly if I want to update an existing item, not just add a new one to the array, all of the autoruns for each item get executed.
So is there a way, using this model, to create a dependency for autorun that is reactively granular to specific portions of a document?
I don't think the way to go is by using an autorun. I would either set up individual reactive dependencies on each item, or use observe/observeChange.
First idea
parent.html:
{{#each items}}
{{> child}}
{{/each}}
parent.js:
Template.parent.helpers({
// Returns only item ids
items: function() {
return Items.find({}, { fields: { _id: 1 } });
}
});
child.html:
{{#each items}}
{{#with item=getItem}}
<div>{{item.title}}</div>
<p>{{item.description}}</p>
{{/with}}
{{/each}}
child.js:
Template.child.helpers({
getItem: function() {
// Get the item and set up a reactive dependency on this particular item
var item = Items.find(this._id);
// After item has been displayed, do something with the dom
Tracker.afterFlush(function () {
doSomething(item.title, item.description);
});
return item;
}
});
Second idea
parent.html:
{{#each items}}
{{> child}}
{{/each}}
parent.js:
function do(item) {
Tracker.afterFlush(function () {
doSomething(item.title, item.description);
});
}
Template.parent.onCreated({
this.items = Items.find({});
this.handle = this.items.observe({
added: function(item) { do(item); },
changed: function(oldItem, newItem) { do(newItem); },
});
});
Template.parent.onDestroyed({
this.handle.stop();
});
Template.parent.helpers({
items: function() {
return Template.instance().items;
}
});
child.html:
{{#each items}}
<div>{{title}}</div>
<p>{{description}}</p>
{{/each}}
There's a tool just for this - 3stack:embox-value provides reactive isolation, and value caching.
Using your example, you could isolate changes to title/description like so:
first up, add the packages
meteor add 3stack:embox-value
meteor add ejson
Then, update your code:
Template.child.onRendered(function(){
// creates a reactive data source, that only triggers "changes"
// when the output of the function changes.
var isolatedData = this.emboxValue(function(){
var data = Template.currentData();
return {title: data.title, description: data.description}
}, {equals: EJSON.equals})
this.autorun(function() {
// This autorun will only execute when title or description changes.
var data = isolatedData()
doSomething(data.title,data.description)
});
});

Error when rendering 2 models for a template in Ember.js

I have a template in wish I have 2 components that represents different Models. I need to create a model that contains both datas, for each component. If I have only one model everything works fine, but when I add other one, then this error happens in the console.
Error while processing route: events record is undefined ember$data$lib$system$store$finders$$_find/</<#http://localhost:1361/test/frontend/bower_components/ember-data/ember-data.prod.js:7475:11
Backburner.prototype.run#http://localhost:1361/test/frontend/bower_components/ember/ember.prod.js:222:18
ember$data$lib$system$store$$Store<._adapterRun#http://localhost:1361/test/frontend/bower_components/ember-data/ember-data.prod.js:13133:16
ember$data$lib$system$store$finders$$_find/<#http://localhost:1361/test/frontend/bower_components/ember-data/ember-data.prod.js:7470:1
tryCatch#http://localhost:1361/test/frontend/bower_components/ember/ember.prod.js:53070:14
invokeCallback#http://localhost:1361/test/frontend/bower_components/ember/ember.prod.js:53085:15
publish#http://localhost:1361/test/frontend/bower_components/ember/ember.prod.js:53053:9
#http://localhost:1361/test/frontend/bower_components/ember/ember.prod.js:31253:7
Queue.prototype.invoke#http://localhost:1361/test/frontend/bower_components/ember/ember.prod.js:901:9
Queue.prototype.flush#http://localhost:1361/test/frontend/bower_components/ember/ember.prod.js:965:11
DeferredActionQueues.prototype.flush#http://localhost:1361/test/frontend/bower_components/ember/ember.prod.js:765:11
Backburner.prototype.end#http://localhost:1361/test/frontend/bower_components/ember/ember.prod.js:158:9
Backburner.prototype.run#http://localhost:1361/test/frontend/bower_components/ember/ember.prod.js:226:13
run#http://localhost:1361/test/frontend/bower_components/ember/ember.prod.js:19151:12
ember$data$lib$adapters$rest$adapter$$RestAdapter<.ajax/</hash.success#http://localhost:1361/test/frontend/bower_components/ember-data/ember-data.prod.js:1728:15
n.Callbacks/j#http://localhost:1361/test/frontend/bower_components/jquery/dist/jquery.min.js:2:26920
n.Callbacks/k.fireWith#http://localhost:1361/test/frontend/bower_components/jquery/dist/jquery.min.js:2:27738
x#http://localhost:1361/test/frontend/bower_components/jquery/dist/jquery.min.js:4:11251
.send/b/<#http://localhost:1361/test/frontend/bower_components/jquery/dist/jquery.min.js:4:14765
The route of the template that contains the components is:
App.EventsRoute = Ember.Route.extend({
model: function()
{
return Ember.RSVP.hash({
event: this.store.find('event'),
featured: this.store.find('event', 'featured')
});
}
});
Here is my template:
<script type="text/x-handlebars" id="events">
<div class="col-xs-8 pull-left">
{{#each event as |event|}}
{{#event-box event=event}}{{/event-box}}
{{else}}
no events
{{/each}}
...
{{#each featured as |highlight|}}
{{#highlight-event hlevent=highlight}}
{{/highlight-event}}
{{else}}
No Highlights
{{/each}}
...
</script>
Does anyone knows why this error happens and what can I do to solve it?
this.store.find('event', 'featured') is invalid.
Also, find is deprecated.
With the latest Ember Data, you should use store.query().
You'd probably use it like
this.store.query('event', {featured: true})
or
this.store.query('event', {filter: 'featured'})
You'll need to adjust your adapter accordingly. Something like:
urlForQuery: function(query, modelName) {
if (query && query.featured === true) {
delete query.featured;
return this._buildURL(modelName, 'featured');
}
return this._super(query, modelName);
}
This should generate an URL like http://..../events/featured.

Ember.js use itemController if not following naming Conventions

According to official documentation, way to create itemcontroller is:
App.PostsController = Ember.ArrayController.extend({
itemController: 'post'
});
App.PostController = Ember.ObjectController.extend({
// the `title` property will be proxied to the underlying post.
titleLength: function() {
return this.get('title').length;
}.property('title')
});
But I'm not setting my ArrayController to App. It is set to a local variable behind a function scope. And the itemController property can only be string (according to documentation). So how do I set the itemController property?
My code looks like this:
var Channels=Ember.Object.extend({
list:Ember.ArrayController.create(
{
"model":[
{
"id":"display",
"label":"Display",
},{
"id":"social",
"label":"Social",
},{
"id":"email",
"label":"Email",
}
]
}
)
});
App.ChannelController=Ember.Controller.extend({
channels:Channels,
}));
<script type="text/x-handlebars" data-template-name='channel'>
<div>
{{#each channel in channels.list}}
{{channel.label}}
{{/each}}
</div>
</script>
I don't want to pollute App namespace with itemControllers that is to be used locally.
Update
Suppose my channels is like this:
var Channels=Ember.Object.extend({
list:Ember.ArrayController.create(
{
"model":[
{
"id":"display",
"label":"Display",
},{
"id":"social",
"label":"Social",
},{
"id":"email",
"label":"Email",
}
]
}
),
selected:"display"
});
and I want to something like this in template:
<script type="text/x-handlebars" data-template-name='channel'>
<h1>{{channels.selected}}</h1>
<div>
{{#each channel in channels.list}}
<div {{bind-attr class="channel.isselected:active:inactive"}}>{{channel.label}}</div>
{{/each}}
</div>
</script>
so that it outputs:
<h1>display</h1>
<div>
<div class="active">Display</div>
<div class="inactive">Social</div>
<div class="inactive">Email</div>
</div>
How do I do it with components?
You'll likely want to read the guide of components to get the full picture, but the gist of it is that you want to replace all item controllers with components. However, components will also replace the template inside of the each block as well. I don't entirely understand what's going on in your code, but here's an example roughly based on your code.
// Component
App.ChannelDisplayComponent = Ember.Component.extend({
channel: null,
isSelected: function() {
// Compute this however you want
// Maybe you need to pass in another property
}.property('channel')
});
{{! Component Template }}
<div {{bind-attr class="channel.isSelected:active:inactive"}}>
{{channel.label}}
</div>
{{!Channels Template}}
{{#each channel in channels.list}}
{{channel-component channel=channel}}
{{/each}}
The component is essentially your item controller, only it gets its own template as well.
You really shouldn't be worried about polluting the app namespace (unless you're having naming collisions, but that's a different issue). And as Kitler said, you should move to components instead of item controllers. But if you want to do this, the best way I can think of is overridding the (private) controllerAt hook.
var ItemController = Ember.Controller.extend({});
App.PostsController = Ember.ArrayController.extend({
controllerAt: function(idx, object, controllerClass) {
var subControllers = this._subControllers;
if (subControllers.length > idx) {
if (subControllers[idx]) {
return subControllers[idx];
}
}
var parentController = (this._isVirtual ? this.get('parentController') : this);
var controller = ItemController.create({
target: parentController,
parentController: parentController,
model: object
});
subControllers[idx] = controller;
return controller;
}
})

Ember RSVP Hash not updating controller content

I am trying to have handle multiple controllers at one route in Ember. This response seems like the way to go, but am having difficult getting this to work. Here is a simple example that fails to work:
App.IndexRoute = Ember.Route.extend({
model: function() {
myData = [1,2,3];
return Em.RSVP.hash({
docs1: myData,
docs2: myData
});
},
setupController: function(controller, model) {
controller.set('model', model.docs1);
this.controllerFor('documents').set('model', model.docs2);
}
});
App.DocumentsController = Ember.ArrayController.extend({
count: function() {
return this.get('length');
}.property('#each'),
});
App.IndexController = Em.ArrayController.extend({
count: function() {
return this.get('length');
}.property('#each'),
});
And a JSBin showing the results:
http://jsbin.com/wavigada/1/edit
You can see that the IndexController reports the correct count of 3, but the DocumentsController doesn't get set.
Can anyone help me out?
You will need to include the documents controller whose content you populate in setupController in your IndexController via needs:
App.IndexController = Em.ArrayController.extend({
needs: ['documents'],
count: function() {
return this.get('length');
}.property('#each'),
});
Then you need to change your template to:
<script type="text/x-handlebars" data-template-name="index">
{{count}}
{{render 'documents' controllers.documents}}
</script>
Note that just putting {{render 'documents' controllers.documents}} (as you did in your question) refers to a documents property of your current model, which doesn't exists.
See: http://jsbin.com/wavigada/6/

Ember multiple Json request Error while loading route Object has no method 'addArrayObserver'

im having a problem with my ember app. Im new to it, and trying to do something fun. So the idea of this app is to go and fetch a list of artists from a server via an ajax call, and then if you click the artist it will go to the server again and fetch the albums via another ajax call.
So the first part is working, it is actually fetching the artists through the ajax call when i click on "music library", but then when clicking on the artist it throws the following error:
Assertion failed: Error while loading route: TypeError: Object [object Object] has no method 'addArrayObserver'
I've read so many different options, and i think im on the right track because by printing on the console i can see that it is actually going to the server and fetching the right artist's albums, but the error is throw at the last moment, so it is not displaying the albums. I was also able to show the albums when reloading or typing the url (not now, since i changed the code to implement the afterModel)
So, here is my code:
App = Ember.Application.create({
LOG_TRANSITIONS: true,
LOG_TRANSITIONS_INTERNAL: true
});
App.Library = Ember.Object.extend({
name: null,
artist: []
});
App.Library.reopenClass({
loadArtist: function() {
var artistList = Em.A();
$.getJSON('url').then(function(data){
//json parsing, creating a library object and putting it into the array
});
return artistList;
}
});
App.Artist = Ember.Object.extend({
id: null,
name: null,
coverArt: null,
albumCount: null
});
App.Albums = Ember.Object.extend({
albums: []
});
App.Artist.reopenClass({
loadAlbums: function(params) {
var albumsJson = 'url' + params.artist_id +'';
var albumList = Em.A();
$.getJSON(albumsJson).then(function(data){
//parsing json, creating artist objects and pushing them into the array
});
return albumList;
//});
}
});
/*****************************ROUTER**************************************************************************************/
App.Router.map(function() {
// put your routes here
this.resource('library', function() {
this.resource('artist', { path: '/:artist_id'});
});
});
App.IndexRoute = Ember.Route.extend({
model: function() {
var hi = ['Welcome'];
return hi;
}
});
App.LibraryRoute = Ember.Route.extend({
model: function() {
return App.Library.loadArtist();
}
});
App.ArtistRoute = Ember.Route.extend({
model: function(params) {
this.transitionTo('artist', params);
},
afterModel: function(params, transition){
var artist = Em.A();
if(params.artist_id==null){
artist.push(App.Artist.create({artist_id: params.id}));
} else {
artist.push(App.Artist.create({artist_id: params.artist_id}));
}
return App.Artist.loadAlbums(artist[0]);
}
});
/**************************************CONTROLLERS***********************************************************************************/
App.ArtistController = Ember.ArrayController.extend({
needs: "library"
});
App.LibraryController = Ember.ArrayController.extend({});
I would really appreciate some help!
Also, the HTML is as follows:
<script type="text/x-handlebars">
<div class="navbar navbar-default">
<div class="navbar-inner">
<a class="navbar-brand" href="#">My Library</a>
<ul class="nav navbar-nav">
<li>{{#linkTo 'index'}}Home{{/linkTo}}</li>
<li>{{#linkTo 'library'}}Music Library{{/linkTo}}</li>
</ul>
</div>
</div>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="index">
<div class="container">
{{#each item in model}}
<h1>{{item}}</h1>
{{/each}}
</div>
</script>
<script type="text/x-handlebars" data-template-name="library">
<div class="container">
<div class="row">
<div class="col-md-4">
<table class="table">
{{#each model}}
<tr><td>
{{name}}
</td></tr>
{{#each artist}}
<tr><td>
{{#linkTo 'artist' this}}
{{name}}
{{/linkTo}}
<!--<a {{action 'selectArtist' this}}> {{name}} </a>-->
</td></tr>
{{/each}}
{{/each}}
</table>
</div>
<div class="col-md-8">
<p>Albumes</p>
{{outlet}}
</div>
</div>
</div>
</script>
<script type="text/x-handlebars" data-template-name="artist">
<div class="container">
<div class="col-md-4">
<table class="table">
<tr><td><p>{{controllers.library.artist.name}}</p></td></tr>
{{#each itemController='album'}}
<tr><td>
{{{name}}}
</td></tr>
{{/each}}
</table>
</div>
</div>
</script>
Thanks a lot!!
To get rid of the error you need to modify the model function of the App.ArtistRoute, to return an array as App.ArtistController is an Ember.ArrayController.
For example,
App.ArtistRoute = Ember.Route.extend({
model: function(params) {
//this.transitionTo('artist', params);
return [];
},
....
Or even place the code of afterModel function in model function to retrieve the albums of this artist.
Although i'm not certain if you really want the model of your artist context to be the albums, it does not look correct to me. I would suggest to make the App.ArtistController aν Ember.ObjectController, assign the model to an App.Artist object and store the albums related to this artist in a property of the App.Artist class. In that case you will need to add a property in App.Artist and create a class of App.Album.
With this in mind have a look at the following example which is a very rough modification of your code (caution the App.ArtistController has not been switched instead its model is an array of albums),
http://emberjs.jsbin.com/AdOfiyiN/2#/library/2
OK, i solved it using this question:
Why isn't my ember.js route model being called?
Instead of putting the logic in the model or afterModel, i just needed to set the controller.
Hope it helps to someone.
Best!

Categories