Ember has the following implementation of setTimeout, which it is recommended developers use because the code gets added to the run loop, which has advantages for tests.
Ember.run.later((function() {
console.log("will run once after 1000");
}), 1000);
Is there a similar Ember replacement for setInterval and, by implication, clearInterval (which is used to cancel a setInterval)? I need to run someFunc every 1000 ms
this.intervalId = setInterval(this.someFunc.bind(this), 1000);
I'm not aware of any equivalent, but I use such a code to have the functionality:
var Poller = Ember.Object.extend({
_interval: 1000,
_currentlyExecutedFunction: null,
start: function(context, pollingFunction) {
this.set('_currentlyExecutedFunction', this._schedule(context, pollingFunction, [].slice.call(arguments, 2)));
},
stop: function() {
Ember.run.cancel(this.get('_currentlyExecutedFunction'));
},
_schedule: function(context, func, args) {
return Ember.run.later(this, function() {
this.set('_currentlyExecutedFunction', this._schedule(context, func, args));
func.apply(context, args);
}, this.get('_interval'));
},
setInterval: function(interval) {
this.set('_interval', interval);
}
});
export
default Poller;
Then, you instantiate the poller: var poller = Poller.create() and then you can play with poller.start() and poller.stop() + set set the interval via poller.setInterval(interval).
In my code, I did more or less that way (polling the reports every 10 seconds):
_updateRunningReport: function(report) {
var poller = new Poller();
poller.setInterval(this.POLLING_INTERVAL);
poller.start(this, function() {
if (report.isRunning()) {
this._reloadReport(report);
} else {
poller.stop();
}
});
eventBus.onLogout(function() {
poller.stop();
});
},
Hope this helps...
I have been playing around with implement "setInterval" in Ember Octane way and I think this is THE MOST SIMPLE and CLEAN solution:
index.hbs:
{{this.counter}}
<button {{on "click" this.start}}>Start</button>
<button {{on "click" this.stop}}>Stop</button>
<button {{on "click" this.reset}}>Reset</button>
controllers/index.js
import Controller from '#ember/controller';
import { tracked } from '#glimmer/tracking';
import { action } from '#ember/object';
import { cancel, later, next } from '#ember/runloop';
export default class IndexController extends Controller {
#tracked counter = 0;
runner = null;
#action start() {
next(this, function () {
this.runner = this.tick();
});
}
#action stop() {
cancel(this.runner);
}
#action reset() {
this.counter = 0;
}
tick() {
return later(this, function () {
this.counter++;
this.runner = this.tick();
}, 1000);
}
}
Another option is to use the ember-poll add-on. Internally, it does somewhat the same as #andrusieczko Poller object (both take the Ember Run Loop into account). From an Ember developer view, you get access to a poll service.
Just install the add-on: npm install ember-poll --save-dev.
Example:
import Ember from 'ember';
export default Ember.Route.extend({
poll: Ember.service.inject(),
setupController() {
this._super(...arguments);
this.get('poll').addPoll({
interval: 1000,
label: 'my-poll',
callback: () => {
//do something every second
}
});
},
actions: {
willTransition() {
this.get('poll').clearPollByLabel('my-poll');
}
}
}
Tested in ember -v 1.18
This is what I've been doing inside my component. This works perfectly fine and should be the ideal way of approaching. If you're handling async promise objects inside the interval, make sure to kill it in willDestroyElement() hook or somewhere.
init() {
this._super(...arguments);
this.startAutoSaveTimer();
},
startAutoSaveTimer() {
let autosaveFunc = function() {
console.info('Arr .. I'm a pirate!');
};
set(this, 'timerInstance', setInterval(autosaveFunc, 1000));
},
Related
I'm writing my first question here sorry for any ambiguity.
I write an integration test for update-pw component which simple render update-pw and then fill input field with fillIn and then click save button which trigger the action savePW in update-pw.js. I only pass email(for whom we want to change password) and new password.
savePW() function further has a function call self.store.updateSingleUserPw(email, newPw) which is written in service store.js.
updateSingleUserPw(email, newPw) returns a promise after server process on API call. On basis of fulfillment or rejection of promise I show a modal.
I just want to make that promise fulfill or rejected in my test instead of server response for promise.
// integration/component/update-pw-test.js
import { module, test } from 'qunit';
import EmberObject from '#ember/object';
import { setupRenderingTest } from 'ember-qunit';
import { render, fillIn, click } from '#ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
import Service from '#ember/service';
module('Integration | Component | update-pw', function(hooks) {
setupRenderingTest(hooks);
const store = Service.extend({
savePW() {
self.store.updateSingleUserPw(email, newPw, function() {
console.log('this is function overriding', email, newPw);
return true;
})
.then(function() {
// Reset controller fields
self.set('password', '');
self.set('updateModal', false);
swal({
title: 'Das hat geklappt',
type: 'success'
});
}, function() {
self.set('updateModal', false);
swal({
title: 'problems with setting new pw.',
type: 'error'
});
})
.finally(function() {
self.set('changingPassword', false);
});
}
});
test('it renders', async function(assert) {
this.application.register('service:store', store);
this.application.inject.service('store', { as: 'store' });
assert.expect(2);
this.set('updateModal', true);
this.set('testing', true);
let currentUpdateAdmin = EmberObject.create({
username: 'steinauer',
email: 'lala#test.at'
});
this.set('currentUpdateAdmin', currentUpdateAdmin);
await render(hbs`{{update-pw updateModal=updateModal currentUpdateAdmin=currentUpdateAdmin testing=testing store=store}}`);
assert.equal(this.element.querySelector('h4').textContent.trim(), 'set new PW for steinauer');
await fillIn('#password', 'test123456');
await click('.save-button');
// Template block usage:
await render(hbs`
{{#update-pw}}
template block text
{{/update-pw}}
`);
// assert.equal(this.element.textContent.trim(), 'what is this');
});
});
// components/update-pw.js
import Component from '#ember/component';
export default Component.extend({
changingPassword: false,
actions: {
savePW() {
let self = this;
if (!self.get('currentUpdateAdmin.email'))
return;
let newPw = self.get('password');
let email = self.get('currentUpdateAdmin.email');
self.set('changingPassword', true);
if (!email)
return;
self.store.updateSingleUserPw(email, newPw)
.then(function() {
// Reset controller fields
self.set('password', '');
self.set('updateModal', false);
swal({
title: 'Das hat geklappt',
type: 'success'
});
}, function() {
self.set('updateModal', false);
swal({
title: 'problems with setting new pw',
type: 'error'
});
})
.finally(function() {
self.set('changingPassword', false);
});
}
}
});
function in Service/store.js :
updateSingleUserPw(email, newPw) {
let headers = this.get('headers');
return new Promise(function(resolve, reject) {
$.ajax({
type: 'POST',
url: ENV.api + '/accounts/updateSingleUserPw',
data: {
email: email,
pwNew: newPw
},
headers,
dataType: 'json'
}).then(function(success) {
if (success) {
resolve(newPw);
} else {
reject('password change failed');
}
}, function(xhr, status, error) {
reject(error);
});
});
}
Before trying to override function I got only rejected promise modal but after the try of overriding the function i'm getting:
Promise rejected during "it renders": Cannot read property register of undefined.
thanks for your question š
Firstly can I thank you for providing your code samples, I would not have been able to solve your question had you not provided so much! I have actually simplified some of the things that you are trying to do and I think by simplifying things I have come to the solution.
Firstly I have renamed the Service that you keep using to be called password-store. Usually when an Ember developer sees a Service named store they tend to think of an ember-data store which I'm assuming you're not actually using here by the functionality that you are expecting.
I generated a very simple mock store that just had one function in it:
// app/services/password-store.js
import Service from '#ember/service';
export default Service.extend({
updateSingleUserPw(email, password) {
// TODO: do something with email & password
return Promise.resolve();
}
});
This just returns a promise so that it won't break any of the other code samples. I then updated your update-pw component to use the new password store:
// app/components/update-pw.js
import Component from '#ember/component';
import { inject as service } from '#ember/service';
function swal() {
// noop - not sure where this comes from
}
export default Component.extend({
passwordStore: service(),
changingPassword: false,
actions: {
savePW() {
if (!this.get('currentUpdateAdmin.email'))
return;
let newPw = this.get('password');
let email = this.get('currentUpdateAdmin.email');
this.set('changingPassword', true);
if (!email)
return;
this.passwordStore.updateSingleUserPw(email, newPw)
.then(() => {
// Reset controller fields
this.set('password', '');
this.set('updateModal', false);
swal({
title: 'Das hat geklappt',
type: 'success'
});
}, () => {
this.set('updateModal', false);
swal({
title: 'problems with setting new pw',
type: 'error'
});
})
.finally(() => {
this.set('changingPassword', false);
});
}
}
});
I also added a swal() function because I didn't quite know where that came from in your example. It seemed to be missing so I just ignored it.
Now lastly I have setup a template so that the test will actually pass:
// app/templates/components/update-pw.hbs
<h4>set new PW for steinauer</h4>
{{input id="password" value=password}}
<button type="button" name="button" class="save-button" {{action 'savePW'}}></button>
Now with the application fully setup here is the full example of a test that will do exactly what you were hoping to do:
// tests/integration/components/update-pw-test.js
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render, fillIn, click } from '#ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
import StoreService from 'your-app-name/services/password-store';
module('Integration | Component | update-pw', function(hooks) {
setupRenderingTest(hooks);
test('it renders', async function(assert) {
const passwordStore = StoreService.extend({
updateSingleUserPw(email, newPw) {
console.log('updateSingleUserPw override!!');
assert.equal(newPw, 'test123456');
return Promise.resolve();
}
});
this.owner.register('service:password-store', passwordStore);
assert.expect(2);
this.set('updateModal', true);
this.set('testing', true);
let currentUpdateAdmin = {
username: 'steinauer',
email: 'lala#test.at'
};
this.set('currentUpdateAdmin', currentUpdateAdmin);
await render(hbs`{{update-pw updateModal=updateModal currentUpdateAdmin=currentUpdateAdmin testing=testing store=store}}`);
assert.equal(this.element.querySelector('h4').textContent.trim(), 'set new PW for steinauer');
await fillIn('#password', 'test123456');
await click('.save-button');
// Template block usage:
await render(hbs`
{{#update-pw}}
template block text
{{/update-pw}}
`);
});
});
The first thing that you might notice is that we are not using this.application.register or this.application.inject. I can't remember exactly if this is how it used to be done a long time ago but this is not available for a few years in Ember.
What we end up doing is we import the StoreService from your-app-name/services/password-store (replacing your-app-name with whatever your modulePrefix is) and then we extend it while overriding the updateSingleUserPw() function. In your example it looked like you were trying to override a function called savePW() but that is actually the action name from the component and it might have been slightly confusing you.
I hope that helps, I have tested the example locally and it works perfectly well! You may also notice I added an assertion inside the service, this is quite a useful pattern to make sure that the service receives the right arguments from the component š
I am new to ember framework. I just want to execute a function that is defined inside the actions hook after the rendering completes.
var Controller = Ember.Controller.extend({
actions: {
foo: function() {
console.log("foo");
}
}
});
Ember.run.schedule("afterRender",this,function() {
this.send("foo");
}
But the above code is not working.
I just want to know, is it possible to run foo() afterRender?
You could use init:
App.Controller = Ember.Controller.extend({
init: function () {
this._super();
Ember.run.schedule("afterRender",this,function() {
this.send("foo");
});
},
actions: {
foo: function() {
console.log("foo");
}
}
});
I'm running into a problem where the Ember application I'm testing doesn't seem to be noticing the models that I'm creating with FactoryGuy. Here's my test file:
import Ember from 'ember';
import startApp from '../helpers/start-app';
import FactoryGuy from 'factory-guy';
import { testMixin as FactoryGuyTestMixin} from 'factory-guy';
import carsFactory from "../fixtures/car";
var application, testHelper, store, make;
var TestHelper = Ember.Object.createWithMixins(FactoryGuyTestMixin);
module('Acceptance: Cars', {
setup: function() {
application = startApp();
testHelper = TestHelper.setup(application);
store = testHelper.getStore();
testHelper.make('car');
},
teardown: function() {
Ember.run(function() { testHelper.teardown(); });
Ember.run(application, 'destroy');
}
});
test('visiting /cars', function() {
equal(store.all('car').get('content.length'), 1);
visit('/cars');
andThen(function() {
equal(currentPath(), 'cars');
var li = find('li');
equal(li.length, 2);
});
});
The first and second equal assertions will succeed, but the last one will fail. Here's what my template looks like:
<ul>
{{#each car in model}}
<li>{{car.label}}</li>
{{/each}}
</ul>
And my route:
import Ember from 'ember';
export default Ember.Route.extend({
model: function () {
this.store.find('car');
}
});
What am I missing in getting the Ember app's store to get properly populated by the FactoryGuy's make method?
Edit: I also have tried adding the following line at the top of the test method and in the setup function, and it still isn't working correctly.
testHelper.handleFindMany('car', 1);
EmberDataFactoryGuy is now an ember addon, so if you are using that then the test would look like this:
import Ember from 'ember';
import startApp from '../helpers/start-app';
import { make } from 'ember-data-factory-guy';
import TestHelper from 'ember-data-factory-guy/factory-guy-test-helper';
var App;
module('Acceptance: Cars', {
setup: function() {
Ember.run(function () {
App = startApp();
TestHelper.setup();
});
},
teardown: function() {
Ember.run(function() {
TestHelper.teardown();
App.destroy();
});
}
});
test('visiting /cars', function() {
TestHelper.handleFindAll('car', 2);
visit('/cars');
andThen(function() {
equal(currentPath(), 'cars');
var li = find('li');
equal(li.length, 2);
});
});
There is a sample acceptance test just like this one in the ember-data-factory-guy repo here ( looks pretty much just like this one though ):
https://github.com/danielspaniel/ember-data-factory-guy/blob/master/tests/acceptance/users-view-test.js
Anyway, there is no more hassle of setting the store, or creating TestHelper, it's all done for you, and setup automatically when you start the application.
Please look at this code...
```
App.BooksRoute = Ember.Route.extend({
model: return function () {
return this.store.find('books');
}
});
App.BooksController = Ember.ArrayController.extend({
actions: {
updateData: function () {
console.log("updateData is called!");
var books = this.filter(function () {
return true;
});
for(var i=0; i<books.length; i++) {
//doSomethingā¦
}
}
}
});
```
I want to call the updateData action on BooksController from the outside.
I tried this code.
App.__container__.lookup("controller:books").send('updateData');
It works actually. But, in the updateData action, the this is different from the one in which updateData was called by clicking {{action 'updateData'}} on books template.
In the case of clicking {{action 'updateData'}}, the this.filter() method in updateData action will return books models.
But, In the case of calling App.__container__.lookup("controller:books").send('updateData');, the this.filter() method in updateData action will return nothing.
How do I call the updateData action on BooksController from the outside, with the same behavior by clicking {{action 'updateData'}}.
I would appreciate knowing about it.
(I'm using Ember.js 1.0.0)
You can use either bind or jQuery.proxy. bind is provided in JS since version 1.8.5, so it's pretty safe to use unless you need to support very old browsers. http://kangax.github.io/es5-compat-table/
Either way, you're basically manually scoping the this object.
So, if you have this IndexController, and you wanted to trigger raiseAlert from outside the app.
App.IndexController = Ember.ArrayController.extend({
testValue : "fooBar!",
actions : {
raiseAlert : function(source){
alert( source + " " + this.get('testValue') );
}
}
});
With bind :
function externalAlertBind(){
var controller = App.__container__.lookup("controller:index");
var boundSend = controller.send.bind(controller);
boundSend('raiseAlert','External Bind');
}
With jQuery.proxy
function externalAlertProxy(){
var controller = App.__container__.lookup("controller:index");
var proxySend = jQuery.proxy(controller.send,controller);
proxySend('raiseAlert','External Proxy');
}
Interestingly this seems to be OK without using either bind or proxy in this JSBin.
function externalAlert(){
var controller = App.__container__.lookup("controller:index");
controller.send('raiseAlert','External');
}
Here's a JSBin showing all of these: http://jsbin.com/ucanam/1080/edit
[UPDATE] : Another JSBin that calls filter in the action : http://jsbin.com/ucanam/1082/edit
[UPDATE 2] : I got things to work by looking up "controller:booksIndex" instead of "controller:books-index".
Here's a JSBin : http://jsbin.com/ICaMimo/1/edit
And the way to see it work (since the routes are weird) : http://jsbin.com/ICaMimo/1#/index
This solved my similar issue
Read more about action boubling here: http://emberjs.com/guides/templates/actions/#toc_action-bubbling
SpeedMind.ApplicationRoute = Ember.Route.extend({
actions: {
// This makes sure that all calls to the {{action 'goBack'}}
// in the end is run by the application-controllers implementation
// using the boubling action system. (controller->route->parentroutes)
goBack: function() {
this.controllerFor('application').send('goBack');
}
},
};
SpeedMind.ApplicationController = Ember.Controller.extend({
actions: {
goBack: function(){
console.log("This is the real goBack method definition!");
}
},
});
You could just have the ember action call your method rather than handling it inside of the action itself.
App.BooksController = Ember.ArrayController.extend({
actions: {
fireUpdateData: function(){
App.BooksController.updateData();
}
},
// This is outside of the action
updateData: function () {
console.log("updateData is called!");
var books = this.filter(function () {
return true;
});
for(var i=0; i<books.length; i++) {
//doSomethingā¦
}
}
});
Now whenever you want to call updateData(), just use
App.BooksController.updateData();
Or in the case of a handlebars file
{{action "fireUpdateData"}}
I'm just in the early stages of learning Ember, and have run into something puzzling.
I'm trying to communicate between two controllers and have their corresponding views update as well.
In a simplified version, I'd like to click a button to fire an event on one controller, which starts a timer on another controller. This works, but the view of the timer is not being updated when the value changes.
Here's what I have:
var App = Ember.Application.create();
App.Route = Ember.Route.extend({
events: {
startTimer: function(data) {
this.get('container').lookup('controller:Timer').start();
}
}
});
App.ApplicationController = Ember.Controller.extend({
actionWord: 'Start',
toggleTimer: function() {
var timer = this.get('container').lookup('controller:Timer');
if(timer.get('running')) {
timer.stop();
} else {
timer.start();
this.set('actionWord', 'Stop');
}
}
});
App.TimerController = Ember.Controller.extend({
time: 0,
running: false,
timer: null,
start: function() {
var self = this;
this.set('running', true);
this.timer = window.setInterval(function() {
self.set('time', self.get('time') + 1);
console.log(self.get('time'));
}, 1000);
},
stop: function() {
window.clearInterval(this.timer);
this.set('running', false);
this.set('time', 0);
}
});
and for the templates:
<script type="text/x-handlebars">
{{ render "timer" }}
<button {{action toggleTimer }} >{{ actionWord }} timer</button>
</script>
<script type="text/x-handlebars" data-template-name="timer">
{{ time }}
</script>
http://jsfiddle.net/mAqYR/1/
UPDATE:
Forgot to mention that if you open the console, you can see the time is being updated inside of the TimeController function, it's just not showing up in the view.
Also, calling the start action on the TimerController directly correctly updates the view.
Thanks!
You were using an out-of-date version of Ember.
I've updated your fiddle to the Ember rc3. Also I've replaced instances of container.lookup with the correct methods. The container is pretty much a private object.
http://jsfiddle.net/3bGN4/255/
window.App = Ember.Application.create();
App.Route = Ember.Route.extend({
events: {
startTimer: function(data) {
this.controllerFor('timer').start();
}
}
});
App.ApplicationController = Ember.Controller.extend({
actionWord: 'Start',
needs: ["timer"],
toggleTimer: function() {
var timer = this.get('controllers.timer');
if(timer.get('running')) {
timer.stop();
} else {
timer.start();
this.set('actionWord', 'Stop');
}
}
});
App.TimerController = Ember.Controller.extend({
time: 0,
running: false,
timer: null,
start: function() {
var self = this;
this.set('running', true);
this.timer = window.setInterval(function() {
self.set('time', self.get('time') + 1);
console.log(self.get('time'));
}, 1000);
},
stop: function() {
window.clearInterval(this.timer);
this.set('running', false);
this.set('time', 0);
}
});