counter that counts on page loads - ember.js

I am trying to build a simple counter that will count from 0 to 100 on page load.Here is some that i came out with but does not work. I'll appreciate all the help thanks
App.AboutController = Ember.ObjectController.extend({
count: 0,
content: 0,
counterUp: function() {
var count = this.get('count') + 1;
this.set('count', count);
this.set('content', count % 100);
}
});

This seems like a great candidate for a component:
component
App.TimerCounterComponent = Ember.Component.extend({
tick: function() {
this.incrementProperty('count');
if (Ember.isEqual(this.get('count'), 100)) {
this.killCounter();
} else {
this.scheduleTick();
}
},
scheduleTick: function() {
this._counter = Ember.run.later(this, 'tick', 600);
},
setup: function() {
this.set('count', 0);
this.scheduleTick();
}.on('didInsertElement'),
killCounter: function() {
Ember.run.cancel(this._counter);
}.on('willDestroyElement')
});
component template
Count: {{count}}
then in any template you want to use the component:
{{timer-counter}}
EDIT: tidied up the jsbin code a bit
Bonus round: full js-bin example

You cannot make the counting on controller's property because it would always re-initialize and you would have always zero. You can access the application controller however and this way the property will not reset:
// better use Controller because ObjectController is getting deprecated
App.AboutController = Ember.Controller.extend({
needs: ['application'],
count: Ember.computed.alias('controllers.application.count'),
content: Ember.computed.alias('controllers.application.content'),
counterUp: (function() {
count = this.get('count') + 1 || 1;
this.set('count', count);
this.set('content', count % 100);
}).on('init')
});
If you want, you can initialize count and content properties like this:
App.ApplicationController = Ember.Controller.extend({
count: 0,
content: 0
})
If you have used the proxying behavior of ObjectController use can replace Controller with it.

As things are, your counterUp method has been declared and defined, but is never called. You have to call it in an appropriate hook. In http://emberjs.jsbin.com/wuxesosadi/1/edit?html,css,js,output you find an example that counts whenevere you click on the number.
You want to count "on page load" - a full page loade which restarts the application will always reset the counter. If you mean a transition into the route, http://emberjs.jsbin.com/wuxesosadi/4/edit?html,css,js,output would be a working example.
That said, for real world problems you would probably have a counter service or the data in a model. The controller should only hold presentation logic. And even though the controller is held in memory and re-used (as a singleton), this feels very wrong. Then again, as an example of how things work, this is perfectly valid.

Related

How to clear the DOM with multiple tests in single ember integration test file

