function to mirror insertAdjacentHTML - javascript

I am trying to write a function to mirror the insertAdjacentHTML dom method Element.insertAdjacentHTML and here it is
function insertAdjacent(targetElement) {
'use strict';
return {
insertAfter: function (newElement, targetElement) {
var parent = targetElement.parentNode;
if (parent.lastChild === targetElement) {
parent.appendChild(newElement);
} else {
parent.insertBefore(newElement, targetElement.nextSibling);
}
},
insertAtBegin: function (newElement) {
var fChild = targetElement.firstChild;
if (!fChild) {
targetElement.appendChild(newElement);
} else {
targetElement.insertBefore(newElement, fChild);
}
},
insertAtEnd: function (newElement) {
var lChild = targetElement.lastChild;
if (!lChild) {
targetElement.appendChild(newElement);
} else {
this.insertAfter(newElement, targetElement.lastChild);
}
}
};
}
The function works fine when you insert two different element nodes at the beginning and the end as shown here. But the problem comes when i try to insert the same element node at the beginning and the end as shown here. It only inserts the element node at the end and not at both the beginning and end.
What could be causing this issue? Thank you

Because One element can't be insert in two place at the same time, if you want to do it, at each function's first line, add newElement = newElement.cloneNode(true); I've altered your 2nd jsfiddle, have a look.

The problem was that you are using the exact same element, which can only be placed in one place...
If you clone it, there shouldn't be a problem.
Here's your second fiddle exactly as you have written it, with an extra deepCopy function from this answer:
adjacentInsert.insertAtBegin(deepCopy(span1));

Related

Can you alter a statement in a javascript function after is run once

I'm having a hard time figuring this out. I'd like the xxp.run.pause(); to be replaced by xxp.run.play(); after the initial code below is actioned by a user once. I've tried creating a closure but I'm not sure I set it up right. Any help would be very much appreciated.
doSkip: function() {
XXP.run.removeListener('canper', XXP.Skip);
XXP.run.currentTime = XXP.skipTo;
XXP.run.pause(); // this is what I would like to change
}
You may set a flag somewhere which records whether or not the doSkip function has been called before:
var hasSkip = false;
doSkip: function() {
XXP.run.removeListener('canper', XXP.Skip);
XXP.run.currentTime = XXP.skipTo;
if (!hasSkip) {
XXP.run.pause();
hasSkip = true;
}
else {
XXP.run.play();
}
}
You can wrap the function in an IIFE with a persistent variable that checks to see if it's been paused. This has the advantage over having a separate variable elsewhere because hasPausedOnce is only needed for the doSkip function - no need to populate the outer scope (which can have readers of your code worrying about whether hasPausedOnce is going to be altered elsewhere)
doSkip: (() => {
let hasPausedOnce = false;
return function() {
XXP.run.removeListener('canper', XXP.Skip);
XXP.run.currentTime = XXP.skipTo;
if (!hasPausedOnce) {
XXP.run.pause();
hasPausedOnce = true;
} else XXP.run.play();
};
})()

Reuse of functions

I think I know the theory behind the solution but I am having trouble implementing it. Consider following piece of code:
this.selectFirstPassiveService = function () {
getFirstPassiveService().element(by.tagName('input')).click();
}
this.clickAddTask = function () {
getFirstActiveService().element(by.tagName('a')).click();
}
this.selectTask = function () {
getFirstActiveService()
.element(by.tagName('option'))
.$('[value="0"]')
.click();
}
this.saveTask = function () {
getFirstActiveService().element(by.name('taskForm')).submit();
}
getFirstActiveService = function () {
return services.filter(function (elem) {
return elem.getAttribute('class').then(function (attribute) {
return attribute === 'service active ';
});
}).first();
}
getFirstPassiveService = function () {
return services.filter(function (elem) {
return elem.getAttribute('class').then(function (attribute) {
return attribute === 'service passive ';
});
}).first();
}
};
To minimalize code duplication, I created two functions:
* getFirstActiveService()
* getFirstPassiveService()
My spec goes as follows:
it('Select service', function () {
servicePage.selectFirstPassiveService();
servicePage.clickAddTask();
servicenPage.selectTask()();
});
Both clickAddTask() and selectTask() use the function called getFirstActiveService(). Everything runs fine in clickAddTask() but when I use the function in selectTask(), some elements (which are present) cannot be found by protractor.
Here goes my theory, every command in getFirstActiveService() is queued in the control flow when the function is called in clickAddTask() and is then executed. When reusing the function in selectTask() the commands aren't queued, the instance created in clickAddTask() is used and therefore, some elements cannot be found since the DOM has changed since then.
Now first question: Is my theory correct?
Second question: How can I fix this?
Thanks in advance!
Cheers
I refactored it a bit and it works now.
The problem was in the test itself, not in the reuse of functions.

