Detect react event from Tampermonkey - reactjs

I'm enhancing a React front end with Tampermonkey , by adding highlights to show cursor location in a grid, and allowing users to directly enter data , rather than then enter data.
After 2 or 3 cursor moves or data entry the grid refreshes or updates - no page change - and looses the highlighting I set up.
I'd like to catch the refresh/update and reset the highlighting.
I'm a noob..
The network tab shows post events so I tried https://jsbin.com/dixelocazo/edit?js,console
var open = window.XMLHttpRequest.prototype.open,
send = window.XMLHttpRequest.prototype.send;
to try and use POST events to detect the refresh. No joy !
I also looked at ajax events.
No luck :(
Can someone point me in the right direction here ?
Once I catch the event, I can then reset the highlighting to fix the problem

Since normally the userscripts run in a sandbox, JavaScript functions or objects cannot be used directly by default, here's what you can do:
Disable the sandbox:
// #grant none
You won't be able to use any GM functions, though.
Run in the page context via unsafeWindow:
const __send = unsafeWindow.XMLHttpRequest.prototype.send;
unsafeWindow.XMLHttpRequest.prototype.send = function () {
this.addEventListener('loadend', e => {
console.log('intercepted', e);
}, {once: true});
__send.apply(this, arguments);
};
Use MutationObserver to detect changes in page DOM:
const observer = new MutationObserver(mutations => {
const matched = [];
for (const {addedNodes} of mutations) {
for (const n of addedNodes) {
if (!n.tagName)
continue;
if (n.matches('.prey:not(.my-highlight)')) {
matched.push(n);
} else if (n.firstElementChild) {
matched.push(...n.querySelectorAll('.prey:not(.my-highlight)'));
}
}
}
// process the matched elements
for (const el of matched) {
el.classList.add('my-highlight');
}
});
observer.observe(document.querySelector('.surviving-ancestor') || document.body, {
subtree: true,
childList: true,
});
.surviving-ancestor means the element that isn't replaced/recreated by the page script. In devtools element inspector it's the one that isn't highlighted temporarily during DOM updates.
See also Performance of MutationObserver.

Related

Integrate highcharts-custom-events with React

I've installed a highcharts-custom-events package to handle custom events(dblclick).
like the code below
var Highcharts = require('highcharts'),
HighchartsCustomEvents = require('highcharts-custom-events')(Highcharts);
But after adding this code, even the existing click is also not working.
Please help me to implement custom events to react.
Here is an example with implemented custom events in Highcharts with using react wrapper.
import CustomEvents from "highcharts-custom-events";
CustomEvents(Highcharts);
//require('highcharts-custom-events')(Highcharts);
Both above ways work - import and require.
Demo: https://codesandbox.io/s/highcharts-react-demo-1rtxl
If this wouldn't help - could you reproduce your case in the online editor which I could work on?
I was having the same problem using this lib, it was breaking the standard single click, I believe this is a duplicate post from this one.
On that post there is a function implementation of double click, that solution also breakes the single click, the thing is that you can add the single click as a condition inside double click function:
Fisrt define the settings:
var doubleClicker = {
clickedOnce : false,
timer : null,
timeBetweenClicks : 400
};
Then define a 'double click reset' function in case the double click is not fast enough and a double click callback:
// call to reset double click timer
var resetDoubleClick = function() {
clearTimeout(doubleClicker.timer);
doubleClicker.timer = null;
doubleClicker.clickedOnce = false;
};
// the actual callback for a double-click event
var ondbclick = function(e, point) {
if (point && point.x) {
// Do something with point data
}
};
Highcharts settings example
series: [{
point: {
events: {
click: function(e) {
if (doubleClicker.clickedOnce === true && doubleClicker.timer) {
resetDoubleClick();
ondbclick(e, this);
} else {
doubleClicker.clickedOnce = true;
doubleClicker.timer = setTimeout(function(){
resetDoubleClick();
}, doubleClicker.timeBetweenClicks);
}
}
}
}
}]

Reactjs web app - MutationObserver use in Safari browser not as expected

I have a react webapp that renders 2 text areas which are resize-vertical (using CSS). My requirement is that when one text area is resized by the user, the code captures it and apply the height to the other text area.
Based on my research, currently, there isn't a convenient method like textarea.onResize(). So, I am using MutationObserver. The API is very elegant and exactly what I need. I was able to configure it properly, capture the change in style attribute and apply it to other text area. This works fine in Google Chrome (Version 76.0.3809.132 (Official Build) (64-bit)) and Mozilla FireFox (Version 68.0.1esr (64-bit)). All this testing was done on Mac OS High Sierra (Version 10.13.6).
The problem
Although, when it comes to Safari browser (Version 12.1.2 (13607.3.10)), it doesn't seem to work as expected.
Upon debugging, I found out that when user resize a given text area, style attribute gets appended to it, with some assigned height and for each change, MutationObserver observes the change produces a MutationRecord, which I capture in my callback to take appropriate actions (in this case, apply height to other text area). Although, Safari browser does not produce MutationRecord for change in style attribute when text area is resized by the user.
Two things:
If I inspect the text area in developer mode of the browser and make a change to the style attribute of the element, MutationRecord is produced correctly and my callback does indeed execute, which is very weird.
I created an example in JS Fiddle, where, in Safari if you resize the text area, no MutationRecord is produced and you won't see any alerts but if you inspect and make a change manually, you will see the alert (appropriate MutationRecords are produced). In Chrome or Firefox, resizing the text area will produce the window alert without needing to make changes by inspecting the element.
Code
CSS
.textField textarea {
resize: vertical;
}
componentDidMount()
componentDidMount() {
if ('MutationObserver' in window) {
const config = {
attributes: true,
subtree: true,
attributeFilter: ['style'],
attributeOldValue: true
};
const sourceTextArea = window.getElementById('sourceTextArea');
this.sourceObserver = new MutationObserver(this.handleManualSourceTextAreaResize);
this.sourceObserver.observe(sourceTextArea, config);
const targetTextArea = window.getElementById('targetTextArea');
this.targetObserver = new MutationObserver(this.handleManualTargetTextAreaResize);
this.targetObserver.observe(targetTextArea, config);
}
}
Callback for source text area
handleManualSourceTextAreaResize = (mutationsList, observer) => {
const sourceTextArea = getElement('sourceTextArea');
const targetTextArea = getElement('targetTextArea').children[0];
if (!this.isStopPropagation) {
targetTextArea.setAttribute('style', 'height: ' + sourceTextArea.offsetHeight + 'px');
this.isStopPropagation = true;
} else {
this.isStopPropagation = false;
}
}
Callback for target text area
handleManualTargetTextAreaResize = (mutationsList, observer) => {
const sourceTextArea = getElement('sourceTextArea').children[0];
const targetTextArea = getElement('targetTextArea');
if (!this.isStopPropagation) {
sourceTextArea.setAttribute('style', 'height: ' + targetTextArea.offsetHeight + 'px'');
this.isStopPropagation = true;
} else {
this.isStopPropagation = false;
}
}

Drag and Drop with Protractor in AngularJS

I appreciate there's quite a bit of stuff already been said about automating drag and drop as part of E2E testing. However after many, many hours of fiddling around, I cannot get any of the methods described to work...that is using Functions, coordinates etc etc. Oddly enough, console.log maintains the tests have passed, but the screenshots clearly show nothing has happened.
Screenshots shows a portion of the application
The user selects a paper and drags onto the image. As the drag 'starts' the grey overlay on the image clears and the paper is rendered on the room.
The code snippet shows one of the more simple ideas I've tried and I would be very pleased to receive any help going!
const JS_HTML5_DND = 'function e(e,t,n,i){var r=a.createEvent("DragEvent");r.initMouseEvent(t,!0,!0,o,0,0,0,c,g,!1,!1,!1,!1,0,null),Object.defineProperty(r,"dataTransfer",{get:function(){return d}}),e.dispatchEvent(r),o.setTimeout(i,n)}var t=arguments[0],n=arguments[1],i=arguments[2]||0,r=arguments[3]||0;if(!t.draggable)throw new Error("Source element is not draggable.");var a=t.ownerDocument,o=a.defaultView,l=t.getBoundingClientRect(),u=n?n.getBoundingClientRect():l,c=l.left+(l.width>>1),g=l.top+(l.height>>1),s=u.left+(u.width>>1)+i,f=u.top+(u.height>>1)+r,d=Object.create(Object.prototype,{_items:{value:{}},effectAllowed:{value:"all",writable:!0},dropEffect:{value:"move",writable:!0},files:{get:function(){return this._items.Files}},types:{get:function(){return Object.keys(this._items)}},setData:{value:function(e,t){this._items[e]=t}},getData:{value:function(e){return this._items[e]}},clearData:{value:function(e){delete this._items[e]}},setDragImage:{value:function(e){}}});if(n=a.elementFromPoint(s,f),!n)throw new Error("The target element is not interactable and need to be scrolled into the view.");u=n.getBoundingClientRect(),e(t,"dragstart",101,function(){var i=n.getBoundingClientRect();c=i.left+s-u.left,g=i.top+f-u.top,e(n,"dragenter",1,function(){e(n,"dragover",101,function(){n=a.elementFromPoint(c,g),e(n,"drop",1,function(){e(t,"dragend",1,callback)})})})})';
describe('Drag and Drop Test', function() {
it('should drag', function () {
var e1 = element(by.xpath('html/body/webapp-app/div/div/webapp-johnlewis-visualiser/div/div[2]/div/digitalbridge-shortlist/div/div/ul/li[1]/a/img'));
var e2 = element(by.css('.db-project-designer'));
element(by.xpath('html/body/webapp-app/div/div/webapp-johnlewis-visualiser/div/div[2]/div/digitalbridge-shortlist/div/div/ul/li[1]/a/img')).click();
//element(by.xpath('html/body/webapp-app/div/div/webapp-johnlewis-visualiser/div/div[1]/div/div/digitalbridge-project/div/digitalbridge-project-designer/canvas')).click();
browser.driver.actions().dragAndDrop(e1.getWebElement(),e2.getWebElement()).perform();
browser.sleep(2000);
});
});
The constant is showing an error 'const' is available in ES6 (use 'esversion: 6') or Mozilla JS extensions (use moz). (W104) - I do have ES6 installed in Node_Modules.
I inserted the click line to see if pre-selecting the item made any difference...it didn't!
Thank you
David
Try this library https://github.com/SunGard-Labs/sg-protractor-tools
The library also includes functions that simplify common tasks like
Scrolling to an element
Drag and drop
Waiting for DOM elements to become visible or hidden
module.exports = function simulateDragAndDrop(sourceNode, destinationNode) {
const EVENT_TYPES = {
DRAG_END: 'dragend',
DRAG_START: 'dragstart',
DROP: 'drop'
};
function createCustomEvent(type) {
const event = new CustomEvent('CustomEvent');
event.initCustomEvent(type, true, true, null);
event.dataTransfer = {
data: {
},
setData: function(type, val) {
this.data[type] = val;
},
getData: function(type) {
return this.data[type];
}
};
return event;
}
function dispatchEvent(node, type, event) {
if (node.dispatchEvent) {
return node.dispatchEvent(event);
}
if (node.fireEvent) {
return node.fireEvent('on' + type, event);
}
}
const event = createCustomEvent(EVENT_TYPES.DRAG_START);
dispatchEvent(sourceNode, EVENT_TYPES.DRAG_START, event);
const dropEvent = createCustomEvent(EVENT_TYPES.DROP);
dropEvent.dataTransfer = event.dataTransfer;
dispatchEvent(destinationNode, EVENT_TYPES.DROP, dropEvent);
const dragEndEvent = createCustomEvent(EVENT_TYPES.DRAG_END);
dragEndEvent.dataTransfer = event.dataTransfer;
dispatchEvent(sourceNode, EVENT_TYPES.DRAG_END, dragEndEvent);
}
You can call it from you code like this
browser.executeScript(dragAndDrop, element, targetArea);

Using Angular Material, is it possible to close a specific dialog

I have an AngularJS app using the Angular Material UI framework.
The app has different mechanisms showing dialogs (e.g error and loading spinner) and it would be preferable to only close one specifically chosen in certain scenarios, e.g. when an AJAX request is finished fetching data, I would like my loading spinner to close, but not any error dialog that may be the result of the fetching.
What I can find in documentation and code doesn't agree (though code should win the argument):
Documentation says only the latest can be closed, with an optional response
The code says the latest, a number of latest or all open can be closed, with an optional reason
Example in the documentation says a specific dialog can be closed, with a flag denoting how or why
I have made a demo of my intent, as MCV as possible – these are the highlights:
var dialog = {},
promise = {};
function showDialogs(sourceEvent) {
showDialog(sourceEvent, "one");
showDialog(sourceEvent, "two");
}
function showDialog(sourceEvent, id) {
dialog[id] = $mdDialog.alert({...});
promise[id] = $mdDialog.show(dialog[id]);
promise[id].finally(function() {
dialog[id] = undefined;
});
}
function closeDialogs() {
$mdDialog.hide("Closed all for a reason", {closeAll: true});
}
function closeDialogLatest() {
$mdDialog.hide("Closed from the outside");
}
function closeDialogReason() {
$mdDialog.hide("Closed with a reason");
}
function closeDialogSpecific(id) {
$mdDialog.hide(dialog[id], "finished");
}
EDIT:
I know the code always wins the argument about what happens, but I wasn't entirely sure it was the right code I was looking at.
I have updated the examples to better test and illustrate my point and problem. This shows things to work as the code said.
What I'm really looking for is whether it might still be possible to achieve my goal in some other way that I didn't think of yet.
Using $mdPanel instead of $mdDialog I was able to achieve the desired effect; I forked my demo to reflect the changes – these are the highlights:
var dialog = {};
function showDialogs() {
showDialog("one");
showDialog("two");
}
function showDialog(id) {
var config = {...};
$mdPanel.open(config)
.then(function(panelRef) {
dialog[id] = panelRef;
});
}
function closeDialogs() {
var id;
for(id in dialog) {
closeDialogSpecific(id, "Closed all for a reason");
}
}
function closeDialogSpecific(id, reason) {
var message = reason || "finished: " + id;
if(!dialog.hasOwnProperty(id) || !angular.isObject(dialog[id])) {
return;
}
if(dialog[id] && dialog[id].close) {
dialog[id].close()
.then(function() {
vm.feedback = message;
});
dialog[id] = undefined;
}
}
I would suggest having two or more dialogs up at the same time isn't ideal and probably not recommended by Google Material design.
To quote from the docs
Use dialogs sparingly because they are interruptive.
You say:
when an AJAX request is finished fetching data, I would like my
loading spinner to close, but not any error dialog that may be the
result of the fetching.
My solution here would be to have one dialog which initially shows the spinner. Once the request is finished replace the spinner with any messages.

Sencha Touch: Clicking a button rapidly will push a view twice

Say I have a button that triggers a push of a new view.
I noticed that if I click it more than once, fast enough, it will push the same view twice.
You can mimic this behavior using their official docs on this page, where they have a live sample:
http://docs.sencha.com/touch/2-0/#!/guide/navigation_view
the clear question is, simply how to prevent it?
Another method is to check what the active view is, and only push if it is not the same as the view you are about to push. I've tested this and it works.
E.g.
if (this.getNavigationView().getActiveItem().xtype != "someView") {
this.getNavigationView().push({ xtype: "someView" });
}
Extending jayteejee's answer, I've overridden the push method in a custom navigation view, like this:
Ext.define('BT.navigation.View', {
extend: 'Ext.navigation.View',
xtype: 'btnavigationview',
push: function (view) {
if(this.getActiveItem().xtype != view.xtype)
this.callParent(arguments);
else
console.warn("Prevented pushing a potentially duplicate view of xtype: " + view.xtype);
}
});
I'm not totally sure if the xtype assumption is safe enough, but I can't think of any situation in my current app that would require one view pushing another view of the same type onto the navigation stack. So, the solution works for me, and it's pretty neat. The warning is there to save me headache later on and possibly pulling my hair out trying to work out why push wouldn't work!
Masking successfully prevents double tapping problem.
In my code I'm using two functions for mask/unmask navigation container:
/**
* Mask container with rolling wheel. Usually need if Ajax-request is sent to the server and app waiting for response
* Best practice is masking the current navigator container, to prevent blocking whole app. Method warns if no container
* is defined. In some cases warning could be suppress with parameter
*
* #param container
* #param {boolean} [suppressWarning]
*/
startLoading: function(container, suppressWarning) {
var loadingComponent = container;
if (!loadingComponent) {
// <debug>
if (!suppressWarning) {
console.warn('Please define navigator container for non-blocking operation, or define suppressWarning parameter');
}
// </debug>
loadingComponent = Ext.Viewport;
}
// var lastMaskedContainer = container;
this.lastMaskedContainer = container;
loadingComponent.setMasked({
xtype: 'loadmask',
message: 'Loading...'
});
/*
Ext.defer(function() {
lastMaskedContainer.setMasked(false);
}, Pipedrive.app.maskingTimeout * 1000)
*/
},
/**
*
* #param {Ext.Container} container
* #param {boolean} [suppressWarning]
*/
stopLoading: function(container, suppressWarning) {
var loadingComponent = container;
if (!loadingComponent) {
// <debug>
if (!suppressWarning) {
console.warn('Please define either navigator container for non-blocking operation, or define suppressWarning parameter');
}
// </debug>
loadingComponent = Ext.Viewport;
}
var alreadyMasked = loadingComponent.getMasked();
var lastMaskedContainer = this.lastMaskedContainer;
if (!alreadyMasked && !suppressWarning) {
// <debug>
if (lastMaskedContainer != container) {
console.warn('Found Start/Stop Loading inconsistency. Please revise code'
+ (container ? '. Container: ' + container.getId() : 'Ext.Viewport')
+ (lastMaskedContainer ? ', last masked container: ' + lastMaskedContainer.getId() : '')
);
}
// </debug>
loadingComponent = Ext.Viewport;
}
loadingComponent.setMasked(false);
}
than in the tap handler:
onDealDetailsTap: function(ct) {
console.log('onDealDetailsTap', ct);
var form = ct.getReferenceForm(),
navigatorContainer = this.getNavigatorContainer(form),
model = form.getRecord();
UiHelper.startLoading(navigatorContainer);
Ext.Viewport.fireEvent('detailfields', {
title: model.get('title'),
id: model.get('id'),
store: 'DealFields',
navigatorContainer: navigatorContainer
})
},
to cleanup the loading mask:
control : {
activitiesContainer: {
push: 'onPushActivitiesContainer'
},
onPushActivitiesContainer: function(ct) {
//console.log('onPushActivitiesContainer', ct);
UiHelper.stopLoading(ct);
},
especially it is cool for waiting for long-timed ajax requests....
Cheers, Oleg
Just suspend the events on the button when it's tapped and resume them when the view is pushed
button.suspendEvents();
...
button.resumeEvents();
I don't think there is another way. As a developer or a user, when you tap a button twice, you expect the event handler to be called twice.
Hope this helps
simply mask the entire container and then unmask it; create a ref for the container or panel in which the button exists in your controller and on tap set:
ref.setMasked(true)
After the new view is pushed simply unmask by
ref.setMasked(false)
Another way is to flip a parameter once the list item has been tapped once, like this:
{
onListItemTap: function () {
if (!this.tapped) {
this.tapped = true;
...
}
}
}
Of course, that only works if you are destroying the list view as soon as the user goes to a different screen.
I created a method for checking this:
ENSURE_NO_DOUBLE_TAP : function(classNameToPush) {
if (Ext.getClassName(Ext.getCmp('MyViewport').getActiveItem()) == classNameToPush) {
return false;
}
return true;
}
Then from your app before anything that could be double tapped is processed:
if (!ENSURE_NO_DOUBLE_TAP('MyApp.view.View')) {
return;
}
If you are listening to the tap event of a button using listeners,then here is
my solution:
listeners : {
release : function(){
if(this.getDisabled())return false;
this.setDisabled(true);
this.fireEvent('tap');
},
tap : function() {
//do what you want
}
}
Extending on jayteejee's and Merott's answers, I've added some code to intercept on multiple fast pushes to not only prevent duplicates but to prevent pushing of different views as well before the page transition completes. Think of a user tapping different list items.
Also notice the view.destroy(); method in the else block to prevent view instances from heaping up in memory.
Ext.define('Overrides.navigation.View', {
extend: 'Ext.navigation.View',
xtype: 'ovrnavigationview',
interceptPush: false,
push: function (view) {
var activeItem = this.getActiveItem();
// Prevent multiple pushes & duplicates
if (!this.interceptPush && activeItem.xtype !== view.xtype) {
// Set interceptPush
this.interceptPush = true;
// Reset interceptPush after 500 ms
Ext.defer(function() {
this.interceptPush = false;
}, 500, this);
// Handle push
this.callParent(arguments);
} else {
// Warn developer
console.warn("Prevented pushing view of xtype: " + view.xtype);
// Destroy view
view.destroy();
return false;
}
}
});
You can just use the "itemsingletap" event.
If you want to support double taps as well, make a second listener for "itemdoubletap" and invoke the same function, both listeners work fine together.

Resources