I have two tests in date-dropdown-test.js:
moduleForComponent('forms/date-dropdown', 'Integration | Component | forms/date dropdown', {
integration: true
});
test('it renders in month mode', function(assert) {
assert.expect(2);
// Set any properties with this.set('myProperty', 'value');
// Handle any actions with this.on('myAction', function(val) { ... });
this.render(hbs`{{forms/date-dropdown dateFormat='MMMM YYYY' daySpecific=false dayToUse=26 dateUnitsLong=24 startIncrement=1}}`);
// Check 24 months appear
assert.equal(this.$('option').length, 24);
// Check next month is selected by default
var today = new Date();
today.setDate(1);
today.setMonth(today.getMonth() + 1);
today.setDate(26);
var expected = moment(today).format('DD-MM-YYYY');
assert.equal(this.$('select').val(), expected);
});
test('it renders in day mode', function(assert) {
assert.expect(1);
// Set any properties with this.set('myProperty', 'value');
// Handle any actions with this.on('myAction', function(val) { ... });
this.render(hbs`{{forms/date-dropdown dateFormat='MMMM YYYY' daySpecific=true dateUnitsLong=300 startIncrement=3}}`);
// Check 300 days appear
assert.equal(this.$('option').length, 300);
});
The problem I have is when the second test runs the component from the first test is still in the DOM and found by this.$('option').length. What is the correct way to clear the DOM between or at the end of tests in Ember testing?
Or is there something more specific than this.$() to use in the context of the component rendered in the test?
EDIT
Even more confusing to me is the fact that it seems to work fine at https://github.com/yapplabs/ember-radio-button/blob/master/tests/unit/components/radio-button-test.js with multiple tests and looking at the dom, but in my second test I definitely see 324 option elements in .length instead of the 300 added by that particular component.
EDIT 2
The components code is:
export default Ember.Component.extend({
dateItems: [],
didInitAttrs: function() {
var today = new Date();
var dateItems = this.get('dateItems');
var i = 0;
if (this.get('daySpecific')) {
for (i = 0; i < this.get('dateUnitsLong'); i++) {
var nextDay = new Date();
nextDay.setDate(today.getDate() + i);
dateItems.addObject({date: nextDay, value: moment(nextDay).format('DD-MM-YYYY')});
}
}
else {
for (i = 0; i < this.get('dateUnitsLong'); i++) {
var nextMonth = new Date();
nextMonth.setDate(1);
nextMonth.setMonth(today.getMonth() + i);
nextMonth.setDate(this.get('dayToUse'));
dateItems.addObject({date: nextMonth, value: moment(nextMonth).format('DD-MM-YYYY')});
}
}
},
didInsertElement: function() {
var startDate = new Date();
if (this.get('daySpecific')) {
startDate.setDate(startDate.getDate() + this.get('startIncrement'));
}
else {
startDate.setDate(1);
startDate.setMonth(startDate.getMonth() + this.get('startIncrement'));
startDate.setDate(this.get('dayToUse'));
}
this.$('select').val(moment(startDate).format('DD-MM-YYYY')).trigger('change');
},
actions: {
dateChange: function() {
this.set('value', this.$('select').val());
}
}
});
hbs
<select class="form-control" {{action 'dateChange' on='change'}}>
{{#each dateItems as |dateItem index|}}
<option value="{{dateItem.value}}">
{{date-formatter dateItem.date dateFormat}}
</option>
{{/each}}
</select>
The idea is to create a reusable component that creates a dropdown of months or days for a given period of time and allows a default of something other than the first day/month. So looking at the first test above {{forms/date-dropdown dateFormat='MMMM YYYY' daySpecific=false dayToUse=26 dateUnitsLong=24 startIncrement=1}}would create a dropdown with 24 months from this month and default to next month.
In any case I wonder whether the final line: this.$('select').val(moment(startDate).format('DD-MM-YYYY')).trigger('change'); of didInsertElement is the offender here? Perhaps the tests continue on but this stops the teardown in the test?
The two tests pass individually if I remove one or the other.
Edit 3
Removing this.$('select').val(moment(startDate).format('DD-MM-YYYY')).trigger('change'); didnt help, perhaps its my use of didInitAttrs to create my dateItems that the #each of the template uses?
I've tried with a very simple application and integration tests seem to work here.
import { moduleForComponent, test } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';
moduleForComponent('x-div', 'Integration | Component | x div', {
integration: true
});
test('it renders', function(assert) {
assert.expect(2);
// Set any properties with this.set('myProperty', 'value');
// Handle any actions with this.on('myAction', function(val) { ... });
this.render(hbs`{{x-div}}`);
assert.equal(this.$().text(), '', 'should be an empty string, got "'+this.$().text()+'"');
// Template block usage:
this.render(hbs`
{{#x-div}}
template block text
{{/x-div}}
`);
console.log(this.$('.thediv').length);
assert.equal(this.$().text().trim(), 'template block text');
});
test('it renders again', function(assert) {
assert.expect(2);
// Set any properties with this.set('myProperty', 'value');
// Handle any actions with this.on('myAction', function(val) { ... });
this.render(hbs`{{x-div}}`);
assert.equal(this.$().text(), '');
// Template block usage:
this.render(hbs`
{{#x-div}}
template block text
{{/x-div}}
`);
console.log(this.$('.thediv').length);
assert.equal(this.$().text().trim(), 'template block text');
});
this.$('.thediv').length returns 1 in both tests, teardown is done properly.
Could the component code make tests fail by any chance?
I thought this might be a bug with didInitAttrs so posted an issue on Github (http://rwjblue.jsbin.com/docepa/edit?html,js,output). It turns out that the issue was in a single line of my code:
dateItems: [],
The array is being shared on the component's prototype. As https://dockyard.com/blog/2014/04/17/ember-object-self-troll explains:
"When you don't pass any properties to create (props), all instances of the Object will share the same prototype. That's pretty much the gist of the prototypical inheritance. It means that any changes on one object will reflect on the others. That explains the behaviour in the first example."
If I had put the component on the same page twice then I would have also seen the issue outside of the test environment.
So I had two choices to resolve this, turn dataItems into a computed property or use Init. I used init like so:
dateItems: null,
init: function() {
this._super.apply(this, arguments);
this.dateItems = [];
},
dateItems are now kept nicely distinct between component instances.

Accepted practice to reset a bunch of controllers

In several situations in my App I need to reset a bunch of controllers (in ajax replies, ...). This happens at different points in the application, so I do not have always a route/controller/view available. I want to have a single easy-to-use function to reset everything, no parameters needed.
This is what I am trying to replace (very old version of ember):
function resetApp() {
Dashboard.router.totalCallsController.set("content", 0);
Dashboard.router.totalMinutesController.set("content", 0);
Dashboard.router.callDurationAvgController.set("content", 0);
};
And now I am doing:
function resetApp() {
var container = Dashboard.__container__;
container.lookup('controller:totalCalls').set("content", 0);
container.lookup('controller:totalMinutes').set("content", 0);
container.lookup('controller:callDurationAvg').set("content", 0);
}
But according to this answer the container should not be used for that. How can I get a controller if I only have access to the application object? Before it was just easy: in Dasboard.router we could access all controllers directly. Now only via the container, but this is not advised, and probably will change (API is n flux). So what can I do?
If you're going for the most Emberish way, but want the function to live outside of Ember, I would suggest the observer pattern. In an essence anytime a controller is created you subscribe it to the global reset function. There are a few ways to accomplish this, I'd probably do it with a mixin.
Here's a small example:
App.ResettableMixin = Em.Mixin.create({
init: function(){
this._super();
App.Resettable.join(this.get('resetMe'), this);
}
});
App.IndexController = Em.ArrayController.extend(App.ResettableMixin,{
resetMe: function(){
console.log('reset index controller');
}
});
App.FooController = Em.ObjectController.extend(App.ResettableMixin,{
resetMe: function(){
console.log('reset foo controller ' + this.get('model'));
}
});
App.Resettable = {
resetters: [],
join: function(methodToCall, context){
this.resetters.push({method: methodToCall, context:context});
},
reset: function(){
var item;
console.log('resetting ' + this.resetters.length + ' items');
for(var i = 0, len = this.resetters.length; i<len;i++){
item = this.resetters[i];
item.method.apply(item.context);
}
},
remove: function(methodToFind){
// iterate through find, and remove it from resetters
}
};
http://emberjs.jsbin.com/hudugene/4/edit

Do something when Ember component is instantiated?

I call a component like this:
{{Gd-text-input label="Specify" name="Specify" key="entry.810220554" triggerKey="tada" hideIf="Client"}}
I would like to run some javascript-code that sets an additional property to this component.
What I'm trying to run is something like this
//Convert string ot array.
GdRadioInput = Em.Component.extend({
init: function(){
var cs = this.get('contentString');
console.log('doesthiswork?');
if(cs){
this.set('content', eval(cs));
}
}
});
But it doesn't run. If someone could just provide a sample that console.logs a the value of a property of a component whenever that component is created, that would be very helpful.
You can run this code in the init method
init:function(){
this._super();
hideIf = this.get('hideIf');
key = this.get('key')
if(hideIf === key){
this.set('class', 'hide');
}
}
Good luck
PD: now this method is private: http://emberjs.com/api/classes/Ember.Component.html#method_init
I know this is an old question, but I wanted to update it with the new way to do things. Instead of overriding the init function, you can now cause a function to run on initialization with .on('init'). Example:
GdRadioInput = Em.Component.extend({
setupFunc: function(){
var cs = this.get('contentString');
console.log('doesthiswork?');
if(cs){
this.set('content', eval(cs));
}
}.on('init')
});
A follow-up: Just in case you are depending on a fully loaded DOM tree, you can use .on('didInsertElement') instead of on('init'):
GdRadioInput = Em.Component.extend({
setupFunc: function(){
var cs = this.get('contentString');
console.log('doesthiswork?');
if(cs){
this.set('content', eval(cs));
}
}.on('didInsertElement')
});
That event is fired when the view's element has been inserted into the DOM: http://emberjs.com/api/classes/Ember.Component.html#event_didInsertElement

Ember-Router: How to add a route in run-time in Ember 1.0-rc2?

In the new Ember.Router that shipts with Ember 1.0-rc2, is it possible add route in run-time?
There is not a supported method of doing this currently. The App.Router.map call is handled by lines 235-247 of this code: https://github.com/emberjs/ember.js/blob/master/packages/ember-routing/lib/system/router.js
Ember.Router.reopenClass({
map: function(callback) {
var router = this.router = new Router();
var dsl = Ember.RouterDSL.map(function() {
this.resource('application', { path: "/" }, function() {
callback.call(this);
})
});
router.map(dsl.generate());
return router;
}
The map is overwritten every time you call Router.map as the callback for the previous call to Router.map is not persisted.
Edit
For better or worse, I've got a pull request in to alter the behavior to allow multiple calls to App.Router.map. We'll see what happens. You can follow here https://github.com/emberjs/ember.js/pull/2485
Another Edit
I've written a gist to do what my pull request does in userland. This will let you map routes at runtime. Just add this code and then replace your calls to App.Router.map with the method that I've defined
https://gist.github.com/grep-awesome/5406461
Answer Changing Edit
As of this pull request, you may now call map multiple times. https://github.com/emberjs/ember.js/pull/2892
I see that wmarbut's answer hasn't been accepted, but it is a good one (for me). It seems that his patch is on its way into the Ember release, but until then this is some code that uses his patch. (Don't accept my answer, I'm just happy to have found this.) I'm planning to use it as part of a solution to let content drive navigation. Good question, user1517325 and thanks, wmarbut!
// was an all-in-one router map as Ember likes it
// App.Router.map(function() {
// this.resource("foods", function(){
// this.route("index", {path: "/"});
// });
// this.route("fourOhFour", { path: "*:"});
// });
//wmarbut's workaround until his patch is applied
App.map_routes = [];
App.MapRoutes = function(routes) {
App.map_routes.push(routes);
return App.Router.map(function() {
var route_lamda, _i, _len, _ref;
_ref = App.map_routes;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
route_lamda = _ref[_i];
route_lamda.call(this);
}
return true;
});
};
//partial mapping
App.MapRoutes(function() {
this.resource("foods", function(){
});
});
//some more mapping
App.MapRoutes(function() {
this.resource("foods", function(){
this.route("index", {path: "/"});
});
});
//even more mapping
App.MapRoutes(function() {
this.route("fourOhFour", { path: "*:"});
});
In the latest release ember.js rc7 it was added the functionality to the Router.map to allow it to be called multiple times without the map being overwritten. This will allow routes to be added at runtime.
Hope it helps.

Trouble with lazy loading in emberjs

I've been trying to implement lazy loading in emberjs by following this answer: https://stackoverflow.com/a/11925918/341358 . But I'm stuck when ember loads the initial dataset. For some reason the first ajax call keeps calling: /newsitems instead of only the first page: /newsitems?page=1. From then on, the loadmore functionality works great, returning me a limited data set for page 2, 3, 4, ...
So my question is: how do I make sure that only the items for the first page are loaded and not all of them at once?
Here's how my route looks like:
App.NewsitemsRoute = Ember.Route.extend({
model: function() {
return App.Newsitem.find();
}
});
Here's my controller:
App.NewsitemsController = Ember.ArrayController.extend({
currentPage: 1,
canLoadMore: function() {
return this.get('currentPage') < 10;
}.property('currentPage'),
loadMore: function() {
if (this.get('canLoadMore')) {
this.set('isLoading', true);
var page = this.incrementProperty('currentPage');
this.get('store').findQuery(App.Newsitem, {page:page});
}
else
{
this.set('isLoading', false);
}
}
});
Can you change your route to include a default page number of 1?
e.g.
App.NewsitemsRoute = Ember.Route.extend({
model: function() {
var controller = this.controllerFor('Newsitems');
return App.Newsitem.find({page: controller.get('currentPage')});
}
});
Edit: What if you get the page number from the controller?