fn.apply is not a function - trying to create a Handlebars helper - ember.js

I create the file app/helpers/test-helper.js:
import Ember from 'ember';
export default Ember.Handlebars.registerBoundHelper('test-helper', function() {
return "Works!";
});
And in the template:
{{test-helper}}
And I get the above error in the console. What am I doing wrong here?

I wrestled with this for awhile myself. The trick is to use makeBoundHelper instead of registerBoundHelper
import Ember from 'ember';
export default Ember.Handlebars.makeBoundHelper('test-helper', function() {
return "Works!";
});
Here's a link to the source code

Related

Ember.js in Component send Action with data to another Component

Component action triggers perfectly.
import Ember from 'ember';
export default Ember.Component.extend({
actions: {
searchField: function() {
console.log('searchField');
this.sendAction('searchField');
}
}
});
Route does not get triggered.
import Ember from 'ember';
export default Ember.Route.extend({
actions: {
searchField: function() {
console.log('ROUTE');
}
}
});
Handlebars
{{input key-up='searchField' searchField=(action "searchField")}}
I have spent so much time with this I am starting to lose interest with Ember.js, as I have also tried according to the documentation, but I get the same result.
http://emberjs.com/api/classes/Ember.Component.html#method_sendAction
sendAction will not reach to route.
You have two options,
define searchField function in controller and from there you can route functionthis.send('searchField')
To directly call from component to route, there is addon ember-route-action-helper for this.
ember install ember-route-action-helper
Refer answer for more info.
To play around - Sample Twiddle

Ember custom conventions

I'm build a list-view, which renders a list of records in a table. The list-view is build as a reusable mixin, and has a reusable template as well. I want the list-view to be as easy to use as possible, and not have to write too much code, to make it work - but only overwrite what I want to change.
Idealy I only want to tell the controller (or perhaps even better) the router, that it's going to render a list-view, and only render custom template, if I have one defined.
Example:
import Ember from 'ember';
import MixinList from '../../mixins/mixin-list';
export default Ember.Route.extend(MixinList, {
model: function() {
return this.store.find('category');
}
});
Currently I have to write this code, to make the list-view work:
Categories route:
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
return this.store.find('category');
}
});
Categories controller:
import Ember from 'ember';
import MixinList from '../../mixins/mixin-list';
export default Ember.Controller.extend(MixinList, {
actions: {
itemAction: function(actionName, item) {
if (actionName === 'edit') {
this.transitionToRoute('categories.edit', item.get('id'));
}
}
}
});
Categories template:
<h1>Categories</h1>
{{partial 'mixin-list'}}
Is it possible to setup conventions, so routes which are using a specific mixin, are given a default controller and template, if they arent added to the project by the user?
After some further research (and some fresh eyes), I found the solution:
import Ember from "ember";
export default Ember.Mixin.create({
renderTemplate: function() {
var currentController = this.container.lookup('controller:' + this.routeName);
if (currentController.isGenerated) {
currentController.reopen(MixinList);
this.render('mixin-list-view');
}
else {
this.render();
}
}
});
That allows me to only define the route, and include the mixin, and let that mixin do the magic:
import Ember from 'ember';
import MixinList from '../../mixins/mixin-list';
export default Ember.Route.extend(MixinList, {
model: function() {
return this.store.find('category');
}
});
The important part here, is the renderTemplate method, and the lookup to the currentController. The currentController exposes a property, that tells if it's autogenerated (not explicitly created by the user). In that case, we can overwrite the rendered template, and even add functionallity to the controller - for example by adding a mixin to the controller (.reopen(...)).

Ember CLI: where to reopen framework classes

I'd like to reopen Ember or Ember Data framework classes. Using Ember CLI, where is the right place to put these so that they get initialized property? Here's an example of something I'd like to do:
import DS from 'ember-data';
DS.Model.reopen({
rollback: function() {
this._super();
// do some additional stuff
}
});
I think the best way to execute modules that have side effects would be to create an initializer. Something like this:
// app/initializers/modify-model.js
import DS from 'ember-data';
let alreadyRun = false;
export default {
name: 'modify-model',
initialize() {
if (alreadyRun) {
return;
} else {
alreadyRun = true;
}
DS.Model.reopen({
// ...
});
}
};
Initializers are automatically run by Ember-CLI, so there's no need to call them yourself.
EDIT: As Karim Baaba pointed out, it's possible for initializers to run more than once. For an easy way around that, I've included an alreadyRun flag.
Using an initializers is sufficient but isn't a good practice for writing tests as they're ran multiple times.
Here is an example of how to reopen the text field view to clear the input when focusIn is triggered
app/overrides/textfield.js:
import Ember from 'ember';
export default Ember.TextField.reopen({
focusIn: function(evt) {
this._super(evt);
this.set('value', '');
}
});
app/app.js
import './overrides/textfield';
The pattern is very simple and can easily be used for DS.Model
Export your content as an ES6 module:
import DS from 'ember-data';
export default DS.Model.reopen({
rollback: function() {
this._super();
// do some additional stuff
}
});
Put the file with your reopen content somewhere like app/custom/model.js, then import the file in app/app.js like this:
import SuperModel from './custom/model';
Now all your models have the custom code.

didInsertElement from controller?

I have an ember application with a controller header.js and a template header.hbs.
Now I have some javascript I need to execute at document $( document ).ready()
I saw on Ember Views there is didInsertElement but how do I do this from the controller?
// controllers/header.js
import Ember from 'ember';
export default Ember.Controller.extend({
});
// views/header.js
import Ember from 'ember';
export default Ember.Controller.extend({
});
// templates/header.js
test
I read several times it's not good practice to be using Ember Views?
the controller is not inserted (the view is) hence there is no didInsertElement.
If you need something to run once, you can write something like this:
import Ember from 'ember';
export default Ember.Controller.extend({
someName: function () { // <--- this is just some random name
// do your stuff here
}.on('init') // <---- this is the important part
});
A more actual answer (Ember 2.18+) while honouring the same principle as mentioned in user amenthes' answer: it's no longer advised to use Ember's function prototype extensions. Instead you can override the init() method of a controller directly. Do note that you'll have to call this._super(...arguments) to ensure all Ember-related code runs:
import Controller from '#ember/controller';
export default Controller.extend({
init() {
this._super(...arguments); // Don't forget this!
// Write your code here.
}
});

Ember CLI - Error when using moment.js in route

I have imported moment.js into my project and it seems to work just fine in my controllers but for some reason it is not working in my routes.
Controller:
// controllers/users.js
import Ember from 'ember';
export default Ember.Controller.extend({
date: function() {
alert(moment().format('X'));
}.property()
...
});
Route:
// routes/users.js
// (Error: /routes/users.js: line 5, col 29, 'moment' is not defined.
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
var data = { start: moment().startOf('month').startOf('day').format('X') };
return this.store.find('event', data);
}
});
Brocfile:
var app = new EmberApp();
app.import('vendor/moment/moment.js');
I guess this is a JsHint Error. You may want to add the following comment to your Route code.
/* global moment:true */
import Ember from "ember";
....
Also (from ember-cli documentation):
If you want to use external libraries that write to a global namespace (e.g. moment.js), you need to add those to the predef section of your project’s .jshintrc file and set its value to true. If you use the lib in tests, need to add it to your tests/.jshintrc file, too.
Then you don't have to do it to every file your using moment.js in.