ember js - controller action not invoked - ember.js

I have the following controller code:
var App = Ember.Application.create();
App.DocumentController = Ember.Controller.extend({
actions:{
test:function(){
console.log('test');
}
}
});
This the form:
div class="form-horizontal form-group form-group-lg row">
<div class="col-xs-10 col-xs-offset-1 col-sm-6 col-sm-offset-1 col-md-5 col-md-offset-2">
{{input type="text" value=name class="form-control" placeholder="Name of document." autofocus="autofocus"}}
</div>
<div class="col-xs-10 col-xs-offset-1 col-sm-offset-0 col-sm-4 col-md-3">
<button class="btn btn-primary btn-lg btn-block" {{action 'test'}}>Create Document</button>
</div>
</div>
{{#if responseMessage}}
<div class="alert alert-success">{{responseMessage}}</div>
{{/if}}
When clicking the button, the debugger outputs:
Nothing handled the action 'firstAction'. If you did handle the action, this error can be caused by returning true from an action handler in a controller, causing the action to bubble
Not sure what the problem is - the route code:
Router.map(function() {
this.resource( 'index', { path: '/' } );
this.route('decisions');
this.route('teams');
this.route('about');
this.resource("documents",{ path: '/documents' }, function() {
this.route('/show', {path: '/show/:id'});
this.route('/edit', {path: ':id/edit'});
});
});
The model code:
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('String'),
url: DS.attr('String'),
text: DS.attr('String')
});
I know ember has a strict naming convention, but I generated the resource (documents) from the ember-cli using the naming convention in their tutorials. How do I get the template hbs code to talk to the controller?

The issue was with my folder structure. Because the document model will have alot of different templates, I decided to add a document folder inside the template folder. I should have done the same with the controller folder (i.e. added a documents folder). Also the file name of the controller needs to be the same file name as the template (in this case index.hbs for template, and index.js for controller file).

The name of the action in your controller is test but you are invoking a method by the name firstAction, which is not defined.
try the below:
<button class="btn btn-primary btn-lg btn-block" {{action 'test'}}>Create Document</button>

Related

A property in an ember component is not propagating to its parent controller

I have a component priority-selector that looks like this...
export default Ember.Component.extend({
priority: 'low',
didInsertElement: function() {
this.$('[data-toggle="tooltip"]').tooltip();
},
actions: {
click: function(priority) {
this.set('priority', priority);
this.$('.btn-primary').removeClass('btn-primary');
this.$('.btn-for-' + priority).addClass('btn-primary');
}
}
});
Template code...
<div class="pull-right btn-group" role="group">
<div type="button" class="btn btn-default btn-for-low" {{action 'click' 'low'}}>LOW</div>
<div type="button" class="btn btn-default btn-for-medium" {{action 'click' 'medium'}}>MEDIUM</div>
<div type="button" class="btn btn-default btn-for-high" {{action 'click' 'high'}}>HIGH</div>
</div>
I use this component in a template like so...
<div class='col-sm-3'>
{{priority-selector priority=priority_gender}}
</div>
And I have it specified in the controller...
export default Ember.Controller.extend({
...
priority_gender: 'low'
...
})
But the property in the controller never changes when I interact with the component, I can observe this to be the case by looking in the ember inspector.
Your code is working.
So, make sure to specify priority_gender in right controller for template you're using priority-selector component.

Using {{render}} to display a form and save a model instance from anywhere in the application

