How to promote DOM range after inserting a Node? - javascript

I insert a node and when insert another it substitutes the previously inserted one.
I guess I need to put the range after the firstly inserted node, but how?
I define a function:
function EmoticonsMenu(jquery_element){
var e = jquery_element;
var top = e.offset().top;
var left = e.offset().left;
var onIconClick_callback;
this.onIconClick = function(eventObject){
var html = $(eventObject.target).parent().html().replace(/\n\s+|\s+\n/g, '');
onIconClick_callback(html);
};
e.blur(function(){
e.css({visibility:'hidden'});
e.hide();
});
this.attach_to = function(element, callback){
onIconClick_callback = callback;
var newTop = element.offset().top - top - 10 - e.height();
var newLeft = element.offset().left - left + 10;
e.css({top:newTop, left:newLeft});
e.css({visibility:'visible'});
e.focus();
};
};
and there is a place where I bind Emoticon to my nicEditor custom button
nicEditorExampleButton = nicEditorButton.extend({
mouseClick : function(eventObject) {
// get nicEdit selected instance - se
var se = this.ne.selectedInstance;
var paste_icon_html = function(html){
// create a DOM node from the string
//var html = '<img src="/assets/emoticons/ac.gif">admin is here!</img>';
var div = document.createElement('div');
div.innerHTML = html;
var node = div.childNodes[0];
// get selection if any, insert html as a Node
var range = se.getRng();
range.deleteContents();
range.insertNode(node.cloneNode(true));
//range.setStart(node,0);
//range.setEnd(node,0);
};
emoticonsMenu.attach_to($(this.button), paste_icon_html);
}
});
nicEditors.registerPlugin(nicPlugin,nicExampleOptions);

If you don't want to substitute anything, remove the call to deleteContents.
Also you might have the problem that while calling this snippet repeatedly you always refer to the same nodes[0], and it can only exist once - to insert it multiple times, you'd need to clone it:
range.insertNode(nodes[0].cloneNode(true));

Here is code change required. Instead of:
range.insertNode(node.cloneNode(true));
//range.setStart(node,0);
//range.setEnd(node,0);
};
should be
range.insertNode(node);
range.setEndAfter(node); //++
range.setStartAfter(node); //++
};

Related

Why Does That JS Code Not Work For Anything Except The First Class On The Page?

The following JS code turns on/off the in-site search, but there is a .search-button button in 2 places on the page, first one works but second one does not work. If I add more, they also do not work. Can I get this code to run on all classes on the page that contain a .search-button?
var wHeight = window.innerHeight;
var sb = document.querySelector(".search-button");
var closeSB = document.querySelector(".search-close");
var SearchOverlay = document.body;
var searchBar = document.querySelector(".search-bar");
// Show
searchBar.style.top=wHeight/2 +'px';
console.log(wHeight);
window.addEventListener("resize", function() {
console.log(wHeight);
wHeight = window.innerHeight;
searchBar.style.top=wHeight/2 + 'px';
}, true);
document.addEventListener("click", function() {
sb.onclick = function() {
console.log("Opened Search for Element: ");
SearchOverlay.classList.add("show-search");
};
// Hide
closeSB.onclick = function() {
console.log("Closed Search for Element: " + closeSB);
SearchOverlay.classList.remove("show-search");
};
}, true);
It's because you're using document.querySelector() which returns the first element matching your query. For more details check https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector.
To query all elements with same class you need to use document.querySelectorAll() which returns all elements matching as an array. Then, you can use the array forEach() function to iterate over all elements and add event function for all.
var sb = document.querySelectorAll(".search-button");
sb.forEach(el => el.onclick = function(e) {
console.log("Opened Search for Element: ");
SearchOverlay.classList.add("show-search");
});
Update: You can also simplify your

Switch Content back and forth using Jquery

