How to reference dynamically created intersection observers in a JS library - javascript

I'm trying to build a micro JS library to emulate InDesign text threading on the web using this constructor pattern boilerplate. The idea is the user feeds the function DOM selectors in a desired order, e.g.
var thread=new TextThread(['#container1','#container2']);
thread.build();
And from there the library splits the text contents of the first container into spans for each word. When a word is out of view, the library will move it to the following container in the order.
Here's where IntersectionObserver comes in: I need to assign an observer to each container fed to the constructor, and then have it observe the spans inside it. When a span goes out of view, it fires the observer callback, which should loop through all the observers with the .takeRecords() method to check which spans are out of view in each container. I've gotten the observers to fire their callbacks with no problem, but the issue is referencing all the observers within the callback.
What I've tried is storing an array variable in the self-executing function from the boilerplate, and then when the constructor is built, it pushes the observers to that array.
var observers=[];
var Constructor = function (selectors) {
/*placeholder for code that selects and assigns the containers*/
containers.forEach((item, i) =>{
var options={
root: item,
rootMargin: '0px',
threshold: 1.0
}
var newObserver=new IntersectionObserver(callback,options);
observers.push(newObserver);
})
};
Then when the text is split into spans by word
words.forEach((word,w) =>{
observers[ current container index ].observe(word); });
In the callback, entries for the observer that fired the callback are visible. However, if I try to reference the other observers using my observers array variable, takeRecords() returns an empty array. Here's the callback I'm testing:
function callback(entries){
//the entries for the observer firing the callback are returning correctly
console.log(entries)
//the forEach below doesn't work though. It returns an empty array for each observer.
observers.forEach((item, i) => {
console.log(item.takeRecords())
});
}

In the callback, entries for the observer that fired the callback are visible. However, if I try to reference the other observers using my observers array variable, takeRecords() returns an empty array - even for the observer that fired the callback!
It's working fine, as documented:
takeRecords() returns an array of IntersectionObserverEntry
objects, one for each targeted element which has experienced an
intersection change since the last time the intersections were checked,
either explicitly through a call to this method or
implicitly by an automatic call to the observer's callback.
Note: If you use the callback to monitor these changes, you don't need to
call this method.
(Emphasis mine)

you declared the observers as a private variable for the class. You either need to make it public, or static. I am not familiar with your design but I would suggest to make it static. From an architectural point of view making those static makes more sense for me

Related

Redux Reselect - selector with argument input is recalculating

I have the following selector:
const getAllAddresses = (withStartEnd) =>
createSelector(
[getAllAddressesSelector, getStartAddressSelector, getEndAddressSelector],
(all, startAddress, endAddress) => {
if (!withStartEnd) return [...Object.values(all)];
return [startAddress, ...Object.values(all), endAddress];
}
);
I noticed that the selector is re-calculating every time, event when all, startAddress and endAddress do not change. If I remove the input for the selector function, to something like this:
const getAllAddresses = (
createSelector(
[getAllAddressesSelector, getStartAddressSelector, getEndAddressSelector],
(all, startAddress, endAddress) => {
return [startAddress, ...Object.values(all), endAddress];
}
)
);
Then everything works as expected and the selector does not re-calculate on every call. Seems like I missing something in the selector concept. Any help would be much appreciated.
Update:
Please refer to How do I create a selector that takes an argument?
In short: the way you did it, will work only if you pass static arguments and create a factory function outside of mapStateToProps. For dynamic arguments it's more complex and please follow the resource I already mentioned above.
The reason your selector is recalculated each time mapStateToProps is called is that calling getAllAddresses will create a new instance of createSelector and the memoization won't work.
Original answer:
In short, reselect determines input selector changes, based on identity check ===.
Therefore, if your input selectors always create and return a new object or array, then your selector will be recalculated each time.
In order to fix the recalculation issues:
Make sure your input selectors always return a reference, instead of new object / array.
Or, if a new object / array is the proper returned value, then you have to customize the equalityCheck for defaultMemoize
From the reselect docs:
Why is my selector recomputing when the input state stays the same? (please follow the link, there are great examples)
Check that your memoization function is compatible with your state update function (i.e. the reducer if you are using Redux). For example, a selector created with createSelector that recomputes unexpectedly may be receiving a new object on each update whether the values it contains have changed or not. createSelector uses an identity check (===) to detect that an input has changed, so returning a new object on each update means that the selector will recompute on each update.

