Ember.js use itemController if not following naming Conventions - javascript

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

Related

Pass variable from within script tags to Vue instance

In my Drupal 7 site's html I have this
<script>$L = $L.wait(function() {
(function($) {
Drupal.behaviors.related_products = {
attach: function (context, settings) {
artiklar = Drupal.settings.related_products.artiklar;
console.log(artiklar);
}
};
})(jQuery);
});</script>
In the variable artiklar above I have some data that I have passed from the server side using Drupal behaviors. Now, on the client side I need to access the variable artiklar in a Vue component, like so:
Vue.component('artikel-lista', {
template:`
<ul>
<artikel v-for="artikel in artiklar">{{ artikel.title }} Pris: {{artikel.price}} <a :href="artikel.link" class="button tiny" target="_blank">Läs mer</a></artikel>
</ul>
`,
data(){
return {
artiklar: "",
};
},
mounted: function(){
this.artiklar = artiklar // how can I access the variable "artiklar" here
},
});
The data in the variable consists of an array of items, that I need in my Vue component. But how can I pass the variable from within the script tags to the Vue instance, that lives in a separate file, inserted just before the ending body tag. Anyone?
If you have data in the globally visible Drupal.settings.related_products.artiklar object then you can refer to it practically the same way in Vue.js. or if you must use this function, assign data to global scope window.*.
new Vue({
template: `<div>{{foo}} / {{bar}}</div>`,
data() {
return {
foo: Drupal.settings.related_products.artiklar,
bar: window.artiklarData
};
}
}).$mount("#app");
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">Vue App</div>
<script>
// simulate global variable
var Drupal = {
settings: {
related_products: {
artiklar: ['fus', 'ro', 'dah']
}
}
};
(function() {
window.artiklarData = Drupal.settings.related_products.artiklar;
})();
</script>
If you assign the value to Drupal.settings.related_products.artiklar after creating the Vue object, you can try to use the solutions described in the documentation, e.g.
const vm = new Vue({
template: `<div>{{foobar}}</div>`,
data() {
return {
foobar: 'Initial value'
};
}
}).$mount("#app");
setTimeout(() => {
// simulate global variable
var Drupal = {
settings: {
related_products: {
artiklar: 'Changed value'
}
}
};
(function() {
vm.foobar = Drupal.settings.related_products.artiklar;
})();
}, 2000);
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">Vue App</div>
Maybe you could use RxJS but I don't have enough knowledge to tell if it's true and give an example.
Just in case anyone else is struggling with the same thing, I post this answer to my own question (I accidentally posted the question with the wrong account). In the end it turns out that the answer from Gander was correct and that I could access the variable directly in the Vue component, w/o first stashing it an a global variable. The viewed result was kind of weird though and after some trialling I found out that I had to parse the result with JSON.parse(). This is the working code now:
Vue.component('artikel-lista', {
template:`
<ul>
<artikel v-for="artikel in artiklar">{{ artikel.title }} Pris: {{artikel.price}} <a :href="artikel.link" class="button tiny" target="_blank">Läs mer</a></artikel>
</ul>
`,
data(){
return{
artiklar:""
}
},
mounted:function(){
this.artiklar = JSON.parse(Drupal.settings.related_products.artiklar);
console.log(this.artiklar);
}
});

How to communicate between 2 component - sibling components(Angular 1.5.8)