I have a resource (Trip) and its routes - trips.index, trips.edit, trips.new. I would like to put a copy of the form in trips/new into application.hbs template so it appears on every page.
The form in trips/new route works but the one in application.hbs doesn't. I get the following errors when I submit the form:
Uncaught Error: Assertion Failed: Cannot delegate set('name', a) to
the 'content' property of object proxy
: its 'content' is
undefined.
Uncaught Error: Assertion Failed: Cannot delegate set('errorMessage',
You have to fill all the fields) to the 'content' property of object
proxy : its 'content' is
undefined.
I have the following code.
application.hbs:
...
{{render "trips/new"}}
...
{{outlet}}
...
templates/trips/new.hbs:
<form {{action "save" on="submit"}} role="form">
<p class="text-danger">{{errorMessage}}</p>
<div class="form-group">
<label for="">Name</label>
{{input class="form-control" value=name}}
</div>
<div class="form-group">
<label for="">Country</label>
{{input class="form-control" value=country}}
</div>
<div class="form-group">
<label for="">Start Date</label>
{{input class="form-control" value=startDate placeholder="YYYY-MM-DD"}}
</div>
<div class="form-group">
<label for="">End Date</label>
{{input class="form-control" value=endDate placeholder="YYYY-MM-DD"}}
</div>
<input type="submit" value="Save" class="btn btn-primary">
<button {{action "cancel"}} class="btn btn-default">Cancel</button>
</form>
controllers/trips/base.js:
import Ember from 'ember';
export default Ember.ObjectController.extend({
isValid: Ember.computed(
'name',
'country',
function() {
return !Ember.isEmpty(this.get('name')) &&
!Ember.isEmpty(this.get('country'));
}
),
actions: {
save: function() {
if (this.get('isValid')) {
var _this = this;
this.get('model').save().then(function(trip) {
_this.transitionToRoute('trips.show', trip);
});
} else {
this.set('errorMessage', 'You have to fill all the fields');
}
},
cancel: function() {
return true;
}
}
});
controllers/trips/new.js:
import TripsBaseController from './base';
export default TripsBaseController.extend({
actions: {
cancel: function() {
this.transitionToRoute('trips.index');
return false;
}
}
});
routes/trips/new.js:
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
return this.store.createRecord('trip');
},
actions: {
save: function() {
return true;
},
cancel: function() {
return true;
}
}
});
Any idea how I can solve this? I'm about to try converting it into a View or component but I'm wondering if I can still use {{render}} and I'm missing something simple here.
I'm using ember-cli with ember 1.7.0 and ember-data 1.0.0-beta.10.
I've also added a JS Bin here:
http://jsbin.com/zofive/edit
Here's the answer - I needed to set the model using the Application Route, setupController and controllerFor.
import Ember from 'ember';
export default Ember.Route.extend({
setupController: function() {
this.controllerFor('trips/new').set('model', this.store.createRecord('trip'));
}
});
JS Bin (non-ES6): http://jsbin.com/zofive/13
Thanks to #locks, #abuiles and #xymbol.

How should I be loading a related model into a template with Ember?

