Ember drag-and-drop setTimeout before drag starts - javascript

I have a draggable-object from Ember drag-and-drop library (https://github.com/mharris717/ember-drag-drop) that default behaves like dragStart is performed instantly. I need to force that object, to start drag when user holds mouse button down for some time, and I thought that I can do it in dragStartAction method delivered by drag-and-drop library, but this solution don't work as expected. its still instant dragStart.
draggable-object from my hbs:
{{#draggable-object
dragHandle=".js-dragHandle"
overrideClass=classes
tagName="tbody"
content=this
dragStartAction=(action "onDragStart")
dragStartHook=(action "onDragStartHook")
dragEndHook=(action "onDragEndHook")
dragEndAction=(action "onDragEnd")
}}
onDragStart action:
#action
onDragStart(componentInstance, data) {
setTimeout(() => {
if (!this.selectedRowIds.includes(this.transaction.id)) {
this.send("selectRow", this.transaction.id);
}
this.selectCoupledVoidPayments();
this.transferTransactions.onDragStart(findComponentObject(componentInstance), this.selectedRowIds, data);
}, 500);
}
onDragStartHook action:
#action
onDragStartHook(event: Event) {
this.transferTransactions.onDragStartHook(event);
}
I was thinking that I can resolve that by using somehow custom delay helper like this, but this is prohibited in my place, and I don't even know that this will do the job.
{{#draggable-object
dragHandle=".js-dragHandle"
overrideClass=classes
tagName="tbody"
content=this
dragStartAction=(action (delay "onDragStart"))
dragStartHook=(action "onDragStartHook")
dragEndHook=(action "onDragEndHook")
dragEndAction=(action "onDragEnd")
}}
What am I doing wrong, or what is the correct way to implement delay in Ember drag-and-drop?

Related

How to handle WebGL CONTEXT_LOST_WEBGL errors more gracefully in PixiJS?

I have a React application that uses a data visualization library that uses PixiJS.
I occasionally get frustrating CONTEXT_LOST_WEBGL errors in Chrome that force the user to manually reload the page, in order for the page to be (re)rendered.
I cannot often or reliably reproduce the error, but I know that it happens as other people tell me the application occasionally shows no data. The situations that raise this error seem very context-dependent and therefore difficult to recapitulate — low-powered graphics adapters, or lots of tabs open at once, etc.
The end user would only know that there are CONTEXT_LOST_WEBGL errors if that user has the Developer Tools console window open. Otherwise, the web page just looks blank.
I have tried the following to set up my React application to reload the window without manual user intervention, when a webglcontextlost event occurs:
componentDidMount() {
...
window.addEventListener("webglcontextlost", (e) => { window.location.reload(); });
...
}
I'm not sure it is working correctly, i.e., if the webglcontextlost event is being handled elsewhere. Or perhaps I am trying to subscribe to the wrong event?
Otherwise, to try to handle this more gracefully, is there a way in raw Javascript, or via a third-party library, to periodically measure available memory for WebGL, and to use that measurement to instead reload the page, when the available memory reaches some arbitrary threshold that might predict an imminent CONTEXT_LOST_WEBGL error condition?
is there a way in raw Javascript to periodically measure available memory for WebGL
No, just as there is no way to measure JavaScript memory
window.addEventListener("webglcontextlost", (e) => { window.location.reload(); });
Is wrong. It should be
someCanvas.addEventListener("webglcontextlost", (e) => { window.location.reload(); });
Each canvas can individually lose its context. Most browsers only allow 8 to 16 WebGL contexts at once. As soon as the limit is reached canvases start to lose their contexts.
As for recovering gracefully it's a lot of work. Basically you need recreate all WebGL resources which means you need to structure your code so that's posssible. Separate all the state of your app from the stuff releated to WebGL (or from pixi.js) and when you get a context lost event then prevent default and recreate all the WebGL stuff
let gl;
someCanvas.addEventListener("webglcontextlost", (e) => {
e.preventDefault(); // allows the context to be restored
});
someCanvas.addEventListener("webglcontextrestored", (e) => {
initWebGL(gl);
});
gl = someCanvas.getContext('webgl');
initWebGL(gl);
Note that pixi.js itself may or may not be designed to handle contextlost
The following code helped restart my Pixijs web application, when the WebGL context is lost:
addCanvasWebGLContextLossEventListener = () => {
const canvases = document.getElementsByTagName("canvas");
if (canvases.length === 1) {
const canvas = canvases[0];
canvas.addEventListener('webglcontextlost', (event) => {
window.location.reload();
});
}
}
removeCanvasWebGLContextLossEventListener = () => {
const canvases = document.getElementsByTagName("canvas");
if (canvases.length === 1) {
const canvas = canvases[0];
canvas.removeEventListener('webglcontextlost');
}
}
For other applications with more than one canvas, some adjustments would be needed to add other listeners.
The following code helped me simulate a lost context condition (and to restore from it, via the webglcontextlost event):
simulateWebGLContextLoss = () => {
//
// simulate loss of WebGL context, for the purposes
// of improving user experience when the browser is
// overwhelmed
//
const canvases = document.getElementsByTagName("canvas");
if (canvases.length === 1) {
setTimeout(() => {
const canvas = canvases[0];
const webgl2Context = canvas.getContext("webgl2", {});
if (webgl2Context) {
console.log(`losing webgl2 context...`);
webgl2Context.getExtension('WEBGL_lose_context').loseContext();
}
else {
const webglContext = canvas.getContext("webgl", {});
if (webglContext) {
console.log(`losing webgl context...`);
webglContext.getExtension('WEBGL_lose_context').loseContext();
}
}
}, 5000);
}
}
For React lifecycle setup:
componentDidMount() {
setTimeout(() => {
this.addCanvasWebGLContextLossEventListener();
}, 2500);
}
componentWillUnmount() {
this.removeCanvasWebGLContextLossEventListener();
}
A timeout is required, as the canvas element is not yet available when the component mounts. For my purposes, the short 2.5s timer provides enough time for the event handler to latch onto the canvas.

addListener behind the scenes

In the past I've also used a resize listener that bundled requestAnimationFrame with it to be a somewhat optimized version of polling resize events:
/**
* Resize listener
* #return {function}
*/
export const optimizedResize = (function () {
let callbacks = [],
running = false;
// fired on resize event
function resize() {
if (!running) {
running = true;
if (window.requestAnimationFrame) {
window.requestAnimationFrame(runCallbacks);
} else {
setTimeout(runCallbacks, 66);
}
}
}
// run the actual callbacks
function runCallbacks() {
callbacks.forEach((callback) => {
callback();
});
running = false;
}
// adds callback to loop
function addCallback(callback) {
if (callback) {
callbacks.push(callback);
}
}
return {
// public method to add additional callback
add(callback) {
if (!callbacks.length) {
window.addEventListener('resize', resize);
}
addCallback(callback);
},
};
}());
I recently came across addListener() which embarrassingly I must say I'm not familiar with. Although it says it's just an alias for addEventListener() the syntax seems pretty straight forward to listen to changes:
const viewportMediumMin = window.matchMedia(`(min-width: 768px)`);
viewportMediumMin.addListener(checkScreenSize);
But, what I'm trying to figure out is addListener() is the equivalent of:
window.addEventListener('resize', function() {
console.log('addEventListener - resize');
}, true);
or if it's doing something "smarter" behind the scenes that I should rely on it exclusively, compared to the optimizedResize method I mentioned. I'm really only interested in the specific event of the media query changing, not finding out every single pixel width change. Thanks for any help!
This is basically reinventing wheels. CSS3 was made to style the pages content gracefully on different screen sizes, and media queries were added to change the look of a page at a certain breakpoint. Those breakpoints are common in todays web development, and modern CSS engines were heavily optimized to perform changes as fast as possible. Therefore
window.matchMedia(`(min-width: 768px)`).addListener(/*...*/)
is probably very performant as the event is detected by the CSS engine that then gets redirected to the JS engine. Adding a listener to resize is probably slower as every pixel change causes a JS event, and your unoptimized JavaScript has to figure out wether a breakpoint was passed. Your optimizeResize doesn't really change that.

react - how to force an element to redraw immediately

I have a toggle button in a react component:
toggleSpeak = () => {
this.setState({buttonOn: !this.state.buttonOn});
}
And the button changes its style depending on its state:
<img key="headphones" className={audioclass} src={this.state.buttonOn ? white : black} onClick={this.toggleSpeak}/>
This also triggers some stuff in a child component:
play={this.state.buttonOn}
This triggers some speechSynthesis playback, which sometimes takes a while. The problem is that I want the user to realize that something is happening right away. The button, however, doesn't change its style right away. As long as I'm triggering something else, whether it is through a passthrough property to the child as above, or through triggering a redux action, it still delays changing color for a few seconds.
I want to change color right away without delay, so the user knows not to keep repushing it. How can I accomplish this?
this.setState({}) function is indeed asynchronous so what you are claiming is likely to be true for a very short number of milliseconds considering that all you have in the trigger is
toggleSpeak = () => {
this.setState({buttonOn: !this.state.buttonOn});
}
The noticeable delay you speak of should be unnoticeable. I would think that the delay is being imposed from elsewhere. (say you require some other synchronous code to run before this.setState({}). Do show us more of the relevant code so that we can get better grasp of what's happening.
Are you doing the speechSynthesis in render?
You should call the function that does the speechSynthesis after toggling the button.
As far as UX is concerned, I would recommend that you show a loading indicator while you are doing a task that might take some time to finish. Also, you could disable the button until the speechSynthesis is finished.
toggleSpeak = () => {
if(!this.state.doingSpeechSynthesis) {
this.setState(
{buttonOn: !this.state.buttonOn, doingSpeechSynthesis: true},
() => speechSynthesis(args, this.setState{doingSpeechSynthesis: false}));
}
}
I'm not sure if this is the "react" way of doing things, but I came up with a solution that works. I split up the property I pass to turn on the player from the button toggle.
state = {
buttonOn: false,
play: false
}
Button attributes are the same as above, changing with the buttonOn state.
ChildComponent property:
... play={this.state.play}
Then, on the button toggle event I wait a half a sec before I change the play state. This is so the button will update it's style right away, and then all the player stuff can run after a tick.
togglePlay = (newValue) => {
this.setState({play: newValue});
}
toggleSpeak = (e) => {
let newValue = !this.state.buttonOn;
this.setState({buttonOn: newValue});
if (this.state.play != newValue) {
setTimeout(function() {
this.togglePlay(newValue);
}.bind(this), 500);
}
Then of course clear the timeout function on dismount:
componentWillUnmount() {
clearTimeout(this.togglePlay);
}

Starting Alexa Skill in a specific state

Earlier I ran into the issue of Alexa not changing the state back to the blank state, and found out that there is a bug in doing that. To avoid this issue altogether, I decided that I wanted to force my skill to always begin with START_MODE.
I used this as my reference, where they set the state of the skill by doing alexa.state = constants.states.START before alexa.execute() at Line 55. However, when I do the same in my code, it does not work.
Below is what my skill currently looks like:
exports.newSessionHandler = {
LaunchRequest () {
this.hander.state = states.START;
// Do something
}
};
exports.stateHandler = Alexa.CreateStateHandler(states.START, {
LaunchRequest () {
this.emit("LaunchRequest");
},
IntentA () {
// Do something
},
Unhandled () {
// Do something
}
});
I'm using Bespoken-tools to test this skill with Mocha, and when I directly feed IntentA like so:
alexa.intended("IntentA", {}, function (err, p) { /*...*/ })
The test complains, Error: No 'Unhandled' function defined for event: Unhandled. From what I gather, this can only mean that the skill, at launch, is in the blank state (because I have not defined any Unhandled for that state), which must mean that alexa.state isn't really a thing. But then that makes me wonder how they made it work in the example code above.
I guess a workaround to this would be to create an alias for every intent that I expect to have in the START_MODE, by doing:
IntentA () {
this.handler.state = states.START;
this.emitWithState("IntentA");
}
But I want to know if there is a way to force my skill to start in a specific state because that looks like a much, much better solution in my eyes.
The problem is that when you get a LaunchRequest, there is no state, as you discovered. If you look at the official Alexa examples, you will see that they solve this by doing what you said, making an 'alias' intent for all of their intents and just using them to change the state and then call themselves using 'emitWithState'.
This is likely the best way to handle it, as it gives you the most control over what state and intent is called.
Another option, assuming you want EVERY new session to start with the same state, is to leverage the 'NewSession' event. this event is triggered before a launch request, and all new sessions are funneled through it. your code will look somewhat like this:
NewSession () {
if(this.event.request.type === Events.LAUNCH_REQUEST) {
this.emit('LaunchRequest');
} else if (this.event.request.type === "IntentRequest") {
this.handler.state = states.START;
this.emitWithState(this.event.request.intent.name);
}
};
A full example of this can be seen here (check out the Handlers.js file): https://github.com/alexa/skill-sample-node-device-address-api/tree/master/src
I would also recommend reading through this section on the Alexa GitHub: https://github.com/alexa/alexa-skills-kit-sdk-for-nodejs#making-skill-state-management-simpler
EDIT:
I took a second look at the reference you provided, and it looks like they are setting the state outside of an alexa handler. So, assuming you wanted to mimic what they are doing, you would not set the state in your Intent handler, but rather the Lambda handler itself (where you create the alexa object).
exports.handler = function (event, context, callback) {
var alexa = Alexa.handler(event, context);
alexa.appId = appId;
alexa.registerHandlers(
handlers,
stateHandlers,
);
alexa.state = START_MODE;
alexa.execute();
};

Trigger Action from Push Notification

I have a cordova application that uses push notification (still using the old plugin :-().
The application uses ngRouter and the navigation is relatively basic - in that I mean that my main menu changes ngView but popups/modals are not part of the navigation and are either triggered by some bound controller property or through a call to a controller function (e.g. $scope.openMyModal).
I am trying to be able to call such function on one of my controllers after I received push notification (and the controller is loaded).
I implemented some code using a timeout to broadcast an event which should be caught in the relevant controller and open the modal. Roughly the code is:
In app.js:
onNotification() {
// some code for determining the type of notification
// then
setTimeout(function() {
$rootScope.$broadcast("someEventCode");
}, 10); // or 1000 in case of cold start
}
In MyController.js:
.controller('MyController', function($scope, $rootScope, $modal,...) {
$scope.openMyModal = function() { // open modal using $model }
$scope.on("someEventCode", function() {
$scope.openMyModal();
});
}
This kind of works but is not consistent/deterministic. For example, in slower devices it may broadcast before the controller is ready to respond to it.
I also tried to set some variable on root scope (in onNotification) and in the controller create a function which is called from the markup (e.g. {{isNotificationReady()}}) but this also doesn't work well.
Another approach was to use double notifications - set a flag in root scope when the notification arrives, wait for an event from the target controller (indicating it is loaded) and then, at $rootScope again, if flag is set, broadcast the "open dialog" event (and delete the flag). Following this approach, I am not sure how to trigger the "loaded" event so I use a function from the markup:
In MyController.js:
$scope.isLoaded = function() {
$scope.$emit("myControllerLoaded");
}
In markup:
<div><!-- the content --></div>
{{isLoaded()}}
In app.js
$rootScope.$on("myControllerLoaded", function(event) {
if ($rootScope.notification === "someEventCode") {
$rootScope.$broadcast("openTheModel");
delete $rootScope.notification;
}
});
This seems like cumbersome and inefficient code. isLoaded() is called multiple times (not sure why) and it is kind of spaghetti code.
My question is - how should I implement something like that in a clear and efficient manner? Just a reminder, the app could be "cold started" or in the background and I need to know when it is "running" (or the controller is ready).
I've found a slightly more robust, timeout based implementation (still not exactly what I was hoping for).
The idea is to set a flag and send (broadcast) the signal after some time. Then resend the signal on interval until the flag is unset by the target controller:
In app.js
function broadcastSomeEvent() {
$rootScope.$broadcast("someEventCode");
if ($rootScope.eventFlag) {
setTimeout(broadcastSomeEvent, 50);
}
}
onNotification() {
// some code for determining the type of notification, then
$rootScope.eventFlag = true;
setTimeout(broadcastSomeEvent, 10); // or 1000 in case of cold start
}
In MyController.js
$scope.$on('someEventCode', function() {
delete $rootScope.eventFlag; // delete flag so event is stopped
$scope.openMyModal();
});
This is still an iff-y implementation to my taste. Even though it does work for both cold start and when the application is in the background I believe that it is not robust as it should.
Still, I wouldn't mark this solution as "the answer".
On the other hand, with no proper state routing, maybe there's not much more than can be done.

Categories

Resources