Serialising an HTML element that has event handlers - javascript

I am currently making a website for a class that I am in. It is supposed to be a store, made in Pure HTML, CSS, and JavaScript (no libraries including jQuery). I am currently attempting at making a functional cart right now. My idea is to take the element that has the information about that item (image and name, but price I kept to be constant throughout to make it simpler). From here, I thought about serialising that element and saving it to localStorage so that I can load it from the cart page. When I try JSON.serialize(product) where product is the element of the item, it only ends up with "{}" and nothing else. XMLS.serialiseToString() only gives the element without the event handler. How am I supposed to make this work?
I also tried loading the event handler in the cart page too, but I cannot figure out a way to get the HTML element from a string. Could someone tell me how to make this work? I looked at a few other questions on here but it does not make much sense to me.
Edit: I got the serialisation and deserialisation working using XMLSerializer. However, I want to change the click event handler after deserialising the element. How am I supposed to make this work?

XMLSerializer should work.
You will need to provide more code for what you're doing with local storage and your event handler but based on the info you provided, I included some code with comments below.
//this is a test click handler function to see if it still works after being added to page
function testClickHandler() {
console.log('I still work after being added to document again.')
}
//initialize XML serializer
var serializer = new XMLSerializer();
//get html element from document
var html = document.querySelector('#serializemecapt');
//convert element to string
var htmlString = serializer.serializeToString(html);
//once element is converted to string you can save in local storage
//after you retrieve the element again from local storage, use the code below to insert it into your document again
//insert element back onto page
document.body.insertAdjacentHTML('afterbegin', htmlString);
<div id="serializemecapt" onclick="testClickHandler()">Click me to see if the click event still works after I'm pushed back into the document.<br/><br/></div>
Edit: If you want to change the click handler after serialization, you can do something like this (run the snippet below and see comments):
//this function will be removed before serialization
function originalFunction() {
console.log('I am the original function before serialization.')
}
//this function will be added after serialization
function newFunction() {
console.log('I am the new function after serialization.')
}
//get html element from document
var html = document.querySelector('#serializeme');
//apply originalFunction to div using addEventListener
html.addEventListener('click', originalFunction)
//new function to demonstrate serialization
function serializeHtml() {
//initialize XML serializer
var serializer = new XMLSerializer();
//remove event listener for originalFunction before serialization
html.removeEventListener('click', originalFunction)
//serialize html
var htmlString = serializer.serializeToString(html);
//insert element back onto page
document.body.insertAdjacentHTML('afterbegin', htmlString);
//change innerText on element for example purposes
html.innerText = 'I am the same div as above after serialization. I have new text and a new function. Click me now and I will run the new function.'
//add newFunction to div using addEventListener
html.addEventListener('click', newFunction)
}
//get button element from document
var serialButton = document.querySelector('#serialButton');
//apply serializeHtml function to button using addEventListener
serialButton.addEventListener('click', serializeHtml)
<div id="serializeme"><b>Click me</b> before serializing to run the original function.<br/><br/></div>
<p><b>Click button below to serialize the div above and apply a new function to the div after serialization.</b></p>
<button id="serialButton">Serialize</button>

Related

Swapping BODY content while keeping state