what is prevObject and context in pushstack in jquery ?

I understand how the pushstack function works and I also understand how to use it for my plugins (I guess that's what its most used for , just for internal use and for end() to function properly and other similar methods) .
now below is the jquery source of pushstack , have a look :
pushStack: function( elems ) {
// Build a new jQuery matched element set
var ret = jQuery.merge( this.constructor(), elems );
// Add the old object onto the stack (as a reference)
ret.prevObject = this;
ret.context = this.context;
// Return the newly-formed element set
return ret;
},
theres a lot going on in that function really and I kind of get most part of it , but I have a small problem , understanding the below lines of code :-
ret.prevObject = this;
ret.context = this.context;
what is prevObject and context ? can somebody give me a clue , it definitely does't seem to be a javascript thing ?
Basically prevObject is used to allow flexibility for the jquery selectors chaining syntax.
Most of jQuery's DOM traversal methods operate on a jQuery object instance and produce a new one, matching a different set of DOM elements. When this happens, it is as if the new set of elements is pushed onto a stack that is maintained inside the object. Each successive filtering method pushes a new element set onto the stack
Everytime you make a new filter a new jquery instance is constructed matching your selector and the previous one is stored in it to allow the .end() and the addBack() functions to work (most of the DOM transversal functions use pushStack internally). If this property were not used every selector will forget about the previous one and it will not behave like a stack. Think parent instead of prevObject and it will make it easier to understand.
The context property is deprecated in jQuery 1.10 and only used for supporting live() method but it must be added because the returning object is constructed merging an empty jQuery constructor as first parameter and a set of elements as the second.
var ret = jQuery.merge( this.constructor(), elems );
Because .merge returns the first array modified it's context property might not have the correct value so is overwritten with the correct one this.context
Check this jsfiddle and open the console. You will see that the first value of prevObject is the document as we are making a new selector and then is the ul element since we filter using find. Moreover you can go to the last filter and lookup the whole chain of selectors up to the document again.
I recommend you to use the JQuery API instead of this property as reference in production but this will allow you for example to know the result of all the selectors that have been applied to obtain a given set of DOM elements.

Some help please in understanding a JavaScript function call. Is it a closure or recursive?

I am using D3js to Tree Layout to present some data, working result here:
http://jsfiddle.net/chrisloughnane/vV3Sc/
I would like to learn how to correctly navigate the nodes in the tree. So for example I could setup a function to set all nodes to either open/closed with a single click or perhaps have a category index so a visitor could click year one and that node and all children would be set to open.
I have stepped through the code and I can't get my head around
function toggleAll(d) {
if (d.children) {
d.children.forEach(toggleAll);
toggle(d);
}
}
root.children.forEach(toggleAll);
To me I call the function with the root and then if the passed node has children for each child of that node I pass it the function name?! When there are no more children i.e. it has reached a leaf it toggles the value from children to _children or the inverse.
What is happening when I'm passing forEach toggleAll?
The key here is forEach, which is a native javascript function.
(documentation here)
This method executes the callback (toggleAll) for each element of the array.
The d parameter will be passed to the toggleAll function by forEach. So it doesn't come from the closure.
Now since toggleAll calls forEach again, it does indeed go into a recursive pattern to the next level of the tree, untill d.children is empty.
How did you produce this working code if you do not understand what's happening?
What is happening when I'm passing it toggleAll?
Do you understand what "it" refers to in this question?
Anyway, you are working with JavaScript arrays (or at least array-like things), you are calling the forEach method, which accepts a function and invokes that function on each of the elements. So when you first call forEach on root.children, toggleAll is invoked with each element in the array root.children. You then call forEach again (recursively now) for any subsequent childnodes.

How does jQuery act on multiple selectors?

I've been wondering for a little while how jQuery acts on multiple selectors. For instance:
$("p").css({"border":"1px solid #000"});
Performs the subsequent function on all p tags. I've had a look through the jQuery source but to be honest it's an extensive read when you're trying to work out one specific bit of functionality. My assumption is that there's some kind of stack whereby css() and other functions merely act on the current stack, which is divined by the selector function.
Other than that, I can't work out how it could be replicated as, I think, there's no way in javascript to return multiple objects to execute a function on. E.g.
House.first_bedroom.size = "large"
House.second_bedroom.size = "small"
House.all_rooms().alertSize();
alertSize() would have to be a member function of some collection of objects rather than a member function of each room object that is returned by all_rooms()?
First, jQuery functions (generally) return a jQuery object, which acts like an array and keeps track of the current set of matched elements. Second, internally each jQuery function makes extensive use of the each() function to iterate over the matched objects, perform subsequent actions and construct the new jQuery object to return. Some functions do return something other than jQuery, like get(). These functions cannot be chained. Chaining is only possible when the function returns a jQuery object. Because the returned object is a jQuery object it has all the functions of jQuery available to it.
The jquery constructor ($(...)) always return a jquery object. You can think of it as a fancy array. The items selected are stored (looks like this is called context in the source).
So then on your object you're calling the function css... See jQuery.fn.css in the source. It basically calls a function which performs the delegate (setting or getting the css) on each item in the context.
Perhaps the DOM is parsed and all elements matching the criteria are added to an array? Or.. something more efficient? :)
Similarly, for event handling, a handler is assigned to each element in the array?
I'm just stabbing in the dark here.

