Imitating a click on a dynamically added element with vanilla JavaScript - javascript

I have a ReactJS-based website which I wanna navigate programmatically. Basically, the workflow is looped:
Click on Element1.
The web page code is dynamically altered, during which Element2 is added.
Click on Element2.
Etc, until, after clicking on ElementN, the web page returns to the initial state and Element1 is displayed again. Clicking on those elements is what I wanna automate from within the website itself.
I have an access to the JS file that is responsible for the website contents creation and alteration. I can locate the code which describes the elements I'm interested in, and add any extra event listeners if need be.
I'm almost completely new to the client-side development, so my approach to solving this task is purely intuitive. So far my idea was to add an event listener for some sort of "onAdded" event which would fire when the element is added to DOM, and from that listener call the "onclick" listener (or dispatch the "click" event in some other way). However, i can't find any events that would indicate an addition to the DOM tree.
So, strictly speaking, i have two questions:
Is the approach described above viable (and adequate)? If so, then how exactly do i accomplish it?
If i'm doing it all wrong then what would be the right way to accomplish my task?
Edit 1
As per Matthew Herbst's suggestion, I looked into React lifecycle methods like componentDidMount. Turns out, the elements I wanna automate clicking on are not independent React component but some other component's contents added inside the render method with a huge chain of createElement calls.
So now the problem switches from detecting a moment when a particular element is added to the DOM structure to finding a way to interact with it.
The easy (and ugly) way to do it, as I currently see it, is to use the window object from componentDidMount, locate the element I wanna click by its data-reactid attribute (which is a string of dot-separated digits which, from what I can tell, is generated dynamically and reflects the element hierarchy) and then dispatch the required DOM event.
It might work, but if the document structure changes then the values of the data-reactid then it's all broken again. I would prefer to somehow dispatch the React's own onClick event properly, but I don't know how. I tried calling the function that gets passed to the createElement method as a value of the onClick property from componentDidMount, but for some reason it doesn't work.
I have also tried the method of interacting with DOM described in this article, but in my case the ReactDOM object doesn't seem to be defined.
Since I'm not much of a client-side developer, especially not a React guru, I don't really see the whole picture of how the website's client logic is working, and the code I'm working with seems to be minified/obfuscated to make it even harder. So if anyone could provide any specific suggestions without sending me to read all the React documentation, I'd be grateful.

Okay, I'm not sure if a question as profane as mine should be answered at all, but it looks like I got it figured out.
The minified/obfuscated code I had to deal with still had prominent features of a React application, such as objects with series of callbacks like render, componentDidMount or componentDidUpdate. So, just like Matthew Herbst suggested, I looked into the things they do.
The elements clicking on which I needed to automate were created inside the render methods by long chains of createElement calls which looked something like this:
T["default"].createElement("li", null, T["default"].createElement("div", {
className: P["default"].img
}, T["default"].createElement("img", {
src: n(600),
alt: ""
}), T["default"].createElement("b", null , "+3")), T["default"].createElement("h4", null , "«Header text»", T["default"].createElement("b", null , "+3")), T["default"].createElement("p", null , "Description"), T["default"].createElement("div", {
className: P["default"].btn
}, T["default"].createElement(A.Btn, {
mod: "info",
onClick: this._router.bind(this, "/gtr", "gtr")
}, "Play")))
I should have posted that code in the original post, but back then I didn't really understand what's going on here.
It turns out, the object referred to by T["default"] is a React object, meaning that T["default"].createElement calls were actually equivalent to the React.createElement ones.
Then, the article by James K. Nelson helped, which explained that in order to locate a specific child of a component I need to assign a ref attribute to it. I needed to access that "Play" button, so I tried adding a ref property to what looked like its descriptor object, so it would look like
T["default"].createElement(A.Btn, {
mod: "info",
ref: "automatedElement1",
onClick: this._router.bind(this, "/gtr", "gtr")
}, "Play")
, and it worked. Now, inside the componentDidUpdate method I could use the code like
if (this.refs.hasOwnProperty("automatedElement1")) {
var buttonElement = this.refs.automatedElement1;
}
Unfortunately, I didn't quite understand how to get from this object to its DOM reflection, but by simply studying its contents I managed to find a property which corresponds to the value of the data-reactid attribute of an HTML element of that button. So then I found no smarter solution than to acquire the element's DOM node by using the document.querySelector method:
var buttonNode = document.querySelector('button[data-reactid="' + buttonElement._reactInternalInstance._rootNodeID + '"]');
if (buttonNode) {
buttonNode.click();
}
This might be a bad solution, but that's the solution I managed to find in my situation, and it works. Hope it helps anyone who had a rough encounter with a React application and didn't know where to start.

Related

Is there a way to use vanilla JS to manipulate data in a Vue component?

