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.
Related
New to Vue and frameworks in general, and may have my thinking not very "Vue-like".
Trying to make a "super" button component that takes a prop, which dictates the buttons behavior so I only have to maintain one button component. The ideal form when implementing would like something like this...
<super-button isType="string"></super-button>
The template is...
<button class="buttonLow" v-bind:class="{buttonHigh: isSelected}">
<slot></slot>
</button>
Where isType prop could be momentary momentary-multi toggle or toggle-multi.
I have a basic set of event emitters/methods and listeners that work regardless of the isType and simply makes the buttons state high or low / on or off using another prop isSelected.
The problem is trying to conditionally setup the mouse events depending on the isType. While figuring out the logic, I used the # syntax to setup the mouse events #click #mousedown #mouseup etc. and everything worked great by itself. For example, the events for a momentary button during testing looked like this...
<button #mousedown="emitButtonHigh()" #mouseup="emitButtonLow" #mouseleave="emitButonLow"></button>
However, a simple toggle button looked more like this...
<button #click="emitButtonToggle()"></button>
Obviously there is a bit of conflict there.
My attempted work around was to use a switch statement in created() that would take isType as the expression and conditionally register the appropriate mouse events...
created(){
switch(this.isType){
case ("momentary"):{
//attach on events
break;
}
case ("toggle"):{
//attach on events
break;
}
case ("toggle-multi"):{
//attach on events
break;
}
default:{
break;
}
}
}
While switch itself is working, I can't figure out how to attach the mouse events in this context. I can attach a custom event no problem using...
this.$root.$on('my-custom-event', ()=>{
//do stuff
});
but trying do something like...
this.$root.$on('click', ()=>{
//do stuff
});
or...
this.$on('click', ()=>{
//do stuff
});
Does not work, nor can I figure out any way to write it out that creates the same functionality as #click let alone #mousedown #mouseup or other built-in events.
TL;DR
How do you write out the # syntax or v-on syntax, for built-in events (click, mousedown, mouseup, etc.), using $on syntax, so the events actually fire?
You could attach all these component events on a single handler, determine the event.types as they're fired and emit your custom events from here, while optionally passing additional arguments.
const SuperButton = Vue.extend({
template: `
<button
#mousedown="emitCustomEvent"
#mouseup="emitCustomEvent"
#mouseleave="emitCustomEvent">
<slot></slot>
</button>
`,
props: {
isType: {
type: String,
default:
String
},
// ...
},
methods: {
emitCustomEvent(e) {
let type = e.type.substr('mouse'.length);
let args = {
type,
isSelected: type === 'down',
args: {
// Your additional args here
}
};
switch(this.isType) {
case ("momentary"):{
//attach on events
break;
}
// ...
}
// Or emit events regardless of the "isType"
this.$emit(`mouse:${type}`, args);
this.$emit('mouse', args);
}
}
});
new Vue({
el: '#app',
methods: {
mousing(args) {
console.log(`mouse:${args.type} from component.`);
},
mouseLeaving() {
console.log('Mouse leaving.');
}
},
components: {
SuperButton
}
});
Vue.config.devtools = false;
Vue.config.productionTip = false;
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<super-button #mouse="mousing">Super button</super-button>
<super-button #mouse:leave="mouseLeaving">Super button 2, detecting leaving only</super-button>
</div>
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
I want an Action event (on="") to trigger when there is a transition to a new Route.
I've seen the list of Action event handlers and closest I could find is attaching the action to the largest HTML element on the page and triggering it with 'Mousemove". This is a terribly flawed away of going about what I want to do.
So just to draw it out.
<div {{action 'displayEitherHtml1or2'}} class="largestDOMelement">
{{#if showHtml1}}
// html 1 inside
{{/if}}
{{#if showHtml2}}
// html 2 inside
{{/if}}
</div>
'/objects' is a list of objects and clicking one leads to 'object/somenumber'. The action should automatically trigger when I enter the 'object/somenumber' page.
UPDATE: I've taken the contents from the previous update and dumped them into my DocRoute, but nothing it being triggered when I transition to 'doc' through {{#link-to 'doc' this.docID}} {{docTitle}}{{/link-to}}
VpcYeoman.DocRoute = Ember.Route.extend(VpcYeoman.Authenticated,{
toggleLetterSwitch: false,
togglePermitSwitch: false,
activate: function () {
var docTemplateID = this.get('docTemplateID');
if ( docTemplateID == 2) {
this.set('toggleLetterSwitch', true);
this.set('togglePermitSwitch', false);
console.log('docTemplateID equals 2');
} else {
this.set('toggleLetterSwitch', false);
this.set('togglePermitSwitch', true);
}
}
});
UPDATE DOS: setDocID is set in the DocsController to 1. Here's the whole thing.
VpcYeoman.DocsController = Ember.ArrayController.extend({
tempDocId: 1,
actions: {
addDoc: function (params) {
var docTitle = this.get('docTitle');
var docTemplateID = 1;
var docTemplateID = this.get('tempDocId');
console.log(this.get('tempDocId'));
var store = this.store;
var current_object = this;
var doc = current_object.store.createRecord('doc', {
docTitle:docTitle,
docTemplateID:docTemplateID
});
doc.save();
return true;
},
setDocId: function (param) {
this.set('tempDocId', param);
console.log(this.get('tempDocId'))
},
}
});
As #fanta commented, it seems like you're looking for the activate hook within your Route. This gets called when you enter the route where you define it. If you want to call it on every transition, you might consider defining a base route for your application and extending that instead of Em.Route:
App.BaseRoute = Em.Route.extend(
activate: function () {
// Do your thing
}
);
App.YourRoutes = App.BaseRoute.extend()
It's possible that there's a more appropriate place/time to do this, but without knowing quite what your action does, this is probably the best guess.
ETA: Looking at your edit, you won't want all your routes to extend App.BaseRoute the way I did it above; you should probably just include that activate hook explicitly in the routes which need it.
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 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
};