Hey I have the following component tree: I have a root component called rules and two sons components called: rulesPanel & rulesEditor.
Now I can create a communication between son and mother component:
rulesEditor can call to rules component and jump an event on him.
rulesPanel can call to rules component and jump an event on him.
I want to have a communication between the 2 brothers:
rulesEditor and rulesPanel.
I don't want to use $scope or $broadcast, I want to do it through the bindings of the component himself.
I have tried to think of way of doing so, but all I got is that I can call to upper level but not to a parallel level.
Edit:
My Question is different from the possible duplication,
I don't want to pass a data, I want to execute a function in one component and then execute another function in the sibling component as a result of a click function in the brother component.
Here is my code and what I have achieved so far:
var app = angular.module("app",[]);
angular.module('app').component('rules', {
template: `
<rules-panel dial-mom="$ctrl.receivePhoneCall(message)">
</rules-panel>
<rules-editor>
</rules-editor>`,
bindings: {
},
controller: rulesController,
});
function rulesController(){
var self = this;
self.receivePhoneCall = function(message){
console.log("Hello Son");
console.log("I got your message:",message)
}
console.log("rulesController")
}
angular.module('app').component('rulesPanel', {
template: `<h1>rulesPanel</h1>
<button ng-click="$ctrl.callMom()">CallMom</button>
<button ng-click="$ctrl.CallBrother()">CallBrother</button>`,
bindings: {
dialMom: '&'
},
controller: rulesPanelController,
});
function rulesPanelController(){
var self = this;
console.log("rulesPanelController");
self.callMom = function(){
console.log("Call mom");
self.dialMom({message:"Love you mom"});
}
self.CallBrother = function(){
console.log("Call brother");
}
}
angular.module('app').component('rulesEditor', {
template: '<h1>rulesEditor</h1>',
bindings: {
},
controller: rulesEditorController,
});
function rulesEditorController(){
console.log("rulesEditorController")
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.1/angular.min.js"></script>
<div ng-app="app">
<rules></rules>
</div>
You can use a semi Angular 2 component approach. Meaning you can use Input/output approach to achieve this.
I will give you an example and you can take it from there.
Let's say you have a header and a main component.
In your header component where you want to notify the main you can raise an event like this:
.component('headerComponent', {
template: `
<h3>Header component</h3>
<a ng-class="{'btn-primary': $ctrl.view === 'list'}" ng-click="$ctrl.setView('list')">List</a>
<a ng-class="{'btn-primary': $ctrl.view === 'table'}" ng-click="$ctrl.setView('table')">Table</a>
`,
controller: function() {
this.setView = function(view) {
this.view = view
this.onViewChange({$event: {view: view}})
}
},
bindings: {
view: '<',
onViewChange: '&'
}
})
With binding view: '<' we specify that header component will be able to read outer something and bind it as view property of the own controller.
Header controller can be used like this:
<header-component view="root.view" on-view-change="root.view = $event.view"></header-component>
On the other hand main in simpler, it only needs to define input it accepts:
.component('mainComponent', {
template: `
<h4>Main component</h4>
Main view: {{ $ctrl.view }}
`,
bindings: {
view: '<'
}
})
And finally it all wired together:
<header-component view="root.view" on-view-change="root.view = $event.view"></header-component>
<main-component view="root.view"></main-component>
Here is a plunker.

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.

Emberjs itemController / controller property binding

I've made a jsbin to illustrate my issue.
the binding seems KO with lastname property defined inside the itemController and the fullname value is not updated in my items loop.
What am I doing wrong ?
Controller for item in list is different than one you edit property lastname for, so it will never get updated. Propery lastname has to be specified as Model's property (if using Ember Data you simply don't use DS.attr for it and it won't be persisted). If you use custom library for data persistence you have to manually remove lastname property. You can use Ember Inspector extension to see that there are 5 controllers when you click on item. 4 for each item in list and one is being generated when you click. You edit property lastname for this fifth controller. To solve this you can use:
JavaScript:
App = Ember.Application.create();
App.Router.map(function() {
this.resource('items', function() {
this.resource('item', {path: '/:item_id'});
});
});
App.Model = Ember.Object.extend({
firstname: 'foo',
lastname: 'bar',
fullname: function() {
return this.get('firstname') + ' ' + this.get('lastname');
}.property('firstname', 'lastname')
});
App.ItemsRoute = Ember.Route.extend({
model: function() {
return [App.Model.create({id: 1}), App.Model.create({id: 2}), App.Model.create({id: 3}), App.Model.create({id: 4})];
}
});
App.ItemRoute = Ember.Route.extend({
model: function(params) {
return this.modelFor('items').findBy('id', +params.item_id);
}
});
Templates:
<script type="text/x-handlebars">
<h2>Welcome to Ember.js</h2>
{{link-to "items" "items"}}
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="items">
<ul>
{{#each item in model}}
<li>
{{#link-to 'item' item.id}}
{{item.fullname}} {{item.id}}
{{/link-to}}
</li>
{{/each}}
</ul>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="item">
{{input value=model.firstname}}
{{input value=model.lastname}}
{{model.fullname}}
</script>
Please keep in mind that ArrayController and ObjectController aren't recommended to use, because they will be deprecated in future. Demo.

Parsing ember-leaflet coordinates from json using ember models

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/

Categories