Systematic HTML tags validation - html

Is there an offline tool that can verify whether certain tags possess particular attributes?
I'm looking for something that can verify that all:
form tags have a name attribute.
div, and span tags have at least an id or class attribute.
Does tidy have an obscure option where you could specify such things?

simply and quickly you can do your validations like this if jQuery is available on your page.
$('form').each(function(){
if ($(this).attr('name') === undefined) {
alert('There is at least one form with no name.');
}
});
$('div').each(function(){
if ($(this).attr('id') === undefined) {
alert('There is at least one div with no id.');
}
});

also this is a non-jQuery solution example
var arr = document.getElementsByTagName('div');
var flag = false;
for (var i = 0, len = arr.length; i < len; i++) {
if (arr[i].getAttribute('id') == null) {
flag = true;
break;
}
}
if (flag) {
alert('There is at least one div with no name.');
}

Related

Can I use duplicate IDs on multiple <template> fragments?

Why does HTML5 validation fail when having duplicate element IDs but on different <template>. I'm planning to use only one template at a time so the actual DOM ID won't be duplicated.
something like:
<template id="companyAccount">
<li><label>Company: <input type="text" id="account_name"></label></li>
<li><label>Street: <input id="account_street" ...
...
</template>
<template id="residentialAccount">
<li><label>Name: <input type="text" id="account_name"></label></li>
<li><label>Street: <input id="account_street" ...
...
</template>
<script>
...
let template = $(isResidential ? '#residentialAccount' : '#companyAccount').get(0).content;
$('#account_info').empty().append(template.cloneNode(true));
</script>
As TJBlackman mentioned each value for id attributes must be unique. However when working with code it's up to you to determine if the code being imported contains a duplicate id. Additionally I've pretty much stopped using validators for the most part as they aren't well maintained (and the W3C's CSS validator is atrocious) and the consoles, proper error handling and using the XML parser for HTML5 will tell you pretty much everything you need to know.
You have a couple of options. You can use data-account="residential" (don't make the mistake of using camelCasing as that will eventually put you in direct conflict with the standards bodies) and detect the attribute via document.querySelectorAll:
function $(o)
{
var a = true;
try {document.querySelectorAll(o);}
catch(err) {a = false; console.log('Error: "'+o+'" is not a valid CSS selector.'); sound.notice();}
return (a && document.querySelectorAll && document.querySelectorAll(o)) ? document.querySelectorAll(o) : false;
}
Usage: $('[data-account="residential"]')[0].length and $('[data-account="residential"]')[0].value.
Alternatively you can do what I do with my platform which has things tightly integrated though this is the function I use that you might decide to slim down for your specific purposes. You can use document.createTreeWalker and literally go through every single element to scan for walker.currentNode.hasAttribute('id') to test against document.getElementById. This code is used before importing XML in to the DOM on my platform:
/******** part of larger ajax() function ********/
if (xhr.readyState == 4 && xhr.status != 204)
{}
//This code occurs within the above condition.
var r = jax_id_duplication_prevention(xhr.responseXML,param_id_container_pos,id_container);
if (r)
{
if (param_id_container_pos=='after') {id_container.parentNode.insertBefore(xml.getElementsByTagName('*')[0],id_container.nextSibling);}
else if (param_id_container_pos=='before') {id_container.parentNode.insertBefore(document.importNode(xml.getElementsByTagName('*')[0],true),id_container);}
else if (param_id_container_pos=='first')
{
if (id_container.childNodes.length > 0) {id_container.insertBefore(document.importNode(xml.getElementsByTagName('*')[0],true),id_container.firstChild);}
else {id_container.appendChild(document.importNode(xml.getElementsByTagName('*')[0],true));}
}
else if (param_id_container_pos=='inside') {id_container.appendChild(document.importNode(xml.getElementsByTagName('*')[0],true));}
else if (param_id_container_pos=='replace') {id_container.parentNode.replaceChild(document.importNode(xml.getElementsByTagName('*')[0],true),id_container);}
else if (param_id_container_pos=='fragment')
{
if (option.fragment) {delete option.fragment;}
option.fragment = document.importNode(new DOMParser().parseFromString(xhr.responseText,'application/xml').childNodes[0],true);
if (id_container && typeof id_container == 'function') {id_container();}
}
else {alert('Error: unknown position to import data to: '+id_container_pos);}
}
/******** part of larger ajax() function ********/
function ajax_id_duplication_prevention(xml,param_id_container_pos,id_container)
{
var re = true;
if (typeof id_container == 'string' && id_container.length > 0 && id_(id_container)) {id_container = id_(id_container);}
if (typeof option.id_fade == 'string' && option.id_fade.length > 0 && id_(option.id_fade)) {element_del(option.id_fade); option.id_fade = '';}
if (typeof xml.firstChild.hasAttribute == 'function')
{
if (xml.firstChild.hasAttribute('id') && xml.firstChild.getAttribute('id').length > 0 && id_(xml.firstChild.getAttribute('id')) && id_(xml.firstChild.id).parentNode.id=='liquid') {change(xml.firstChild.id,'fade');}
if (xml.firstChild.hasAttribute('id') && xml.firstChild.getAttribute('id').length > 0 && id_(xml.firstChild.id) && !id_(xml.firstChild.id).parentNode.id=='liquid') {re = false;}
else if (typeof document.createTreeWalker=='function')
{
var idz = [];
try
{
var walker = document.createTreeWalker(xml,NodeFilter.SHOW_ELEMENT,null,false);
while (walker.nextNode())
{
if (walker.currentNode.hasAttribute('id') && walker.currentNode.getAttribute('id').length > 0)
{
if (walker.currentNode.id==undefined && walker.currentNode.nodeName.toLowerCase()=='parsererror') {console.log('Error: a parser error was detected.');}
else if (walker.currentNode.id==undefined) {alert('walker.currentNode.nodeName = '+walker.currentNode.nodeName+'\n\n'+document.serializeToString(xml));}
else
{
for (var i = 0; i<id_('liquid').childNodes.length; i++)
{
if (id_('liquid').childNodes[i].nodeType==1 && id_(walker.currentNode.id) && is_node_parent(walker.currentNode.id,id_('liquid').childNodes[i]) && (param_id_container_pos!='replace' || walker.currentNode.id!=id_container.id))
{
if (param_id_container_pos != 'replace' && id_container != walker.currentNode.id) {element_del(id_('liquid').childNodes[i]);}//If changing operator test: ajax('get','?ajax=1&web3_url=/'+url_section()+'/'+url_page(),'replace',push_current_id());
}
}
var n = id_(walker.currentNode.id);
if (in_array(walker.currentNode.id,idz))
{
var fd = new FormData();
fd.append('ajax','error_xml');
fd.append('post_error','Duplicate id <code>'+walker.currentNode.id+'</code>.');
fd.append('post_url',url_window().split(url_base())[1].split('?')[0]);
fd.append('post_xml',new XMLSerializer().serializeToString(xml));
if (fd) {ajax('post',path+'/themes/',fd);}
modal.alert('Error: can not import XML, the id \''+walker.currentNode.id+'\' was detected twice in the layer being imported. Duplicated ID\'s break expected functionality and are illegal. While the XML content was not imported it is still possible that the related request was successful. It is possible to override this problem by simply doing a full request (press the Go button in your browser\'s graphic user interface) however if the id is referenced programmatically the website may exhibit unusual behavior.');
break;
setTimeout(function()
{
history.back();
push_reload();
console.log('Developer: duplicate id '+walker.currentNode.id+' was encounterted.');
if (status >= 9) {modal.xml('Duplicate ID Error', '%3Cp%3EError%3A%20the%20id%20%3Ccode%3E'+walker.currentNode.id+'%3C%2Fcode%3E%20occurred%20twice%20and%20therefore%20the%20page%20can%20not%20be%20viewed.%3C%2Fp%3E%3Cp%3EFor%20debugging%20and%20fixing%20purposes%20you%20should%20consider%20opening%20the%20URL%20in%20a%20new%20tab.%3C%2Fp%3E%3Cdiv%20class%3D%22center%20margin%22%3E%3Cinput%20onclick%3D%22modal.close()%3B%22%20tabindex%3D%223%22%20type%3D%22button%22%20value%3D%22Close%22%20%2F%3E%3C%2Fdiv%3E');}
else {modal.xml('Duplicate ID Error', '%3Cp%3EError%3A%20the%20id%20%3Ccode%3E'+walker.currentNode.id+'%3C%2Fcode%3E%20occurred%20twice%20and%20therefore%20the%20page%20can%20not%20be%20viewed.%3C%2Fp%3E%3Cdiv%20class%3D%22center%20margin%22%3E%3Cinput%20onclick%3D%22modal.close()%3B%22%20tabindex%3D%223%22%20type%3D%22button%22%20value%3D%22Close%22%20%2F%3E%3C%2Fdiv%3E');}
re = false;
},4000);
}
else {idz.push(walker.currentNode.id);}
}
if (id_(walker.currentNode.id) && (param_id_container_pos!='replace' && walker.currentNode.id!=id_container.id && !is_node_parent(walker.currentNode.id,id_container)))
{//ajax replace (carousel loader) complications if changed.
re = false;
modal.alert('Error: unable to import page, the id \''+walker.currentNode.id+'\' already exists in the DOM.');
break;
}
}
}
}
catch (err) {}//IE9
}
}
return re;
}
Regardless of how you approach addressing the issue it is not one addressed by the standards bodies and must be explicitly handled by developers. Failure to handle duplicate id attribute/values will result in the wrong element being chosen at some point which may quietly accrue compromised/malformed data over time that no one might notice for years and thus could easily hinder if not outright destroy any business relations effected by it. Good luck!

HTML - Hiding objects based on class

I have a function that hides a div with the corresponding ID based on a radio button change, however, I would like to hide multiple items at once and as ID is unique I am not able to just hide them all. How would I set up a class that I can hide and how would I adjust this code below to make that work?
Any help greatly appreciated
function onChangePackage() {
const nodes = document.getElementsByClassName("baseClass");
var selectedValue;
// Get selected radio
for (var i = 0, length = nodes.length; i < length; i++) {
if (nodes[i].checked) {
selectedValue = nodes[i].value;
break;
}
}
// Showing all nodes first
const nodePostFix = ['A','B','C'];
nodePostFix.forEach( node => {
const currentElement = elementsToHide.item(i);
if (currentElement.hasClass("hidden" + selectedValue)) {
currentElement.style.display = "none";
} else {
currentElement.style.display = "block";
}
});
};
You can use data attributes for this purpose together with the attribute selectors. So you need just to add the data-hidden-for attributes to the required nodes and access them using document.querySelector() or document.querySelectorAll()
First give all the elements a base class name baseClass. You could just give them a class name like hidden and then in your code you could do something like below:
const elementsToHide = document.getElementsByClassName("baseClass");
for (var i = 0; i < elementsToHide.length; i++) {
const currentElement = elementsToHide.item(i);
if (currentElement.hasClass("hidden")) {
currentElement.style.display = "none";
} else {
currentElement.style.display = "block";
}
}
And on the click event of the radio button you could add this class I mentioned above to whichever ones you want to hide:
element.classList.add("hidden");
or
element.classList.remove("hidden");

Mail merge: can't append images from template

I'm trying to create a mail merge function to create a document based on a simple template.
I tried to use the following function to copy the template elements but I'm having problems with the (inline) images, they appear always as PARAGRAPH and not INLINE_IMAGE and the following icon appears instead of the images:
Here's the code:
function appendToDoc(src, dst) {
// iterate accross the elements in the source adding to the destination
for (var i = 0; i < src.getNumChildren(); i++) {
appendElementToDoc(dst, src.getChild(i));
}
}
function appendElementToDoc(doc, object)
{
var element = object.copy();
var type = object.getType();
if (type == DocumentApp.ElementType.PARAGRAPH) {
doc.appendParagraph(element);
} else if (type == DocumentApp.ElementType.TABLE) {
doc.appendTable(element);
} else if (type== DocumentApp.ElementType.INLINE_IMAGE) { // This is never called :(
var blob = element.asInlineImage().getBlob();
doc.appendImage(blob);
}
}
Any ideas on how to solve this? Thanks in advance!
As per my knowledge, inline images are included in a paragraph so we have to check for Image type in the Paragraph.
So the code for checking that would be this way:
if (type == DocumentApp.ElementType.PARAGRAPH) {
if (element.asParagraph().getNumChildren() != 0 && element.asParagraph().getChild(0).getType() == DocumentApp.ElementType.INLINE_IMAGE) {
var blob = element.asParagraph().getChild(0).asInlineImage().getBlob();
doc.appendImage(blob);
}
else doc.appendParagraph(element.asParagraph());
}
Hope that helps!

HTML 5 make Checkbox required

I have multiple checkbox on my form. I need to make user to select atleast one checkbox or else the form will not submit. I tried required attribute but it checks if user has checked all check boxes.
How to do this?
One solution is to add required attributes to all the checkboxes, and when at least one of these checkboxes is checked, remove the required attributes for all the checkboxes using jQuery.
var requiredCheckboxes = $(':checkbox[required]');
requiredCheckboxes.change(function(){
if(requiredCheckboxes.is(':checked')) {
requiredCheckboxes.removeAttr('required');
}
else {
requiredCheckboxes.attr('required', 'required');
}
});
DEMO
You can use Javascript that just loops through your html checkboxes and sets the Variable issomethingchecked to TRUE if at least one of them is cheched .
demo http://jsfiddle.net/updpxrfj/2/
var checkboxElements = document.getElementsByTagName("input");
var issomethingchecked = false;
for(var i = 0; i < checkboxElements.length; i++) {
if(checkboxElements[i].type.toLowerCase() == 'checkbox') {
if (checkboxElements[i].checked == true) {
issomethingchecked = true;
}
}
if (issomethingchecked === true) {
// Continue with submiting the form
console.log(issomethingchecked);
}
}

GAS is it possible to replace getActiveDocument().getSelection() at once?

My User has the following selection in his Gdoc.
Now from the sidebar he wants to to replace the selection he made on the document. The GAS question is if it is possible to do that at once, something like:
var selection = DocumentApp.getActiveDocument().getSelection()
selection.replace("newtext")
Or do I have to loop through selection.getRangeElements() in order to delete them (or replace them) and than in someway place the new text in that position?
Not, that's not possible (well, if it is, it's not documented).
You have to loop through the selected elements, mainly because the selection may take part of paragraphs, forcing you to manage that. i.e. deleting just the selected part. And for completed selected elements, you can just remove them entirely (like images).
Here's an implementation on how to do this (part of the Kaylan's Translate script modified by me to properly replace images and partially selected paragraphs.
function replaceSelection(newText) {
var selection = DocumentApp.getActiveDocument().getSelection();
if (selection) {
var elements = selection.getRangeElements();
var replace = true;
for (var i = 0; i < elements.length; i++) {
if (elements[i].isPartial()) {
var element = elements[i].getElement().asText();
var startIndex = elements[i].getStartOffset();
var endIndex = elements[i].getEndOffsetInclusive();
var text = element.getText().substring(startIndex, endIndex + 1);
element.deleteText(startIndex, endIndex);
if( replace ) {
element.insertText(startIndex, newText);
replace = false;
}
} else {
var element = elements[i].getElement();
if( replace && element.editAsText ) {
element.clear().asText().setText(newText);
replace = false;
} else {
if( replace && i === elements.length -1 ) {
var parent = element.getParent();
parent[parent.insertText ? 'insertText' : 'insertParagraph'](parent.getChildIndex(element), newText);
replace = false; //not really necessary since it's the last one
}
element.removeFromParent();
}
}
}
} else
throw "Hey, select something so I can replace!";
}