I'm working with a form that may be a bit over-engineered, and I'm trying to write a script to step through the form and submit it. Most of the form is pretty hackable, but there's a 3-part date input which is just not responding to my attempts to manipulate it programmatically.
The date field works, if I click or tab to it and begin typing. But if I manually dispatch events, even ones that are identical to what it receives when I type and have exactly what the code seems to be looking for, I can't get it to hold onto its values and perform validation. I've tried a lot of variations of this. I've tried manually dispatching a custom event that matches a custom Vue event it should be listening for.
Is there a way to instead manipulate the data of the Vue component directly? To force it to have a certain "monthValue" for example, without intermediate events? I don't expect that there is, but hopefully I'm missing something. Please note that I do have the ability to refactor the form, but that should be an absolute last resort.
As far as I can tell, no, there isn't a way to do this. But it wasn't necessary.
The solution involved being more careful in looking at what the components were actually trying to do. In this case they were input elements using v-model, which is shorthand for a combination of #input (event listener) and :value attributes[1]. This meant that dispatching synthetic input events with the correct data attribute could convince Vue to accept the value and retain it (whereas any value dispatched in any keyboard event would be ignored entirely). This only addressed half of my problem with this particular form, but it is the correct solution for the question asked.
[1] https://vuejs.org/guide/essentials/forms.html

knockout.js change behavior of observable subscribers based on where a change event originates from

I think I found a hard requirement to change behavior in a beforeChange handler based on the bindingContext of where a change is coming from. I already made some changes where a beforeChange handler gets to see the newValue along with the oldValue and where the handler can return boolean false to prevent the change from going forward.
The reason for this is the same objects may be bound to two (or more) different html nodes. The parent node may use different observables to wrap the same model object. But at some point, the child nodes are bound to properties through the same observables, sharing the same observable in more than one view element. When a change happens, I need to know from which of the two view elements the change originated.
I can save the bindingContext where the observable was made in the closure of my callbacks. But now I need the event to tell me what the bindingContext of the element was which just initiated the value change.
I can see the binding context somewhere in this domData thing, as follows: the valueUpdateHandler is called with the event object as argument it doesn't care about. But the event.target is our element that originated the change. I can see on that event target something like: "__ko__1655641582113" which I guess I am supposed to access with this ko.util.domData.
ko.utils.domData.get(arguments[0].target, "1__ko__1655641582113")
and lo and behold here I get an object with {alreadyBound: true, context: ko.bindingContext}
So I could force my way into this secret place and then get that context
ko.utils.domData.get($0, "1__ko__1655641582113").context.$parentContext.$rawData
and from there I could tell if I am in the element that should go forward with the change or not.
I am sure if anybody read this far that you'd be puzzled asking why in the hell I want to do that. But think about it, the observables are cool and all, but you sometimes need different behaviors based on where in your app the user is. In some areas they are just supposed to view, in others they can make (controlled) updates.
In other words, I know what I want is right and just, but I am wondering why it is so hard to get to what I want. For example, why the domData property is such a cryptic thing with a (timestamp) instead of a constant predictably named property. It's as if to tell me that I am making a big mistake even going there...

Alternative to Falsely Triggering an Event

TLDR Below
JS Fiddle To Demo
I've been really involved in recreating the tools that are foundations of premiere JS Libraries to better improve my skills. Currently I'm working on functional data-binding a la Angular.
The idea of data-binding is to take data and bind it to elements so that if manipulated all elements subscribed will change accordingly. I've gotten it to work but one thing I hadn't considered going into it was the issue with innerHTML vs value. Depending on the element you need to change one or the other( in the demo above you'll see that I needed to specifically single out the button element in a conditional statement because it has both, but that's kind of a fringe case )
The issue is that in order to capture a SPAN tag update I needed to trigger an event to happen, and the easiest one to manipulate for Text Boxes/Textareas was 'keyup'.
In my function then, if you pass in an element with no value property we assume you're going to be updating innerHTML, and we setup an observer to determine if the element ever mutates, and if it ever does, the observer will emit a 'keyup' event.
if (watchee.value == void(0)) {
var keyUpEvent = new Event('keyup');
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
watchee.dispatchEvent(keyUpEvent);
});
});
observer.observe(watchee, {
childList: true
});
}
Now it may just be my paranoia, but it seems like I might be tunneling into a can of worms by faking 'keyup' on an element that doesn't natively have that support.
TLDR:
I'm curious if there's an alternative way to make, a.e. a span tag reactive other than faking a 'keyup'/'keydown'/'change' event? For instance, is there a way that I can make my own pure event(by pure I mean not reliant on other events) that checks if innerHTML or value has changed and then performs a function? I know that this is probably possible with a timer, but I feel like that might hinder performance.
EDIT: just an aside. In the demo the function called hookFrom works by taking a DOM node and returning a function that will take the receiving dom node and continues to return a function that will take additional receiving dom nodes. :
hookFrom(sender)(receiver);
hookFrom(sender)(receiver)(receiver2);
hookFrom(sender)(receiver)(receiver2)(receiver3)(receiver4)...(receiver999)...etc
JS Fiddle To Demo (same as above)
There is nothing inherently wrong with creating a similar event on a DOM node that doesn't natively have that functionality. In fact this happens in a lot of cases when trying to polyfill functionality for separate browsers and platforms.
The only issue with doing this sort of DOM magic is that it can cause redundancy in other events. For instance the example given in this article: https://davidwalsh.name/dont-trigger-real-event-names shows how a newly minted event using the same event name can cause problems.
The advice is useful, but negligible in this specific case. The code adds the same functionality between text boxes, divs, spans, etc... and they are all intentionally handled the same way, and if the event would bubble up to another event, it would be intentional and planned.
In short: There is a can of worms that one can tunnel into while faking already explicitly defined event names, but in this case, the code is fine!

