Ember Controller extend not loading variables into template - ember.js

I've created a mixin, a controller, and a template.
// checkIn/index.js.handlebars
<div class="page-header text-center">
<h2>
Check in for
{{#unless checkInSettings.loaded}}
<span class="icon-spinner icon-spin"></span>
{{else}}
<span class="text-info">{{checkInSettings.programName}}</span> - <span class="text-info">{{checkInSettings.programStartDate}}</span>
{{/unless}}
</h2>
</div>
// controllers/check_in_controller.js
App.CheckInController = Em.Controller.extend(App.LoadCheckInSettings);
// mixins/load_check_in_settings.js
App.LoadCheckInSettings = Em.Mixin.create({
init: function() {
this._super();
if (!this.get('checkInSettings')) {
this.set('checkInSettings', App.CheckInSettings.create());
this.get('checkInSettings').set('isLoading', true);
var self = this;
$.getJSON('/check_in/settings').then(function(data) {
self.get('checkInSettings').set('isLoading', false);
self.get('checkInSettings').set('programName', data.program_name);
self.get('checkInSettings').set('programAcademicYear', data.program_academic_year);
self.get('checkInSettings').set('programStartDate', data.program_start_date);
self.get('checkInSettings').set('loaded', true);
});
}
}
});
I am trying to load checkInSettings into the entire CheckInController and all its subcontrollers. I would like to see checkInSettings in my checkIn/index template, but it's not showing up.
What does work is if I create the controller:
// controllers/check_in_index_controller.js
App.CheckInIndexController = Em.Controller.extend(App.LoadCheckInSettings);
However, I want to load the settings for use in other areas of CheckIn, such as the settings page.
Is there a way to load this mixin for the all of CheckIn, in the controller or route, so that I can access checkInSettings from index or settings?

Create a controller CheckInsIndexController. On that controller add these two properties needs: ['check_ins'], a checkinsBinding: 'controllers.checkIns.content'. Then you can use checkIns in your index template.

Related

How to save the endpoint data in model store if call is made from actions in controller

I have created a dialog box using the ember-modal-dialog. The content that is going to displayed in the dialog is received from the server. I am able to make the call to server and fetch the data. But I don't know how to save the data into my model store from actions.
Controller.js
actions:{
fiModal1: function(photo){
Ember.$('body').addClass('centered-modal-showing');
var currentState = this;
photo.toggleProperty('fidialogShowing'))
console.log('opendialog');
raw({
url: 'http://example.co.in/api/photo/'+photo.get('like_pk')+'/likes/',
type: 'GET',
}).then(function(result){
currentState.set('model.feed.liker',result)
});
},
bookmarked:function(liker){
liker.set('is_bookmarked',true)
},
}
feed.hbs
<p {{action "fiModal" photo }}>
{{photo.0.numlikes}}
</p>
{{#if photo.fidialogShowing}}
{{#modal-dialog translucentOverlay=true close = (action "fiDialogClose" photo)}}
{{#each model.feed.liker as |liker}}
<div class = "col-sm-6">
{{#if liker.is_bookmarked}}
<a href {{action "unbookmarked" liker}}>
<img class="foll" src = "images/button-bookmark-secondary-state-dark-b-g.png">
</a>
{{else}}
<a href {{action "bookmarked" liker}}>
<img class="foll" src = "images/button-bookmark.png">
</a>
{{/if}}
</div>
{{/each}}
Now the problem is that when action inside the dialog box is fired it throws an error:
fiver.set is not function
I think that the problem is occurring because I am not saving the result in the model store. How should I do it so the action inside the dialog box also works?
You can just encapsulate the results from your server into Ember.Object
Ember.Object.create(json)
For exemple replace your line
currentState.set('model.feed.liker',result)
by
currentState.set('model.feed.liker', result.map(function(item) {
return Ember.Object.create(item);
})
that way each elements inside your model.feed.liker should have a method 'set' available.

how to trigger element features after render in ember-cli

I want to add tooltips onto a button in a component that can appear based on a set of results back from the server. (i.e. action buttons for delete, edit etc.)
I have created a “search” component that is rendering into the application and when a search button is clicked the server may return a number of rows into that same search component template.
so for example:
My-app/pods/factual-data/template.hbs
Contains:
…
{{#if results}}
<div class="row">
<div class="col-sm-3"><b>Factual ID</b></div>
<div class="col-sm-2"><b>Name</b></div>
<div class="col-sm-2"><b>Town</b></div>
<div class="col-sm-2"><b>Post Code</b></div>
<div class="col-sm-2"><b>Actions</b></div>
</div>
{{/if}}
{{#each result in results}}
<div class="row">
<div class="col-sm-3">{{result.factual_id}}</div>
<div class="col-sm-2">{{result.name}}</div>
<div class="col-sm-2">{{result.locality}}</div>
<div class="col-sm-2">{{result.postcode}}</div>
<div class="col-sm-2">
<button {{action "clearFromFactual" result.factual_id}} class="btn btn-danger btn-cons tip" type="button" data-toggle="tooltip" class="btn btn-white tip" type="button" data-original-title="Empty this Row<br> on Factual" ><i class="fa fa-check"></i></button>
</div>
</div>
{{/each}}
…
However I cannot get the tooltip code to function, due to an element insert detection/timing issue..
In the component
My-app/pods/factual-data/component.js
Contains:
...
didInsertElement : function(){
console.log("COMPONENT: didInsertElement");
Ember.run.scheduleOnce('afterRender', this, this.afterRenderEvent);
this.enableToolTips();
},enableToolTips: function() {
var $el = Ember.$('.tip');
console.log("TOOLTIP:", $el);
if($el.length > 0) {
$el.tooltip({
html:true,
delay: { show: 250, hide: 750 }
});
}
}
...
However it seems didInsertElement is only run when the component is first rendered, is there a different function that is called everytime something in the DOM is changed within a component?
I did try to use observes: i.e.
…
enableToolTips: function() {
var $el = Ember.$('.tip');
console.log("TOOLTIP:", $el);
if($el.length > 0) {
$el.tooltip({
html:true,
delay: { show: 250, hide: 750 }
});
}
}.observes('results')
…
Which does trigger when the results variable is changed however it is still triggering before the content is actually rendered. I am assuming this because is I manually run in the console Ember.$('.tip').tooltip() (after the button is displayed) then the tooltips work ok.
Any pointers on this issue?
Try
enableToolTips: function() {
Ember.run.scheduleOnce('afterRender', this, function() {
var $el = Ember.$('.tip');
console.log("TOOLTIP:", $el);
if($el.length > 0) {
$el.tooltip({
html:true,
delay: { show: 250, hide: 750 }
});
}
});
}.observes('results')
Checking Ember.Component API there are two hooks that can do that
willClearRender : When component html is about to change.
willInsertElement : When old html is cleared and new one is going to be placed.
But you need to have a look on scheduleOnce.
Its worth noting that didInsertElement runs every time. But when it runs view was not updated. To solve that you need to run your code inside a Run Loop like this
didInsertElement : function(){
var self = this;
Ember.run.scheduleOnce('afterRender', this, function(){
//run tool tip here
self.$().find(".tip").tooltip({
});
});
}

getting back reference to a specific model using Ember's Array Controller

I'm new to Ember and am finding some of their concepts a bit opaque. I have a app that manages inventory for a company. There is a screen that lists the entirety of their inventory and allows them to edit each inventory item. The text fields are disabled by default and I want to have an 'edit item' button that will set disabled / true to disabled / false. I have created the following which renders out correctly:
Inv.InventoryitemsRoute = Ember.Route.extend({
model: function(params) {
return Ember.$.getJSON("/arc/v1/api/inventory_items/" + params.location_id);
}
<script type="text/x-handlebars" data-template-name="inventoryitems">
{{#each}}
<div class='row'>
<p>{{input type="text" value=header disabled="true"}}</p>
<p>{{input type="text" value=detail disabled="true"}}</p>
<button {{action "editInventoryItem" data-id=id}}>edit item</button>
<button {{action "saveInventoryItem" data-id=id}}>save item</button>
</div>
{{/each}}
</script>
So this renders in the UI fine but I am not sure how to access the specific model to change the text input from disabled/true to disabled/false. If I were just doing this as normal jQuery, I would add the id value of that specific model and place an id in the text input so that I could set the textfield. Based upon reading through docs, it seems like I would want a controller - would I want an ArrayController for this model instance or could Ember figure that out on its own?
I'm thinking I want to do something like the following but alerting the id give me undefined:
Inv.InventoryitemsController=Ember.ArrayController.extend({
isEditing: false,
actions: {
editInventoryItem: function(){
var model = this.get('model');
/*
^^^^
should this be a reference to that specific instance of a single model or the list of models provided by the InventoryitemsRoute
*/
alert('you want to edit this:' + model.id); // <-undefined
}
}
});
In the Ember docs, they use a playlist example (here: http://emberjs.com/guides/controllers/representing-multiple-models-with-arraycontroller/) like this:
App.SongsRoute = Ember.Route.extend({
setupController: function(controller, playlist) {
controller.set('model', playlist.get('songs'));
}
});
But this example is a bit confusing (for a couple of reasons) but in this particular case - how would I map their concept of playlist to me trying to edit a single inventory item?
<script type="text/x-handlebars" data-template-name="inventoryitems">
{{#each}}
<div class='row'>
<p>{{input type="text" value=header disabled="true"}}</p>
<p>{{input type="text" value=detail disabled="true"}}</p>
<button {{action "editInventoryItem" this}}>edit item</button>
<button {{action "saveInventoryItem" this}}>save item</button>
</div>
{{/each}}
</script>
and
actions: {
editInventoryItem: function(object){
alert('you want to edit this:' + object.id);
}
}
Is what you need. But let me explain in a bit more detail:
First of all, terminology: Your "model" is the entire object tied to your controller. When you call this.get('model') on an action within an array controller, you will receive the entire model, in this case an array of inventory items.
The {{#each}} handlebars tag iterates through a selected array (by default it uses your entire model as the selected array). While within the {{#each}} block helper, you can reference the specific object you are currently on by saying this. You could also name the iteration object instead of relying on a this declaration by typing {{#each thing in model}}, within which each object would be referenced as thing.
Lastly, your actions are capable of taking inputs. You can declare these inputs simply by giving the variable name after the action name. Above, I demonstrated this with {{action "saveInventoryItem" this}} which will pass this to the action saveInventoryItem. You also need to add an input parameter to that action in order for it to be accepted.
Ok, that's because as you said, you're just starting with Ember. I would probably do this:
<script type="text/x-handlebars" data-template-name="inventoryitems">
{{#each}}
<div class='row'>
<p>{{input type="text" value=header disabled=headerEnabled}}</p>
<p>{{input type="text" value=detail disabled=detailEnabled}}</p>
<button {{action "editInventoryItem"}}>edit item</button>
<button {{action "saveInventoryItem"}}>save item</button>
</div>
{{/each}}
</script>
with this, you need to define a headerEnabled property in the InventoryitemController(Note that it is singular, not the one that contains all the items), and the same for detailEnabled, and the actions, you can define them also either in the same controller or in the route:
App.InventoryitemController = Ember.ObjectController.extend({
headerEnabled: false,
detailEnabled: false,
actions: {
editInventoryItem: function() {
this.set('headerEnabled', true);
this.set('detailEnabled', true);
}
}
});
that's just an example how you can access the data, in case the same property will enable both text fields, then you only need one, instead of the two that I put . In case the 'each' loop doesn't pick up the right controller, just specify itemController.

How can I render a block only if a specific route is active?

I wanna render a block in Ember Handlebars only, if a specific route is active.
So, how can I create a 'ifRoute' helper, with the same conditons then the 'active' class on the 'linkTo' helper?
I want this, because I've a two layer navigation. So, I want to show the sub-navigation only, if the head navigation point is active. I dont wanna use the 'active' class, because I use lazy loading and I only want to load the sub navigation when the head navigation point is active.
So, what I want to do is:
<ul>
{{#each assortmentGroups}}
<li>
{{#linkTo "assortmentGroup" this}} {{description}} {{/linkTo}}
{{#ifRoute "assortmentGroup" this}}
<ul>
{{#each itemCategories}}
<li>{{#linkTo "itemCategory" this}} {{description}} {{/linkTo}}</li>
{{/each}}
</ul>
{{/ifRoute}}
</li>
{{/each}}
<ul>
How can I do this or is there a better solution?
Thanks
Just add to the controller:
needs: ['application'],
isCorrectRouteActive: Ember.computed.equal('controllers.application.currentRouteName', 'correctRoute')
Similarly:
isCorrectPathActive: Ember.computed.equal('controllers.application.currentPath', 'correct.path')
isCorrectURLActive: Ember.computed.equal('controllers.application.currentURL', 'correctURL')
I am quite sure latest Ember does the rest
Here are two possible options, although for both you first have to save the currentPath in your ApplicationController to have access to it whenever you need it:
var App = Ember.Application.create({
currentPath: ''
});
App.ApplicationController = Ember.ObjectController.extend({
updateCurrentPath: function() {
App.set('currentPath', this.get('currentPath'));
}.observes('currentPath')
});
Using a computed property
Then in the controller backing up the template, let's say you have a NavigationController you create the computed property and define also the dependency to the ApplicationController with the needs API to gather access, then in the CP you check if the currentPath is the one you want:
App.NavigationController = Ember.Controller.extend({
needs: 'application',
showSubMenu: function(){
var currentPath = this.get('controllers.application.currentPath');
return (currentPath === "assortmentGroup");
}.property('controllers.application.currentPath')
});
So you can use a simple {{#if}} helper in your template:
...
{{#linkTo "assortmentGroup" this}} {{description}} {{/linkTo}}
{{#if showSubMenu}}
<ul>
{{#each itemCategories}}
<li>{{#linkTo "itemCategory" this}} {{description}} {{/linkTo}}</li>
{{/each}}
</ul>
{{/if}}
</li>
...
Using a custom '{{#ifRoute}}' helper
But if your really want a custom helper to deal with your condition then this is how you could do it, note that the currentPath stored on your application is still needed since we need a way to get the value of the current route:
Ember.Handlebars.registerHelper('ifRoute', function(value, options) {
if (value === App.get('currentPath')) {
return options.fn(this);
}
else {
return options.inverse(this);
}
});
And then you could use it like this:
...
{{#linkTo "assortmentGroup" this}} {{description}} {{/linkTo}}
{{#ifRoute "assortmentGroup"}}
<ul>
{{#each itemCategories}}
<li>{{#linkTo "itemCategory" this}} {{description}} {{/linkTo}}</li>
{{/each}}
</ul>
{{/ifRoute}}
</li>
...
See here also a simple Demo of the "custom helper" solution: http://jsbin.com/izurix/7/edit
Note: with the second solution there is a catch! Since bound helpers do not support blocks (in embers handlebars customization) I used a simple helper that does not reevaluate the condition depending on bindings which is may not what you want.
Hope it helps.
After investigating the ember code for the linkTo and if helpers, the answer from intuitivepixel and a blog post about writing my own bound block helper, I've found a solution:
var resolveParams = Ember.Router.resolveParams;
var resolvedPaths = function(options) {
var types = options.options.types.slice(1),
data = options.options.data;
return resolveParams(options.context, options.params, { types: types, data: data });
};
Ember.Handlebars.registerHelper('ifRoute', function(name) {
var options = [].slice.call(arguments, -1)[0];
var params = [].slice.call(arguments, 1, -1);
var theResolvedPaths = resolvedPaths({ context: this, options: options, params: params });
var router = options.data.keywords.controller.container.lookup('router:main');
var self = this;
var evaluateIsCurrentRoute = function() {
self.set('current_route_is_active_bool_for_ifroute', (function() {
return router.isActive.apply(router, [name].concat(theResolvedPaths)) ||
router.isActive.apply(router, [(name + '.index')].concat(theResolvedPaths));
})());
};
evaluateIsCurrentRoute();
router.addObserver('url', evaluateIsCurrentRoute);
options.contexts = null;
return Ember.Handlebars.helpers.boundIf.call(this, 'current_route_is_active_bool_for_ifroute', options);
});
I found an easy way to check if a route is active, but to get this into a computed property may not be so easy.
// Test if you are currently in a route by it's lowercase name
App.isInRoute = function(name) {
return App.Router.router.currentHandlerInfos.mapProperty('name').contains(name);
}
To use:
App.isInRoute('posts.show'); // true if in the route

Triggering the display of a View from within a template

In my application I display a list of accounts like so:
<script type="text/x-handlebars" data-template-name="accounts">
{{#each account in controller}}
{{#linkTo "account" account class="item-account"}}
<div>
<p>{{account.name}}</p>
<p>#{{account.username}}</p>
<i class="settings" {{ action "openPanel" account }}></i>
</div>
{{/linkTo}}
{{/each}}
</script>
Each account has a button which allows users to open a settings panel containing settings just for that account. as you can see in this quick screencast:
http://screencast.com/t/tDlyMud7Yb7e
I'm currently triggering the opening of the panel from within a method located on the AccountsController:
Social.AccountsController = Ember.ArrayController.extend({
openPanel: function(account){
console.log('trigger the panel');
}
});
But I feel that it's more appropriate to open the panel from within a View that I've defined for this purpose. This would give me access to the View so that I can perform manipulations on the DOM contained within it.
Social.MainPanelView = Ember.View.extend({
id: 'panel-account-settings',
classNames: ['panel', 'closed'],
templateName: 'mainPanel',
openPanel: function(){
console.log('opening the panel');
}
});
<script type="text/x-handlebars" data-template-name="mainPanel">
<div id="panel-account-settings" class="panel closed">
<div class="panel-inner">
<i class="icon-cancel"></i>close
<h3>Account Settings</h3>
Disconnect Account
</div>
</div>
</script>
The problem I'm encountering is that I don't see how I can trigger a method on the Social.MainPanelView from the context of the AccountsController. Is there a better solution?
UPDATE 1
I've worked up a Fiddle to illustrate what I'm talking about:
http://jsfiddle.net/UCN6m/
You can see that when you click the button it calls the showPanel method found on App.IndexController. But I want to be able to call the showPanel method found on App.SomeView instead.
Update:
Approach One:
Simplest of all
Social.AccountsController = Ember.ArrayController.extend({
openPanel: function(account){
/* we can get the instance of a view, given it's id using Ember.View.views Hash
once we get the view instance we can call the required method as follows
*/
Ember.View.views['panel-account-settings'].openPanel();
}
});
Fiddle
Approach Two:(Associating a controller, Much Cleaner)
Using the Handlebars render helper: what this helper does is it associates a controller to the view to be displayed, so that we can handle all our logic related to the view in this controller, The difference is
{{partial "myPartial"}}
just renders the view, while
{{render "myPartial"}}
associates App.MyPartialController for the rendered view besides rendering the view, Fiddle
now you can update your code as follows
application.handlebars(The place you want to render the view)
{{render "mainPanel"}}
accounts controller
Social.AccountsController = Ember.ArrayController.extend({
openPanel: function(account){
this.controllerFor("mainPanel").openPanel();
}
});
main panel view
Social.MainPanelView = Ember.View.extend({
id: 'panel-account-settings',
classNames: ['panel', 'closed']
});
main panel controller
Social.MainPanelController = Ember.Controller.extend({
openPanel: function(){
console.log('opening the panel');
}
})
Approach Three:
This one is the manual way of accomplishing Approach Two
Social.MainPanelView = Ember.View.extend({
id: 'panel-account-settings',
controllerBinding: 'Social.MainPanelController',
classNames: ['panel', 'closed'],
templateName: 'mainPanel'
});
Social.MainPanelController = Ember.Controller.extend({
openPanel: function(){
console.log('opening the panel');
}
})
use this.controllerFor("mainPanel").openPanel()
You need to use the action helper rather than directly coding the links. The action helper targets the controller by default, but you can change it to target the view instead:
<a {{action openPanel target="view"}}></a>
Your second link should be a linkTo a route, since you are specifying a link to another resource. The whole snippet, revised:
Social.MainPanelView = Ember.View.extend({
id: 'panel-account-settings',
classNames: ['panel', 'closed'],
templateName: 'mainPanel',
openPanel: function(){
console.log('opening the panel');
}
});
<script type="text/x-handlebars" data-template-name="mainPanel">
<div id="panel-account-settings" class="panel closed">
<div class="panel-inner">
<a {{action openPanel target="view"} class="button button-close"><i class="icon-cancel"></a></i>
<h3>Account Settings</h3>
{{#linkTo "connections"}}Disconnect Account{{/linkTo}}
</div>
</div>
</script>