I've only just started using Ember and am struggling with a couple of concepts. I used Knockout for a previous application which was great, and I can already see how Ember will help me be more structured and write less code. Although I could go back to Knockout and get this up and running pretty quickly, I really want to persevere with Ember.
The bit I'm struggling with is the right way and place to load related records. My app has a list of systems, and a list of suppliers related to each system. Each system may have one or more suppliers, and vice-versa.
So far I have a route called Systems with a corresponding controller and template. The model hook on the route gets the list of systems from an API (I'm using Ember Data). The systems are displayed as a list via the template, and when the user selects one (via a radio button) I need to load and display the related suppliers. That's the bit I'm not sure about.
I'm not changing the URL here, just displaying an additional list of suppliers underneath the list of systems so adding a new route doesn't sound quite right. Conceptually what would be the best way to do this?
Should I use an action in the controller to load and display the data? That doesn't sound quite right either as the model hooks are all in the routes.
Should I just load the data all up front and filter it (and toggle display) using the controller?
Is there a right, or better, way?
Here's the current code.
System Model
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
suppliers: DS.hasMany('supplier')
});
Supplier Model
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
systems: DS.hasMany('system')
});
Systems Route
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
return this.store.find('system');
}
});
Systems Controller
import Ember from 'ember';
export default Ember.ObjectController.extend({
systemSelected: false,
actions: {
selectSystem: function(id) {
this.set('systemSelected', true);
console.log(id);
},
selectSupplier: function(supplier) {
console.log(supplier);
}
}
});
Systems Template
<h1>systems</h1>
<form id="systems">
<fieldset>
<div class="form-group">
<label for="system" class="control-label">Select the system</label>
{{#each}}
<div class="radio">
<label>
<input type="radio" name="system" value="{{id}}" {{action 'selectSystem' id}}>
<span>{{name}}</span>
</label>
</div>
{{/each}}
</div>
{{#if systemSelected}}
<div class="form-group">
<label for="supplier" class="control-label">Select the supplier</label>
{{#each suppliers}}
<div class="radio">
<label>
<input type="radio" name="supplier" value="{{id}}" {{action 'selectSupplier' supplier}}>
<span>{{name}}</span>
</label>
</div>
{{/each}}
</div>
{{/if}}
</fieldset>
</form>
The way to handle this is pretty simple. You just load both related models in your route. You do that using RSVP.
First of all, we'll need a Suppliers Controller, but just a really basic one to store all the Suppliers:
App.SuppliersController = Ember.ArrayController.extend();
In your Systems Route, fetch the models like this and set them to the correct model properties:
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
return Ember.RSVP.hash({
systems: store.find('system'),
suppliers: store.find('suppliers')
});
},
setupController(controller,models) {
this.controllerFor('systems').set('model',models.systems);
this.controllerFor('suppliers').set('model',models.suppliers);
}
});
Then in your Systems Controller, add the Suppliers Controller to the needs:
import Ember from 'ember';
export default Ember.ObjectController.extend({
needs:['systems'],
systemSelected: false,
actions: {
selectSystem: function(id) {
this.set('systemSelected', true);
console.log(id);
},
selectSupplier: function(supplier) {
console.log(supplier);
}
}
});
You might want to use an ArrayController instead of an ObjectController for Systems Controller since it stores more than one object.
Then in your Systems Template, you can access the Systems Controller as a property of your main controller, like this:
<h1>systems</h1>
<form id="systems">
<fieldset>
<div class="form-group">
<label for="system" class="control-label">Select the system</label>
{{#each system in controllers.systems}}
<div class="radio">
<label>
<input type="radio" name="system" value="{{id}}" {{action 'selectSystem' id}}>
<span>{{name}}</span>
</label>
</div>
{{/each}}
</div>
{{#if systemSelected}}
<div class="form-group">
<label for="supplier" class="control-label">Select the supplier</label>
{{#each suppliers}}
<div class="radio">
<label>
<input type="radio" name="supplier" value="{{id}}" {{action 'selectSupplier' supplier}}>
<span>{{name}}</span>
</label>
</div>
{{/each}}
</div>
{{/if}}
</fieldset>
</form>
There ya go. Hope that helps. There are other ways to do this, but this is the most Emberific way to do it that I know.

Change a view property from an unrelated controller

I have the following view:
App.MessageTrayView = Bootstrap.AlertMessage.extend({
message: 'This is a message.',
});
Displayed in this template:
<script type="text/x-handlebars" data-template-name="nodes">
<article>
<form class="form-horizontal">
<fieldset>
{{view App.MessageTrayView id="message-tray-view"}}
<div id="legend" class="">
<legend class="">Nodes <span class="badge">{{controllers.nodesIndex.length}} records</span>
<div class="pull-right">
<a {{action destroyAllRecords}}><i class="icon-remove-circle"></i><a/>
{{#linkTo "nodes.new" class="btn btn-primary"}}Add Node{{/linkTo}}
</div>
</legend>
</div>
{{outlet}}
</fieldset>
</form>
</article>
</script>
And this unrelated controller:
App.NodesIndexController = Ember.ArrayController.extend({
destroyAllRecords: function () {
console.log('destroyAllRecords called');
App.MessageTrayView.set('message', 'All nodes have been deleted');
},
});
I want to change the message displayed as soon as the destroyAllRecords is triggered. This is not working (the error message in the console is telling me that I am doing something * very* wrong). How can I change the message property, so that the changes are directly visible on the page?
You can see the code live here
One quick way of doing this could be to define a property on the App namespace:
App = Ember.Application.create({
messageTrayContent: ''
});
then bind to it in your view using the suffix Binding after your property name:
App.MessageTrayView = Bootstrap.AlertMessage.extend({
messageBinding: 'App.messageTrayContent'
});
Now doing:
App.NodesIndexController = Ember.ArrayController.extend({
destroyAllRecords: function () {
console.log('destroyAllRecords called');
App.set('messageTrayContent', 'All nodes have been deleted');
},
});
should work.
Hope it helps.

EmberJS: How to update model attributes

I've got a list of messages that are provided by a Rails backend. What I need is when the "toggle_visibility" action button is pressed, it would toggle the "publicly_viewable" property. This means, making a corresponding REST call (to effect the database) and changing the state of the corresponding cached message. Here is where I'm at so far.
Here's what I've got so far, that manages to end up on the debug console:
# app.js
App.Store = DS.Store.extend({
revision: 12,
adapter: DS.RESTAdapter.extend({
url: 'http://localhost:3000'
})
});
App.Message = DS.Model.extend({
body: DS.attr('string'),
mobile_number: DS.attr('string'),
publicly_viewable: DS.attr('boolean'),
created_at: DS.attr('date')
});
App.Router.map(function() {
this.resource('messages');
});
App.MessagesRoute = Ember.Route.extend({
model: function() { return App.Message.find() }
});
App.MessagesController = Ember.ArrayController.extend({
toggle_visibility: function(){
debugger;
}
});
# index.html
{{#each model}}
<button class="close" {{action toggle_visibility this}}><i class="icon-eye-close"></i></button>
<p class="message_body lead">{{body}}</p>
<small class="source_number">from {{mobile_number}}, received {{date created_at}}</small>
{{/each}}
I've been spending the past few hours reading through the Ember Guides and while I've gotten an idea on what the different classes there are, I still can't visualize clearly how to go about it. Particularly, I'm not sure if this should be a route concern or a controller, and I know that if ever it was a controller responsibility, I know that it should be on an ObjectController but I've been having trouble making it work.
You can use ArrayController#itemController and define a controller for the individual record in your ModelArray. Then you have to specify in the Array Controller the Object Controller responsible for a single object, which you have to reference as well in Handlebars. You can do something like this:
JS:
App.MessageController = Ember.ObjectController.extend({
visibilityClass: function() {
var visibility = this.get('model.publiclyViewable');
return 'toggle-visibility mdi-action-visibility%#'.fmt(
visibility ? '':'-off'
);
}.property('model.publiclyViewable'),
actions: {
toggleVisibility: function() {
var model = this.get('model');
model.toggleProperty('publiclyViewable');
model.save();
}
}
});
Handlebars:
<script type="text/x-handlebars" data-template-name="messages">
<!--
At this point the {{each}} helper will know how to lookup for
the controller simply by it's name
-->
{{#each model itemController="message"}}
<div class="panel panel-primary">
<div class="panel-heading">
<div class="pull-left">
<h3 class="panel-title">{{title}}</h3>
</div>
<div class="pull-right">
<a {{action 'toggleVisibility'}}>
<i class={{visibilityClass}} style="color: #FFF"></i>
</a>
</div>
</div>
<div class="panel-body">
{{body}}
</div>
<div class="panel-footer">
<i class="mdi-communication-quick-contacts-dialer"></i> {{mobileNumber}}
<i class="mdi-notification-event-note"></i> {{createdAt}}
</div>
</div>
{{/each}}
</script>
(see fiddle)
Note: Updated to Ember 1.11.x-beta and changed the code a little bit