I have an app where when the user clicks on a table, assigned to btnAlternativeService, the content from this table swaps with another one.
The jQuery I have used involves creating two variables for each piece of content in the table, one to act as a JS selector and another to retain the original content. It looks like this:
// Switch between the two services
btnAlternativeService.on('click', function(){
// Set original recommended text variables
var serviceSubTextOriginal = $('.js-second-step .subtext-top').text();
var serviceProductTitleOriginal = $('.js-second-step .product-title').text();
var serviceMileageOriginal = $('.js-miles').text();
var serviceRecommendationOriginal = $('.js-second-step .full-rec').text();
// Set recommended text selector variables
var serviceSubText = $('.js-second-step .subtext-top');
var serviceProductTitle = $('.js-second-step .product-title');
var serviceMileage = $('.js-miles');
var serviceRecommendation = $('.js-second-step .full-rec');
// Set original alternative variables
var alternativeProductTitleOriginal = $('.js-alternative h3').text();
var alterativeMilageOriginal = $('.js-miles-alternative').text();
var alternativeSubTextOriginal = $('.js-alternative .alternative-subtext').text();
// Set alternative selector variables
var alternativeProductTitle = $('.js-alternative h3');
var alterativeMilage = $('.js-miles-alternative');
var alternativeSubText = $('.js-alternative .alternative-subtext');
// Swap everything around
serviceProductTitle.text(alternativeProductTitleOriginal);
serviceMileage.text(alterativeMilageOriginal);
serviceRecommendation.text(alternativeSubTextOriginal);
alternativeSubText.text(serviceRecommendationOriginal);
alternativeProductTitle.text(serviceProductTitleOriginal);
alterativeMilage.text(serviceMileageOriginal);
});
This seems very long winded - is there a better way for me to swap the content around?
You can select the elements by order and create 2 collections and use the indices for setting the text contents:
var $first = $('.js-second-step .subtext-top, ...');
var $second = $('.js-alternative h3, ...');
$first.text(function(index, thisText) {
// select the corresponding element from the second set
var $that = $second.eq( index ), thatText = $that.text();
$that.text( thisText );
return thatText;
});
Use a function:
function switchContent(selector_1, selector_2){
data_1 = $(selector_1).text()
data_2 = $(selector_1).text()
$(selector_1).text(data_2)
$(selector_2).text(data_1)
}
btnAlternativeService.on('click', function(){
switchContent('.js-alternative h3', '.js-second-step .product-title')
switchContent('.js-miles-alternative', '.js-miles')
switchContent('.js-alternative .alternative-subtext', '.js-second-step .full-rec')
});

Javascript "getSelection" only if it's in a certain div

