mutation observer production infinite loop - javascript

I'm writing a function using the mutation observer with jQuery to register changes to the DOM, specifically when a new node is added so i can change its content:
$("SELeCTOR GOOD" ).click(function(){
var targetNode = $(this).find('.content').get(0);
var config = { attributes: true, childList: true, subtree: true, attributeOldValue: true };
// Callback function to execute when mutations are observed
var callback = function(mutationsList) {
for(var mutation of mutationsList) {
if (mutation.type == 'childList') {
var courses = $(targetNode).find('.courses').get(0);
$(courses).find('.coursebox.clearfix').each(function( index,item ) {
var attacherURL = $(item).find('.coursename a').attr('href');
var moreInfoURL = '<a class="btn btn-outline-primary pull-right" href="'+attacherURL+'">More info</a>';
var oldHTML = $(item).find('div.moreinfo').html();
var newHTML = moreInfoURL + oldHTML;
//this following line is supposed to replace the html, but it creates an infinite loop
$(item).find('div.moreinfo').html(newHTML);
//end
});
}
else if (mutation.type == 'attributes') {
console.log('The ' + mutation.attributeName + ' attribute was modified.');
}
}
};
I've tried append/prepend as well, but everything creates the same infinite loop. As usual, any help is greatly appreciated.
Regards

Well, your modification causes another mutation, which results in you modifying it again, causing an infinite loop. A simple solution is to add a class to your element to mark it as already processed, and ignore already processed nodes (nodes that have that class). Another is just check if the dev.morinfo alreade has moreInfoURL inside it, and ignore if it already does

Related

Javascript checking whole document length in loop

I have a problem with validation size of HTML after some interactions on the page complete in the loop.
After first iteration do{}while(), I try to validate size of plain text on the page (outerHTML), but it don't changed and loop stops activity.
If i try to execute command >document.documentElement.outerHTML.length out from loop: in browser console - i see that change size happend, and it don't work inside loop, but why?
Can somebody explain to me, please, how to correct loop behavior?
var controlpagesize_int = 0;
do{
controlpagesize_int = document.documentElement.outerHTML.length
element_dom.click()
sleep_fc(2000)
}while (controlpagesize_int != document.documentElement.outerHTML.length)
PS: sleep_fc() - is a timeout function
Thank You all:)
Your code is just doing an infinite loop. The loop is not going to allow the DOM to update. That sleep code is doing nothing
To match what you think you are doing would need to be done with a interval or timeout
var controlpagesize_int = document.documentElement.outerHTML.length;
function checkDOM () {
if (controlpagesize_int === document.documentElement.outerHTML.length) {
window.setTimeout(checkDOM, 1000);
} else {
console.log('Page Has Changed');
}
}
checkDOM();
A better approach is probably to just use MutationObserver
const callback = function(mutationsList, observer) {
// looking at all the changes and find hello
const newNode = mutationsList.find(mutation => [...mutation.addedNodes].some(node => node.textContent &&
node.textContent === 'hello'));
if (newNode) {
console.log("updated with hello");
// if you found the change you are looking for, remove the listener
observer.disconnect();
}
};
const targetNode = document.body;
const config = {
// attributes: true,
childList: true,
subtree: true
};
const observer = new MutationObserver(callback);
observer.observe(targetNode, config);
window.setTimeout(function() {
const temp = document.createElement("div");
temp.innerHTML = "<p>hello</p>";
document.body.appendChild(temp);
}, 5000);

Getting Lost in Prototype Method Calls

apologies I realise there are a number of other threads that describe similar issues however I have not been able to find one that answers my question fully (at least in a way that I understand).
I use the following code to create an Object which manages the UI interactions for the various complicated instances of an input Form across my site.
Using Prototypes, I effectively end up with an object called categoryForm with various methods contained within:
- categoryForm.addEventListeners
- categoryForm.handlers
- categoryForm.validation
The last two are objects containing a number of different Methods.
The below code is a cut down version of my finished code, however should be sufficient to get the problem across, as the rest is variations on a similar theme.
The Issue I have is that, in the below example:
- I click '.addNewItems' on my table
- This triggers my listener, which calls the 'addNewTableItem' handler method.
- The handler then attempts to Loop through the Inputs, passing them through the 'validation.checkInputVal' method to validate each input before proceeding.
However, by the time we're in this loop, the scope of this has totally changed (as expected) and I have no idea how to refer to my categoryForm Object and call the 'validation.checkInputVal' method. I just get an error saying that this is not a function.(again expected)
Here's the code:
function categoryFormFuncs(){
// VARIABLES
var _this = this;
var table = $('table');
// EVENT LISTENER FUNCTIONS
this.addEventListeners = function(){
// Listen for the AddItemButton on Tables and call the addNewTableItem function
table.on('click', '.addNewItems', function(e){
// Find new ItemGroup and Collect Inputs into an Array
var newItemGroup = $(this).parents('.newItemGroup')[0];
// Send New Item Group and Table to be updated to the Handler
_this.handlers.addNewTableItem(newItemGroup);
});
}
};
// HANDLER FUNCTIONS
categoryFormFuncs.prototype.handlers = {
// Function to Create a NewItemGroup table row
addNewTableItem: function (inputGroup){
var validationcheck;
// Get all the Inputs
var inputs = $(inputGroup).find('input');
// Check Inputs are valid and Highlight them if not
for(var i = 0; i < inputs.length; i++){
validationcheck = validation.checkInputVal(inputs[i]);
if(!validationcheck.passed){
$(inputs[i]).addClass('input-inValid')
return
} else {
$(inputs[i]).removeClass('input-inValid')
}
};
// If Valid, turn each input into a Table Cell and clear the original Input value
var rowCells = ""
for(var i = 0; i < inputs.length; i++){
rowCells += "<td>" + $(inputs[i]).val() + "</td>";
$(inputs[i]).val("");
}
// Construct the new Table row and update the DOM
var newRow = "<tr class='itemGroup'>" + rowCells + "<td><span class='float-right remove-item fa fa-minus-circle'></span></td></tr>";
$(inputGroup).before(newRow);
}
}
// VALIDATION CHECKS
categoryFormFuncs.prototype.validation = {
checkInputVal: function(input){
if($(input).val()){
return { passed: true }
} else {
return { passed: false, message: "Input with no Value"}
}
}
}
var categoryForm = new categoryFormFuncs();
categoryForm.addEventListeners();
I have found one way to make this work which is to provide the validation method as an argument to the Handler:
function categoryFormFuncs(){
// VARIABLES
var _this = this;
var table = $('table');
// EVENT LISTENER FUNCTIONS
this.addEventListeners = function(){
// Listen for the AddItemButton on Tables and call the addNewTableItem function
table.on('click', '.addNewItems', function(e){
// Find new ItemGroup and Collect Inputs into an Array
var newItemGroup = $(this).parents('.newItemGroup')[0];
// Send New Item Group and Table to be updated to the Handler
_this.handlers.addNewTableItem(newItemGroup, _this.validation.checkInputVal);
});
}
};
// handlers
categoryFormFuncs.prototype.handlers = {
// Function to Create a NewItemGroup table row
addNewTableItem: function (inputGroup, checkInputVal){
var validationcheck;
// Get all the Inputs
var inputs = $(inputGroup).find('input');
// Check Inputs are valid and Highlight them if not
for(var i = 0; i < inputs.length; i++){
validationcheck = checkInputVal(inputs[i]);
if(!validationcheck.passed){
$(inputs[i]).addClass('input-inValid')
return
} else {
$(inputs[i]).removeClass('input-inValid')
}
};
// If Valid, turn each input into a Table Cell and clear the original Input value
var rowCells = ""
for(var i = 0; i < inputs.length; i++){
rowCells += "<td>" + $(inputs[i]).val() + "</td>";
$(inputs[i]).val("");
}
// Construct the new Table row and update the DOM
var newRow = "<tr class='itemGroup'>" + rowCells + "<td><span class='float-right remove-item fa fa-minus-circle'></span></td></tr>";
$(inputGroup).before(newRow);
}
}
// VALIDATION CHECKS
categoryFormFuncs.prototype.validation = {
checkInputVal: function(input){
if($(input).val()){
return { passed: true }
} else {
return { passed: false, message: "Input with no Value"}
}
}
}
var categoryForm = new categoryFormFuncs();
categoryForm.addEventListeners();
Alternatively I could also pass a reference to _this from the listener to the handler in order to access _this.validation.
Either way these all feel like very messy and clunky solutions.
My question is:
a) Is there a way, using my original deisgn to access the validation methods?
b) Is there a better established pattern for this type of Lister / Handler / Validation scenario that I should know about?
(Disclaimer: I am very new to programming (6 months) so apologies if my description is incorrect in any way.

Trying to create script to update event names

I am subscribed to a calendar and sometimes instead of the ' sign it shows '
I tried to make a script that gets the next 100 events (because getting all events doesn't seem to exist in Google Apps script). This works, I can see the events in the log. Now my question is, after it gets these events, how do I make it replace the title?
what I have for now is this:
function listNext100Events() {
var calendarId = 'primary';
var now = new Date();
var events = Calendar.Events.list(calendarId, {
timeMin: now.toISOString(),
singleEvents: true,
orderBy: 'startTime',
maxResults: 100
});
if (events.items && events.items.length > 0) {
for (var i = 0; i < events.items.length; i++) {
var event = events.items[i];
if (event.start.date) {
// All-day event.
var start = parseDate(event.start.date);
Logger.log('%s (%s)', event.summary, start.toLocaleDateString());
} else {
var start = parseDate(event.start.dateTime);
Logger.log('%s (%s)', event.summary, start.toLocaleString());
}
}
} else {
Logger.log('No events found.');
}
}
Also I know that the function to replace a title is
title.replaceText("'", "'");
How do I combine this knowledge into a working script?
Thank you!
Looking at your structure, I'm assuming that the title you're referring to is stored in the event.title attribute. If that's the case, this should work for replacing all instances (using regex because a regular string.replace only gets the first instance)
event.title = event.title.replace(/'/g, "'");

IndexedDB cursors

my question is pretty simple.
I can't understand an example from the MDN article about iterating a result from an IndexedDB. Specifically, I can't see any loop used for iteration. There is no for/while/do cycle. Here is the example:
function displayData() {
var transaction = db.transaction(['rushAlbumList'], "readonly");
var objectStore = transaction.objectStore('rushAlbumList');
objectStore.openCursor().onsuccess = function(event) {
var cursor = event.target.result;
if(cursor) {
var listItem = document.createElement('li');
listItem.innerHTML = cursor.value.albumTitle + ', ' + cursor.value.year;
list.appendChild(listItem);
cursor.continue();
} else {
console.log('Entries all displayed.');
}
};
};
The "loop" happens implicitly. Each successful advance of the cursor results in a "success" event, which will trigger a call to the handler assigned to the "onsuccess" property of the request. Thus, the iteration happens because of this line:
cursor.continue();

JsTree v3.0 drag and drop plugin. Reference target node upon dropping

I use drag and drop plugin of jsTree library (ver. 3.0)
With the following code I can bind to the end of drag'n'drop action, but I can not see a way to get the reference to the target node (the node I'm dropping on).
$(document).on('dnd_stop.vakata', function(e, data) {
// how to get target_node here?
});
I had same problem. I found other solution than event dnd_stop.vakata, which returns old data before changed position.
This works:
$('#jstree_demo_div').on("move_node.jstree", function (e, data) {
//data.node, data.parent, data.old_parent is what you need
//console.log(data);
alert(data.node.id);
alert(data.parent);
});
Another solution is to use the get_node() function on the jstree object.
$(document).on('dnd_stop.vakata', function (e, data) {
ref = $('#jstree').jstree(true);
parents = ref.get_node(data.element).parent;
});
You can get all parents with:
all_parents = ref.get_node(data.element).parents;
I had the same problem and had to get the ID within the dnd_stop event, so I came up with this:
$(document).on('dnd_stop.vakata', function(e, data) {
var t = $(data.event.target);
var targetnode = t.closest('.jstree-node');
var nodeID = targetnode.attr("id");
});
That way I can get the ID of the targetnode, for example.
$(document).on('dnd_stop.vakata', function(e, data) {
var inst = $.jstree.reference('#jstree');
console.log("END DROP:");
var sourceID = data.data.nodes[0];
console.log("Source ID: " + sourceID);
var targetNode = inst.get_node(data.event.target, true);
var targetID = targetNode[0].id;
console.log("Target ID: " + targetID);
});
If you need to do this via the check_callback then you can access the target node via the more parameter.
'check_callback': function(operation, node, node_parent, node_position, more) {
// operation can be 'create_node', 'rename_node', 'delete_node', 'move_node' or 'copy_node'
// in case of 'rename_node' node_position is filled with the new node name
if (operation === "move_node") {
if (more.ref.data.type === "folder") {
return true;
}
}
return false; //allow all other operations
}
To get the target node you use the dnd_stop.vakata event. Once you get the node you can access to the different properties like id:
$(document).bind("dnd_stop.vakata",function(e, data) {
var targetNode = $('#jstree').jstree(true).get_node($(data.event.target));
var nodeId = targetNode.id;
});
you just need to call:
'check_callback': function(operation, node, node_parent, node_position, more) {
// operation can be 'create_node', 'rename_node', 'delete_node', 'move_node' or 'copy_node'
if (operation === "move_node") {
var node = more.origin.get_node('fiche-1');
return true;
}
return true; //allow all other operations
}
Bind the listener after document is ready:
$(document).ready(function() {
$(document).on('dnd_stop.vakata', function (e, data) {
let ref = $.jstree.reference("#jstree");
let nodes = data.data.nodes.map(node_id => ref.get_node(node_id));
let parent_node_id = nodes[0].parent;
let parent = ref.get_node(parent_node_id);
});
});
jstree has an internal listener for dnd_stop.vakata.jstree that performs the ui logic. It's setup inside a $(function() {...}) i.e. when the document is ready. If you bind your custom function before jstree, you get the parent before the ui logic is executed.

Categories

Resources