Best way to perform JavaScript code on specific pages/body class?

I know there are libraries made for this, such as require.js, but I don't want anything complicated. I just want to run JS code that runs when there is a specific class on the body.
My try at this was to create functions for each page code, and a function to check if the body has the class name to execute code, then run it.
var body = $('body');
initPageOnClass('.home', initFrontPage());
function initPageOnClass(className, func) {
if (body.hasClass(className)) {
return func;
}
}
function initFrontPage(){
// some code for the frontpage
}
Which works, but I fear this may be bad practice. Is it? I know the more pages there is, there will be more functions:
initPageOnClass('.home', initAboutPage());
initPageOnClass('.subscriptions', initSubscriptionPage());
initPageOnClass('.team', initTeamPage());
But I'm not sure if this would be a big no, or what. I want to do this properly.
What is the correct or best way to perform this task?
Id rather use some attribute in this case and map of functions. Your markup will have role attribute defined:
<body role=home>...</body>
And the script may look like:
var initMap = {
'home':initAboutPage,
'subscriptions': initSubscriptionPage,
'team': initTeamPage };
// getting initializer function by content of 'role' attribute
var initializer = initMap[ $('body').attr('role') ] || initAboutPage;
initializer();
And yet check my spapp - it's quite simple (60 LOC)
I think you're on the right path, but not quite executing properly and if you're going to have a bunch of different classes/functions that you check for, then I'd suggest a table driven approach, so you can maintain it by just adding/removing/modifying items in the table rather than writing new code:
var bodyClassList = {
"home": initFrontPage,
"subscriptions": initSubscriptionPage,
"team": initTeamPage
};
function runBodyInit() {
var body = $(document.body);
$.each(bodyClassList, function(cls, fn) {
if (body.hasClass(cls) {
// if class found, execute corresponding function
fn();
}
});
}
runBodyInit();
Or, if you're doing this on document.ready, then you might do this:
$(document).ready(function() {
var bodyClassList = {
"home": initFrontPage,
"subscriptions": initSubscriptionPage,
"team": initTeamPage
};
var body = $(document.body);
$.each(bodyClassList, function(cls, fn) {
if (body.hasClass(cls) {
// if class found, execute corresponding function
fn();
}
});
});

Function with memoization, how not to evaluate each time?

I'm writing a simple jQuery function that will swap some HTML elements for others when on certain viewports. The idea is simple:
<div data-swap-for="#element" data-swap-on="phone"></div>
Will insert the element with id #element after that line when the current media query corresponds to phone (the details about how that is done are not important).
My function looks like this:
jq.fn.swapElements = function(viewport) {
var targets = jq('[data-swap-for][data-swap-on='+viewport+']');
if (targets.length) {
console.log('Found elements to swap for', viewport);
} else {
console.log('Found no elements to swap for', viewport);
}
return {
on: function() {
console.log('Should swap elements for', viewport);
},
off: function() {
console.log('Should restore elements', viewport);
}
}
};
So whenever the screen enters the phone layout, it calls:
jq().swapElements('phone').on();
Which should do all the DOM transformations, and when it exits the phone layout, it calls:
jq().swapElements('phone').off();
Which should restore them.
My problem is that these two are creating a new evaluation of the var targets... part, resulting in:
As the output in the console, and I need this function to cache or remember the variables that it uses, so that the resulting console output is:
> Found elements to swap for phone
> Should swap elements for phone
That is, only evaluating the elements and saving the variables once per each call (a different viewport value should call for a new evaluation).
I've been looking into higher order functions and memoization, but I'm confused about how to apply this in this case and specially to a jQuery function.
Please help?
Thanks
You can use some variable (object or array) to cache already targeted elements.
var cache = {}; // Should be out of function
if (viewport in cache) {
var targets = cache[viewport];
} else {
var targets = jq('[data-swap-for][data-swap-on='+viewport+']');
cache[viewport] = targets;
}
I Would go with slightly different approach:
jq.fn.swapElements = {
var cache;
getTargets: function(viewport) {
if (viewport in this.cache) {
return cache[viewport];
} else {
var targets = jq('[data-swap-for][data-swap-on='+viewport+']');
if (targets.length) {
console.log('Found elements to swap for', viewport);
} else {
console.log('Found no elements to swap for', viewport);
}
this.cache[viewport] = targets;
return this.cache[viewport];
}
}
on: function(viewport) {
console.log('Should swap elements for', viewport);
},
off: function(viewport) {
console.log('Should restore elements', viewport);
}
};
Pseudocode might not work in particular case, but You get the idea. Whenever You need targets you call swapElements.getTargets(viewport) function.
I'm pretty sure you don't need a higher-order memoize function (although you could trivially apply it when you have written one anyway).
What you need to do is to store the result of jq().swapElements('phone') in a variable, and when the screen enters/exits the phone layout you should call the methods on that variable, instead of creating new instances.

Array value as condition of if else- JS

I've been trying to understand why whenever value of the array I click, it always add the class "foo".
Example: I clicked on London (cities[1], right?) and it added the class foo.
var cities = [
document.getElementById('Paris'),
document.getElementById('London'),
document.getElementById('Berlin')
];
for (var i = 0; i < cities.length; i++) {
cities[i].onclick = test;
function test(){
if(cities[i] === cities[0]) {
el.classList.add("foo");
}
}
}
EDIT: my original answer was incorrect, this updated one is right. addEventListener returns nothing. Instead, you should use some kind of wrapper to add and remove your listeners, again so that you don't waste resources on listeners that you aren't using:
function on (element, eventName, callback) {
element.addEventListener(eventName, callback);
return function unregister () {
element.removeEventListener(callback);
}
}
function test (event) {
if (event.currentTarget===cities[0]) {
event.target.classList.add('foo');
}
}
var listenerRemovers = cities.map(function (city) {
return on(city, 'click', test);
});
Now you can remove any of these listeners by calling the corresponding function in your listenerRemovers array:
listenerRemovers.forEach(function (unRegisterFunc) { unRegisterFunc(); });
ORIGINAL WRONG ANSWER:
For what it's worth, you're probably better off using .map in a case like this, since best practice is to keep a reference to the event listeners so you can cancel them if needed.
function test (event) {
if (event.currentTarget===cities[0]) {
event.target.classList.add('foo');
}
}
var listenerHandlers = cities.map(function (city) {
return city.addEventListener('click', test);
});
This is happening because you are setting the event functions inside a loop. Each function is using the same value of i.
Try to use this instead of trying to cities[i] inside the function.
function test(){
if(this === cities[0]) {
el.classList.add("foo");
}
}
The easiest approach to achieve this functionality is to use jQuery, here is the idea:
In html tags, give those cities a common class, e.g. class="city"
$('.city').click(function(){$('.city').addClass('foo')});
jQuery saves you more time and coding efforts.
The problem is you are trying to assign a function to a DOM attribute. You are not registering a listener but modifying the DOM. If you wish to do it this way, you must assign the onclick as cities[i].onclick = 'test()'
Also, you should move the function test outside of the for loop to look like the following. The problem is the function test is being declared many times, each with a different 'i' value.
for (var i = 0; i < cities.length; i++) {
cities[i].onclick = 'test(this)';
}
function test(el){
if(cities[i] === cities[0]) {
el.classList.add("foo");
}
}

Categories

Resources