So I'm trying to collect what people are selecting on our site. Currently, it works EVERYWHERE, and I don't want that. I only want it if they are selecting in a certain DIV.
it's basically a simple modification to a script I found.
<script type="text/javascript">
function appendCopyright() {
var theBody = document.getElementsByClassName("sbReview")[0];
var selection;
selection = window.getSelection();
var copyrightLink = '<br /><br /> - Read more at: '+document.location.href+'<br />©2012 <? printf($product. ' & ' .$spOrganization); ?>';
var copytext = selection + copyrightLink;
var extra = document.createElement("div");
extra.style.position="absolute";
extra.style.left="-99999px";
theBody.appendChild(extra);
extra.innerHTML = copytext;
selection.selectAllChildren(extra);
window.setTimeout(function() {
theBody.removeChild(extra);
},0);
}
document.oncopy = appendCopyright;
I tried modifying selection = window.getSelection(); but it just broke it :(
Basically, I want the above code, ONLY to work in a certain div, not the whole body
Probably you shouldn't use document.oncopy, instead try using div.oncopy where div is the div element you are interested in.
var selection = getSelection().toString(); is your solution - getSelection() returns a Selection object and you can get the string just by using .toString() method. More properties and methods of Selection object could be found here: https://developer.mozilla.org/en-US/docs/DOM/Selection
According to the Mozilla JS docs the selection class has a method containsNode. The following should work.
function appendCopyright() {
var theBody = document.getElementsByClassName("sbReview")[0];
var selection;
selection = window.getSelection();
// HERE's THE GOODS
// set aPartlyContained to true if you want to display this
// if any of your node is selected
if(selection.containsNode(aNode, aPartlyContained)){
var copyrightLink = '<br /><br /> - Read more at: '+document.location.href+'<br />©2012 <? printf($product. ' & ' .$spOrganization); ?>';
var copytext = selection + copyrightLink;
var extra = document.createElement("div");
extra.style.position="absolute";
extra.style.left="-99999px";
theBody.appendChild(extra);
extra.innerHTML = copytext;
selection.selectAllChildren(extra);
window.setTimeout(function() {
theBody.removeChild(extra);
},0);
}
}
document.oncopy = appendCopyright;

Manipulating DOM data without affecting the view

<!DOCTYPE html>
<html><body>
<p id="intro">Hello <em id="abcd">intro</em> World!</p>
<script type="text/javascript">
var txt=document.getElementById("intro").innerHTML;
var el = document.createElement("span");
el.innerHTML = txt;
var aa = el.getElementById("abcd").innerHTML;
alert( aa );
</script>
</body></html>
The above is a simple snippet. Actually I have an HTML editor and when the user saves the data I should save only the required content. Here I am getting the content of an element and manipulating it with DOM and pass the details to the server. This way I will not change the page content (user view remains the same) and he/she will continue editing the document.
The above is a simple example but in the real case I have to remove, change and move certain elements. The above code fails el.getElementById("abcd").innerHTML. Appreciate any pointers.
You can create a hidden iframe to manipulate all your changes, thus creating a separate DOM, then simply pull back the results you want.
var iframe;
if (document.createElement && (iframe = document.createElement('iframe'))) {
iframe.name = iframe.id = "externalDocument";
iframe.className = "hidden";
document.body.appendChild(iframe);
var externalDocument;
if (iframe.contentDocument) {
externalDocument = iframe.contentDocument;
} else if (iframe.contentWindow) {
externalDocument = iframe.contentWindow.document;
}
else if (window.frames[iframe.name]) {
externalDocument = window.frames[iframe.name].document;
}
if (externalDocument) {
externalDocument.open();
externalDocument.write('<html><body><\/body><\/html>');
externalDocument.close();
/* Run your manipulations here */
var txt = document.getElementById("intro").innerHTML;
var el = document.createElement("span");
el.innerHTML = txt;
/* Attach your objects to the externalDocument */
externalDocument.body.appendChild(el);
/* Reference the externalDocument to manipulate */
var aa = externalDocument.getElementById("abcd").innerHTML;
alert(aa);
}
/* Completed manipulation - Remove iFrame */
document.removeChild(iframe);
}
I have it working here:
http://jsfiddle.net/ucpvP/
Try using jQuery like given below.
function SaveData() //Your add function
{
var txt=$("#intro").html();
$(document).append("<span id='abcd'>" + txt+ "</span>");
var aa = $("#abcd").hmtl();
alert(aa);
}
You can use a DOM Element that is never appended to the DOM.
I use this 'cleanup' function:
function cleanup(str){
var tester = document.createElement('div'),
invalid, result;
tester.innerHTML = str;
//elements I don't allow
invalid = tester.querySelectorAll('script,object,iframe,style,hr,canvas');
// the cleanup (remove unwanted elements)
for (var i=0;i<invalid.length;(i+=1)){
invalid[i].parentNode.removeChild(invalid[i]);
}
result = tester.innerHTML;
tester = invalid = null;
//diacritics to html-encoded
return result.replace(/[\u0080-\u024F]/g,
function(a) {return '&#'+a.charCodeAt(0)+';';}
)
.replace(/%/g,'%25');
}
//usage:
cleanup(document.getElementById("intro").innerHTML);
You can extend the function with your own code to remove, change and move certain elements.

obj is null, javascript

function init()
{
alert("init()");
/**
* Adds an event listener to onclick event on the start button.
*/
xbEvent.addEventListener(document.getElementById("viewInvitation"), "click", function()
{
new Ajax().sendRequest("31260xml/invitations.xml", null, new PageMaster());
xbEvent.addEventListener(document.getElementById("declinebutton"), "click", function ()
{
declineInvitation();
});
});
ok so what I have here is a event listerner function, the case is when viewInvitation is clicked , the program will fetch my xml file and run page master function where I created my decline button with id="declinebutton", however this does not work, the error message that i get is obj=null or the program could not find id = declinebutton, why is it so? I have created it when I called page master using dom. any help will be appreciated.
function PageMaster()
{
this.contentDiv = document.getElementById("content");
}
/**
* Builds the main part of the web page based on the given XML document object
*
* #param {Object} xmlDoc the given XML document object
*/
var subjectList;
var i;
PageMaster.prototype.doIt = function(xmlDoc)
{
alert("PageMaster()");
alert("Clear page...");
this.contentDiv.innerHTML = "";
if (null != xmlDoc)
{
alert("Build page...");
//create div Post
var divPost = document.createElement("div");
divPost.className = "post";
//create h1 element
var h1Element = document.createElement("h1");
var headingText = document.createTextNode("Invitations");
h1Element.appendChild(headingText);
//insert h1 element into div post
divPost.appendChild(h1Element);
subjectList = xmlDoc.getElementsByTagName("subject");
var groupList = xmlDoc.getElementsByTagName("group");
for (i = 0; i < subjectList.length; i++) //for each subject
{
var divEntry = document.createElement("div");
divEntry.className = "entry";
var subjectNum = subjectList[i].attributes[0].nodeValue;
var subjectName = subjectList[i].attributes[1].nodeValue;
var groupId = groupList[i].attributes[0].nodeValue;
var groupName = groupList[i].attributes[1].nodeValue;
var ownerId = groupList[i].attributes[2].nodeValue;
//set up the invitation table attributes
var table=document.createElement("table");
table.width = 411;
table.border = 3;
table.borderColor = "#990000"
var input=document.createElement("p");
var inputText=document.createTextNode("You are invited to join " + groupName + "(groupId : " + groupId +")");
input.className="style11";
var blank=document.createElement("nbps");
input.appendChild(inputText);
var acceptButton=document.createElement("input");
acceptButton.type="button";
acceptButton.id="acceptbutton";
acceptButton.value="accept";
var declineButton=document.createElement("input");
declineButton.type="button";
declineButton.id="declinebutton";
declineButton.value="decline";
table.appendChild(input);
table.appendChild(acceptButton);
table.appendChild(declineButton);
divEntry.appendChild(table);
var blankSpace = document.createElement("p");
divEntry.appendChild(blankSpace);
divPost.appendChild(divEntry);
}
//insert div post into div content
this.contentDiv.appendChild(divPost);
}
};
/**function getValueOf()
{
return i;
}**/
function declineInvitation()
{
alert("decline");
}
function acceptInvitation()
{
alert("hello");
/**var pos=getValueOf();
alert(subjectList[pos].attributes[0].nodeValue);**/
}
That's my page master function, and I definitely have created the button. but it does not work.
Try calling your function like this:
window.onload=init;
The javascript runs as the page loads. At that point, the element does not yet exist in the DOM tree. You'll need to delay the script until the page has loaded.
The example you gave doesn't create the "Decline" button, as your question suggests it should. If it should, you might want to look at that.
Of course, if the button already exists, please disregard this answer.
You have a listener inside a listener. Is that right?
What about this?:
function init(){
alert("init()");
/** * Adds an event listener to onclick event on the start button. */
xbEvent.addEventListener(document.getElementById("viewInvitation"), "click", function()
{
new Ajax().sendRequest("31260xml/invitations.xml", null, new PageMaster());
}
xbEvent.addEventListener(document.getElementById("declinebutton"), "click", function ()
{
declineInvitation();
});
As far as I understand, you create button with id="declinebutton" for each entry from xml, is that right?
If yes, I'd suggest you to generate different id's for each button (for example, append line index to 'declinebutton', so you have buttons 'declinebutton0', 'declinebutton1' an so on), and assign event listener to buttons separately in the loop.

Categories