Event.observe in a loop and variables

To put things in context, I'm loading a list of items via Ajax, creating a div with main info for each one and want to display details on page when clicking on it. So I have that code in my onSuccess :
items = transport.responseText.evalJSON(); // my list of objects that contains all the details I'll need for that page
for (var itemID in items)
{
newDiv = ... // Creating my div with main infos
$('myDiv').appendChild(newDiv);
// More code to make everything look pretty and that works fine
Event.observe(newDiv, 'click', function() { loadItem(itemID); });
}
loadItem is my function that will display all the item details. And my problem is that itemID isn't replace by its value when creating the observe event, so it always returns the same ID for all items.
Any idea how I can fix that ? I checked bind on prototype doc, that seemed to be made for those cases, but probably didn't get it, since it wouldn't work for me.
For a minimal-impact fix, replace your Event.observe line with this:
Event.observe(newDiv, 'click', loadItem.curry(itemID));
Explanation:
In your original code, the event handler functions you're creating close over (have an enduring reference to) the itemID variable, and so will use the value of that variable at of when the event handler is called, not as of when you assign it to the event. That value will be the last value that itemID has in the loop — for all of the handler functions. More about closures here.
With the minimal-impact revised code, we use Prototype's curry function, which will create a function for you that, when called, will call the underlying function with the arguments you gave curry. (The name is from mathematics; Haskell Curry came up with the technique, though there are arguments he wasn't the first to do so.) We could do the same thing ourselves:
items = transport.responseText.evalJSON(); // my list of objects that contains all the details I'll need for that page
for (var itemID in items)
{
newDiv = ... // Creating my div with main infos
$('myDiv').appendChild(newDiv);
// More code to make everything look pretty and that works fine
Event.observe(newDiv, 'click', prepLoadItem(itemID));
}
function prepLoadItem(id) {
return function() {
loadItem(id);
};
}
...but because Prototype has a general-purpose function for it, we don't have to.
Off-topic: Is items an array? If not, ignore this off-topic comment. If so, don't use for..in to loop through it, or at least, not unless you take some precautions the code above doesn't to do it properly. Details here, but for..in is not for looping through the indexes of an array; it's for looping through the properties of an object. Array objects may well have properties other than array indexes (and in fact, if you're using Prototype, they do.)

Categories