Can the DOM be differentially updated?

First and foremost, I've done extensive research about this, under different names that I think could apply such as "Javascript differential templating", "Javascript update DOM without reparsing", "Javascript render UI using deltas" and other variations. Pardon me if I missed an existing thread that covers my question.
Essentially, I would first like to know if most DOM parsers in browsers do the following already, even though I'm fairly sure the answer is no: do they update the DOM differentially (i.e. only the nodes that have changed in the same tree since the last update) when a node is modified? Like I said, I figure the answer is no and they actually reparse and rerender the updated node and everything in its tree.
Which brings me to my question: is there any Javascript library that allows to manage differential updates to a data model and to the DOM?
I realize I might not be really clear about this, so I will provide some code to explain what I mean: http://jsfiddle.net/btZ3e/6/
In that example, I have an "event queue" (which is really a timeline) with events in it. UserEvents all have a unique ID. The way it works now is that UserEvents can execute() and undo(), in the former they modify data in memory (myAppManager.dataModel) and append a <p> in the DOM while in the latter they undo these changes. (Each UserEvent's undo() is defined within the execute() of the same UserEvent as to allow more flexibility, one could consider moving events around independently)
Then, there is myAppManager.render() :
var myAppManager = new function () {
this.dataModel = {
someValue: 0,
disableButton: false
};
this.render = function () {
$('#displaysomevalue').text(this.dataModel.someValue);
$('#go').prop('disabled', this.dataModel.disableButton)
}
}
How would it be possible (is it at all?) that myAppManager.render() only updates what has changed since the last update? I reckon this would mean that I would have to have some sort of differentiation system in my data model too. Ultimately I'm wondering about this because I'm gonna be receiving multiple new UserEvents per second (let's say 20-30 per second at worst?) via websockets and I was wondering if I would need to rerender my whole UI for every new piece of data I get. I investigated into Javascript templates to see how they do it, and it seems they all just go this route:
document.getElementById('someTemplateContainer').innerHTML = someTemplateEngine.getHtmlOutput();
I doubt however they need to refresh as often as I need to in some instances. Is there prior work on this? Did I miss anything? Thank you very much!
The way Backbone.js, as an example, does this is that models (name:value pairs basically) are backed by a view/template, and that models have events associated with them like change. Let's say you have a <ul> where each <li> is one Backbone view, backed by a model.
You could bind every model's change event to re-render its own view (and ONLY its own view). So when the 5th <li> gets its name changed, it will re-render just the contents of that <li>, and the rest of the <ul> is undisturbed.
That lets only new or updated models have their DOM nodes touched and updated.
The difference is that you don't need to know 'what parts of the whole <ul> have changed and just render those', because you've actually decomposed the problem to a series of smaller ones, each of which are responsible for their own rendering and updating logic. (I'm sure other frameworks have similar patterns, and you can do them in vanilla JS too no doubt)

how to replace HTML with jQuery but keep event bindings

this is more a strategic question than a specific one, but I think it's precisely asked so here goes:
let's say I have a page or ap that has 3 separate sections. A change on part of the form sends an ajax post to the server, and this requires a change in section two. I want to send back the re-processed HTML output of section 2, and have this replace the original state of section 2
but, section 2 has many elements that have change, click, drag etc. bindings - and from experience when I do a html replace, I lose all my bindings.
HOWEVER, this leaves me with rewriting certain things in many of the elements in section 2 individually so as not to lose the bindings.
I KNOW there's an easier approach to this, seems like a common problem. Can anyone provide me with the "aha" part of this question, and perhaps a few examples or links? I really appreciate it.
You will need to divide the problem into two sections
Handling events
This can be done using event delegation using $.on(). ie instead of registering events on the element you register on a parent which will not be removed
Ex:
$('.container').on('click', 'a', function(){
//do something
})
Handling widgets like draggable
Here I think you are out of luck because I don't see any other way than to reinitialize those widgets once the new dom elements are added
Ex:
var ct = $('.container').html('');
ct.find('li').draggable({})
You could use Event Delegation, so you don't have to re-bind.
From this ticket
In case anyone arrives here as I did looking for a quick alternative to replaceWith() that keeps attached events, it can be done using a combination of existing functions in the current API:
this:
old.replaceWith( new );
can be changed to:
old.before( new ).detach();
Both return a handle to the removed element, so depending on the use case it should be pretty simple to change.

Categories

Resources