Dynamically swapping BODY content using jQuery html function works as expected with 'static' content.
But if forms are being used, current state of inputs is lost.
The jQuery detach function, which should keep page state, seems to be blanking out the whole page.
The following code is the initial idea using jQuery html but of course the text input value will always empty.
function swap1( ) {
$("body").html('<button onclick="swap2();">SWAP2</button><input type="text" placeholder="swap2"/>');
}
function swap2( ) {
$("body").html('<button onclick="swap1();">SWAP1</button><input type="text" placeholder="swap1"/>');
}
With not knowing what form inputs there are, how would one swap in and out these forms in the BODY and keep track of the form states?
Demo of two text inputs which should keep state when they come back into the BODY:
https://jsfiddle.net/g7hksfne/3/
Edit: missunderstood use case. This covers saving the state while manipulating the DOM, instead of switching between different states.
Instead of replacing the <body>'s content by serializing and parsing HTML, learn how to use the DOM. Only replace the parts of the DOM you actually change, so the rest of it can keep its state (which is stored in the DOM).
In your case: you might want to use something like this:
function swap1( ) {
document.querySelector("button").onclick = swap2;
document.querySelector("button").textContent = "SWAP2";
document.querySelector("input").placeholder = "swap2";
}
function swap2( ) {
document.querySelector("button").onclick = swap1;
document.querySelector("button").textContent = "SWAP1";
document.querySelector("input").placeholder = "swap1";
}
<button onclick="swap1();">SWAP1</button><input type="text" placeholder="swap1"/>
(This is not optimized and should only serve as an example.)
Put the content you want to save in a node below <body>, like a simple ยด` if you don't already have a container. When you want to save and replace the container, use something like:
var saved_container = document.body.removeChild(document.querySelector("#app_container");
// or document.getElementById/getElementsByClassName, depends on container
The element is now detached and you can add your secondary to document.body. When you want to get back, save the secondary content (without overwriting the first container of course), then reattach the primary content it with:
document.body.appendChild(savedContainer);

TinyMCE retrieving elements using DomQuery

I'm dealing with TinyMCE to create a WYSIWYG editor, but there is a problem and now I'm stuck on it.
I need to create a system where users are allowed to create specific documents, each devided into sections i.e. a wrapper. Inside every section there are textual and block elements (p, table, img and so on).
Now, the problem is: when a new section needs to be created I'm using the following code
function insertRawSection () {
// Close the current section and open the next one
Editor.execCommand('mceInsertRawHtml',false,`</section><section><h1>${ZERO_SPACE}</h1>`)
}
This code works, but the real problem comes out when I need to move the cursor at the start of the new h1 element.
I can't retrieve the new heading because if I look for it with DomQuery it doesn't appear.
The code I use to lookup the h1 element is the following
function insertRawSection () {
// Close the current section and open the next one
Editor.execCommand('mceInsertRawHtml',false,`</section><section><h1 data-pointer>${ZERO_SPACE}</h1>`)
// Lookup the last inserted heading
console.log($('[data-pointer]'))
}
Note: The variable $ is not JQuery but is TinyMCE.DomQuery (everything is correctly setted up)
The log print only the previous existent headings, but not the last one. Probably there is somethings like a refresh to execute, but what i have to do in order to "communicate" between this command and the DomQuery APIs?
Instead of using mceInsertRawHtml same can be achieved using dom methods of tinymce.
var ed = tinymce.activeEditor;
var currentNode = ed.selection.getNode();
var newEle = ed.dom.create('section', {}, '<h1></h1>');
ed.dom.insertAfter(newEle, currentNode);
ed.selection.select(newEle.firstChild.firstChild);
ed.selection.collapse(false);
ed.focus();
Once the element is created same can be selected using dom methods to place the cursor at the begining/ending of the element.

Script to pulled element class inner html for Google Tag Manager

I am working on a script that is meant to pull the inner html of a paragraph that is only shown onblur. I have spent hours on this and have not been able to successfully surface this inner html in our data layer.
Firstly, the data layer is custom (i.e. not the default data layer for GTM) - not sure if this makes a difference.
Secondly, the JavaScript I am using to attempt to achieve this. The onblur listener shows a parahraph (with no Element ID, only Element Class) when a user moves on from the form (which is the first parent element which has an ID, it is several parent elements up from the class). The paragraph passes a validation message (e.g. enter a valid email). I am attempting to pass the error message to the data layer:
<script>
object.onblur=function(){
var myForm = document.getElementById('instantSearchForm');
if(myForm) {
var myPara = myForm.getElementsByTagName('error-message');
if(myPara.length) {
var paraValue = myPara[0].innerHTML;
dataLayer.push({inputError: paraValue });
}
}}
</script>
As per your comment I see that you are using the class name for selecting your elements. Then you should try
myForm.getElementsByClassName('error-message');
instead of
myForm.getElementsByTagName('error-message');

Saving ID for specific onclick element

I'm trying to create a JavaScript where you write a message and the time and message appears on the website. The function doing this is "renderMessage". However, it includes an image you can click to delete that message and then I want to write all the remaining ones again. Problem is that I don't know how to save some sort of ID so I know which image was clicked so I delete the correct position in the array of messages.
The code for renderMessage is:
function renderMessage(theMessage, theMessages){
var text = document.createTextNode(theMessage.getText());
var time = document.createTextNode(theMessage.getDate());
var div = document.getElementById("writeMessages");
div.appendChild(text);
div.appendChild(time);
var image = document.createElement('img');
image.src = 'img/deletePic.png';
div.appendChild(image);
div.appendChild(document.createElement('br'));
image.onclick = function(e){
theMessages.splice(); // This is where I don't know how to remove the correct one
removeAll(theMessages); // This removes all html code in the div and writes
// the array again (hopefully this time with the correct
// element removed from it)
};
}
Firstly, thumbs up for using plain js.
I would say you enclose the message, time and the image into another element. My be a ul li block. And, when you render the messages in DOM, you set the message id as id attribute of the li so it will be something like this
<ul>
<li>Message 1 - 10:21 PM <img src="remove jpg"/></li>
<li>Message 2 - 10:22 PM <img src="remove jpg"/></li>
</ul>
and your js code can be,
image.onclick = function () {
var message_id = this.parentNode.id;
// here you got the message id.
// splice your message array and render
}
Why are you re-rendering all the messages? You could simply
// splice your message array and render
var li = this.parentNode;
li.parentNode.removeChild(li);
}
you can save the id of the message inside an attribute of the element. eg <div class="your_message_container data-id="14">...</div>
Using jquery for example you can read that attribute with $(your_selector).attr("data-id"); and write it with $(your_selector).attr("data-id", "new_value");
As an alternative, also have a look at http://api.jquery.com/jquery.data/
Edit:
i made you a fiddle with pure js: http://jsfiddle.net/A64zh/2/ Note that the id of the message element must equal the data-message-id attribute of your delete image.
the advantage of using an element attribute for storing the id is that your javascript does not depend on the html structure like it does if you are using something like this.parentNode.parentNode.removeChild.... which would need to be changed if you would add more html layers in between the two

Counting classes on another page and displaying them

To save me a lot of work editing a number in when adding a document to a site I decided to use javascript to count the number of elements with a class doc .
I am two main problems:
There is trouble displaying the variable. I initially thought this was because I hadn't added function, however when I tried adding this the variable was still not displayed.
The elements with the class I want to count are on another page and I have no idea how to link to it. For this I have tried var x = $('URL: /*pageURL*/ .doc').length; which hasn't worked.
Essentially I want the total elements with said class name and this to be displayed in a span element.
Currently I have something similar to what's displayed below:
<script>
var Items = $('.doc').length;
document.getElementById("display").innerHTML=Items;
</script>
<span id="display"></span>
Found an example of something similar here where the total numbers of articles are displayed.
Edit:
#ian
This code will be added to the homepage, domain.net/home.html. I want to link to the page containing this documents, domain.net/documents.html. I've seen this done somewhere before and if I remember correctly they used url:domainname.com/count somewhere in their code. Hope this helps.
Here is a jQuery call to retrieve the url "./" (this page) and parse the resulting data for all elements with class "lsep" "$('.lsep', data)". You should get back a number greater than 5 or so if you run this from within your debug console of your browser.
$.get("./", function(data, textStatus, jqXHR)
{
console.log("Instances of class: " + $('.lsep', data).length)
});
One important thing to remember is that you will run into issues if the URL your are trying to call is not in the same origin.
Here's an updated snippet of code to do what you're describing:
$(document).ready(
function ()
{
//var url = "/document.html" //this is what you'd have for url
//var container = $("#display"); //this is what you'd have for container
//var className = '.data'; //this is what you'd have for className
var url = "./"; //the document you want to parse
var container = $("#question-header"); //the container to update
var className = '.lsep'; //the class to search for
$.get(url, function (data, textStatus, jqXHR) {
$(container).html($(className, data).length);
});
}
);
If you run the above code from your browser's debug console it will replace the question header text of "Counting classes on another page and displaying them" with the count of instances the class name ".lsep" is used.
First, you have to wait until the document is ready before manipulating DOM elements, unless your code is placed after the definition of the elements you manipulate, wich is not the case in your example. You can pass a function to the $ and it will run it only when the document is ready.
$(function () {
//html() allows to set the innerHTML property of an element
$('#display').html($('.doc').length);
});
Now, if your elements belongs to another document, that obviously won't work. However, if you have used window.open to open another window wich holds the document that contains the .doc elements, you could put the above script in that page, and rely on window.opener to reference the span in the parent's window.
$('#display', opener.document.body).html($('.doc').length);
Another alternative would be to use ajax to access the content of the other page. Here, data will contain the HTML of the your_other_page.html document, wich you can then manipulate like a DOM structure using jQuery.
$.get('your_other_page.html', function(data) {
$('#display').html($('.doc', data).length);
});

Categories

Resources