I'd like to show a loading symbol while my validation is processing. My .hbs file looks like this
<div {{bind-attr class='isProcessing:spinner'}}></div>
I tried to wrap my validations into the afterRender run loop, but apparently afterRender doesn't mean after-CSS. My validations are executed before the css class of the div changes (It doesn't change at all).
App.PostController = Em.Controller.extend({
isProcessing: false,
actions: {
savePost: function() {
this.set('isProcessing', true);
Ember.run.scheduleOnce('afterRender', this, function() {
// do a lot of validating and other (non-asynchronous) stuff here
// ... this may take several seconds
this.set('isProcessing', false);
});
}
}
});
What can I do that my code starts to execute after all CSS rendering is done?
I also tried Ember.run.next. This doesn't work either. Ember.run.later works with a timeout of at least about 200ms but of course I don't want to hardcode any static timespan.
UPDATE: Here is a js-fiddle for my problem: http://jsfiddle.net/4vp2mxr0/2/
Any help would be apprechiated! Thanks in advance!
Please wrap your code in Ember.run.next. Like this:
savePost: function() {
this.set('isProcessing', true);
Em.run.next(this, function() {
//just some synchronous code that takes some time (the time inbetween the two alerts)
var i=0,a;
alert('start code');
while (i++ < 10000000) {
a = Math.pow(i, Math.pow(1/i, Math.sin(i)))/3/4/5/5 * Math.log(i);
}
this.set('isProcessing', false);
alert('finished code');
});
}
It works as intended. I think your problem is that you set isProcessing to true and then immediately set it to false, because you don't wait for async code/promises to complete. In this case you have to set isProcessing to false when promises are resolved. If this is only one promise you can set isProcessing to false inside .then(), for example:
myModel.get('children').then (children) =>
#set 'isProcessing', false
Or if you resolve multiple promises, or you resolve promises inside a loop, you can use Ember.run.debounce which will fire after XXXms after last call to this method, for example:
finishProcessing: ->
#set 'isProcessing', false
actions:
savePost: ->
myModel.get('children').then (children) =>
children.forEach (child) =>
child.then (resolvedChild) =>
Ember.run.debounce #, 'finishProcessing', 500
Related
I am having a problem with action handling in Ember controller. I want to run some function continuously after edit button is clicked in hbs. I have tried it like this in action.
openEditWindow() {
this.set('someChangingValue', true);
},
Here is the function that reacts to action someChangingValue change.
someChangingValue: false,
someTestFunction: observer('someChangingValue', function() {
var test = this.get('someChangingValue');
if(test === true){
Ember.run.later((function() {
this.functionThatIsRunningEachTwoSeconds();
}), 2000);
} else {
console.log('this should not do anything');
}
}),
But this runs functionThatIsRunningEachTwoSeconds only once. Also tried the same functionality with changing someChangingValue to false if true and otherwise, that put me in an infinite loop of observing property.
Thanks!
Ember.run.later runs function only once. It is said clear in docs
Also, do you use very old version of ember? Ember.run.later is outdated and you supposed to use partial import import { later } from '#ember/runloop'; instead of that
As for your task, there is at least two ways
Using ember-concurrency addon
Install ember-concurrency and write in controller:
import { task, timeout } from 'ember-concurrency';
export default Controller.extend({
infiniteTask: task(function* () {
while(true) {
this.functionThatIsRunningEachTwoSeconds();
yield timeout(2000);
}
}).drop(),
});
Template:
{{#if infiniteTask.isIdle}}
<button onclick={{perform infiniteTask}}>Start</button>
{{else}}
<button onclick={{cancel-all infiniteTask}}>Stop</button>
{{/if}}
This addon is helpful in lot of situations, read it's docs to understand why you might need it
Creating a function that will recursively call itself
It's a classical JS approach to repeat some action, but in vanilla JS we use setTimeout instead of ember's later.
import { later, cancel } from '#ember/runloop';
export default Controller.extend({
infiniteFuction() {
this.functionThatIsRunningEachTwoSeconds();
this.set('infiniteTimer', later(this, 'infiniteFuction', 2000));
},
startInfiniteFunction() {
//clear timer as safety measure to prevent from starting few
//"infinite function loops" at the same time
cancel(this.infiniteTimer);
this.infiniteFuction();
},
stopInfiniteFunction() {
cancel(this.infiniteTimer);
this.set('infiniteTimer', undefined);
}
});
Template:
{{#unless infiniteTimer}}
<button onclick={{action startInfiniteFunction}}>Start</button>
{{else}}
<button onclick={{action stopInfiniteFunction}}>Stop</button>
{{/unless}}
Just to clarify what's wrong with your current code (and not necessarily promoting this as the solution), you must change the value for the observer to fire. If you set the value to true, and then set it to true again later without ever having set it to false, Ember will internally ignore this and not refire the observer. See this twiddle to see a working example using observers.
The code is
init(){
this._super(...arguments);
this.set('count', 0);
this.set('execute', true);
this.timer();
},
timer: observer('execute', function(){
//this code is called on set to false or true
if(this.get('execute')){
Ember.run.later((() => {
this.functionThatIsRunningEachTwoSeconds();
}), 2000);
// THIS IS SUPER IMPORTANT, COMMENT THIS OUT AND YOU ONLY GET 1 ITERATION
this.set('execute', false);
}
}),
functionThatIsRunningEachTwoSeconds(){
let count = this.get('count');
this.set('count', count + 1);
this.set('execute', true);
}
Now that you know what's wrong with your current approach, let my go back again on record and suggest that this is not a great, intuitive way to orchestrate a repeated loop. I recommend the Ember-Concurrency as well since it is Ember lifecycle aware
If you handled the edit button in the route, you could super cleanly cancel it on route change
functionThatIsRunningEachTwoSeconds: task(function * (id) {
try {
while (true) {
yield timeout(2000);
//work function
}
} finally {
//if you wanted code after cancel
}
}).cancelOn('deactivate').restartable()
Deactivate corresponds to the ember deactivate route hook/event:
This hook is executed when the router completely exits this route. It
is not executed when the model for the route changes.
I've found a lot of questions about deferring, promises, running javascript synchronously, etc. and I've tried numerous things already but still can't get this to work.
Edit Here's a little more explanation on the problem. fetchData has a routine that depends on all the code inside showStuff being complete. In particular, there's divs that get created using percentage of screen size, and we need to get the height of those divs so we can draw gauges inside them. fetchData is running before slideDown() is complete. Please see the additional console.log code I've added directly below.
My button onClick() calls showOverlay().
function showOverlay() {
showStuff().promise().done( function() {
console.log($("#gauge1").height()); //returns -0.5625 or something close
fetchData(); //ajax call
});
}
function showStuff() {
$("#overlay").fadeIn(200);
$("#gauges").slideDown(800);
$(".gauge").each(function() {
$( this ).show(); //unhides #gauge1 div
});
}
The error I'm getting says: cannot call method 'promise' of undefined.
I'm not showing my fetchData() function but it basically uses ajax to call a web service and then creates gauges on the screen using Raphael. If fetchData runs before the animations are complete the gauges are not displayed correctly because their size is relative to the .gauge div's.
Edit1
Neither of the examples below work. They both run without errors but return too quickly.
function showOverlay() {
showStuff().promise().done(function() {
fetchData();
});
}
function showStuff() {
var def = $.Deferred();
$("#overlay").fadeIn(200);
$("#gauges").slideDown(800);
$(".gauge").each(function() {
$( this ).show();
});
def.resolve();
return def;
}
Doesn't work either:
function showOverlay() {
$.when(showStuff()).done(function() {
fetchData();
});
}
function showStuff() {
$("#overlay").fadeIn(200);
$("#gauges").slideDown(800);
$(".gauge").each(function() {
$( this ).show();
});
}
You've 2 issues, the deferred and thats not how you run animations one after the other.
This will get you part of the way:
function showStuff() {
var deferred = $.Deferred();
$("#overlay").fadeIn(300,function(){
$("#gauges").slideDown(800,function(){
$(".gauge").show(); //doing this one after another takes more code.
deferred.resolve();
});
});
return deferred;
}
Heres the codepen: http://codepen.io/krismeister/pen/pvgKj
If you need to do sophisticated animations like this. You might find better results with GSAP.
Heres how to stagger:
http://www.greensock.com/jump-start-js/#stagger
Try to use $.when() instead:
$.when(showStuff()).done(function() {
fetchData();
});
You a) need to return something from showStuff b) should return a promise directly, so that the .promise() method is unnecessary:
function showOverlay() {
showStuff().done(function() {
fetchData();
});
}
function showStuff() {
return $("#overlay").fadeIn(200).promise().then(function() {
return $("#gauges").slideDown(800).promise();
}).then(function() {
return $(".gauge").show().promise();
});
}
If any of the following listeners are activated, they are activated 2^x times, x being the number of times any one of them has been triggered. The first time it will run 2 times, ten 4, 8, 16 ect. What am I missing that is triggering them more than once?
$(document).on('click',"#post-ride",(function() {
addRide(currentDriver, $(destinationInput).val(), $(originInput).val(),$(dateInput).val(), $(timeInput).val());
console.log("add ride called");
$.getScript("scripts/myRides.js", function() {
});
}));
$(document).on('click',"#request-ride",(function() {
requestRide(currentDriver, $(destinationInput).val(), $(originInput).val(), $(dateInput).val(), $(timeInput).val());
$.getScript("scripts/myRides.js", function() {
});
}));
$(document).on('click',"#leave-ride",(function() {
leaveRide(currentDriver, $(this).closest('div').attr('id') );
$.getScript("scripts/myRides.js", function() {
});
console.log("leave ride called");
}));
$(document).on('click',"#cancel-ride",(function() {
cancelRide(currentDriver, $(this).closest('div').attr('id') );
$.getScript("scripts/myRides.js", function() {
});
}));
$(document).on('click',"#remove-friend",(function() {
removeFriend(currentDriver, $(this).closest('div').attr('id') );
$.getScript("scripts/friends.js", function() {
});
}));
$(document).on('click',"#add-friend",(function() {
addFriend(currentDriver, $(this).closest('div').attr('id') );
$.getScript("scripts/friends.js", function() {
});
}));
Presumably, either myRides.js or friends.js or both are adding duplicate event listeners every time you load it.
The logical question to ask would be why are you loading the same script over and over again? You probably should not be doing that.
If there is one function in that script that you want to execute, you can test whether that function is already loaded and if so, just call it. If not, then load the script and let it execute when it's loaded.
If there's some other reason why you are loading the same script over and over again, then you could protect each event listener from installing a subsequent copy by keeping track of whether you've loaded each one yet in it's own state variable, though this is probably the more tedious way to solve the problem.
You are adding new handlers each time you "getScript"
I'm trying to set animations on rendering and closing an ItemView with Backbone.Marionette. For rendering a view, this is fairly simple:
MyItemView = Backbone.Marionette.View.extend({
...
onRender: function() {
this.$el.hide().fadeIn();
}
...
});
This will have my view fade in when I render it. But let's say I want to fade out my view upon close.
beforeClose: function() {
this.$el.fadeOut(); // doesn't do anything....
}
This won't work, because the item closes immediately after calling this.beforeClose(), so the animation doesn't have time to complete.
Is there any way, using Marionette as it stands, to accomplish a closing animation?
Alternatively, this is the workaround I've been using:
_.extend(Backbone.Marionette.ItemView.prototype, {
close: function(callback) {
if (this.beforeClose) {
// if beforeClose returns false, wait for beforeClose to resolve before closing
// Before close calls `run` parameter to continue with closing element
var dfd = $.Deferred(), run = dfd.resolve, self = this;
if(this.beforeClose(run) === false) {
dfd.done(function() {
self._closeView(); // call _closeView, making sure our context is still `this`
});
return true;
}
}
// Run close immediately if beforeClose does not return false
this._closeView();
},
// The standard ItemView.close method.
_closeView: function() {
this.remove();
if (this.onClose) { this.onClose(); }
this.trigger('close');
this.unbindAll();
this.unbind();
}
});
Now I can do this:
beforeClose: function(run) {
this.$el.fadeOut(run); // continue closing view after fadeOut is complete
return false;
},
I'm new to using Marionette, so I'm not sure if this is the best solution. If this is the best way, I'll submit a pull request, though I'll want to put a bit more thought into how this could work with other types of views.
This could potentially be used for other purposes, such as asking for confirmation on close (see this issue), or running any kind of asynchronous request.
Thoughts?
Overriding the close method is the one way to do this, but you can write it bit shorter, as you can call the Marionettes close method instead of duplicating it:
_.extend(Backbone.Marionette.ItemView.prototype, {
close: function(callback) {
var close = Backbone.Marionette.Region.prototype.close;
if (this.beforeClose) {
// if beforeClose returns false, wait for beforeClose to resolve before closing
// Before close calls `run` parameter to continue with closing element
var dfd = $.Deferred(), run = dfd.resolve, self = this;
if(this.beforeClose(run) === false) {
dfd.done(function() {
close.call(self);
});
return true;
}
}
// Run close immediately if beforeClose does not return false
close.call(this);
},
});
Another idea is to overide the remove method of your view. So you fade out the element of the view and then remove it from the DOM
remove: function(){
this.$el.fadeOut(function(){
$(this).remove();
});
}
I am running jasmine tests like this;
jasmine.getEnv().addReporter(new jasmine.TrivialReporter());
jasmine.getEnv().execute();
I would like to detect, using JavaScript, when the tests complete. How can I?
As #Xv. suggests, adding a reporter will work. You can do something as simple as:
jasmine.getEnv().addReporter({
jasmineDone: function () {
// the specs have finished!
}
});
See http://jasmine.github.io/2.2/custom_reporter.html.
Some alternative ways:
A) Use the ConsoleRunner, that accepts an onComplete option. Older versions (1.2rc1) receive the complete callback as a standalone parameter.
Since you also supply the function that writes (options.print) you keep control about having the test reports written to the console.
You can have several reporters active at the same time jasmineEnv.addReporter().
B) Haven't tried, but you could create your own reporter, with empty implementations of every public method but jasmineDone()
C) Check an old post in the Jasmine google group, where the author saves and overrides jasmine.getEnv().currentRunner().finishCallback:
var oldCallback = jasmineEnv.currentRunner().finishCallback;
jasmineEnv.currentRunner().finishCallback = function () {
oldCallback.apply(this, arguments);
$("body").append( "<div id='_test_complete_signal_'></div" );
};
jasmineEnv.execute();
I found two different ways to solve this issue. One is to hack jasmine to throw a custom event when it completes. Because I wanted to screen scrape after the test loaded, I inserted the event trigger into jasmine-html.js at the end of "reportRunnerResults"
$( 'body' ).trigger( "jasmine:complete" );
Then it's a matter of listening for the event:
$( 'body' ).bind("jasmine:complete", function(e) { ... }
In my case, I was running jasmine in an iFrame and wanted to pass the results to a parent window, so I trigger an event in the parent from my first bind:
$(window.parent).find('body').trigger("jasmine:complete");
It is also possible to do this without jquery. My strategy was to poll for text to be added to the "finished-at" span. In this example I poll every .5 seconds for 8 seconds.
var counter = 0;
function checkdone() {
if ( $('#test-frame' ).contents().find('span.finished-at').text().length > 0) {
...
clearInterval(timer);
} else {
counter += 500;
if (counter > 8000) {
...
clearInterval(timer);
}
}
}
var timer = setInterval( "checkdone()", 500 );
I'm running Jasmine 1.3.1 with the HtmlReporter. I ended up hooking in like this:
var orig_done = jasmineEnv.currentRunner_.finishCallback;
jasmineEnv.currentRunner_.finishCallback = function() {
orig_done.call(this);
// custom code here
};