jQuery: How to remove this code redundancy? - javascript

clone.find('[id]').each(function() {
id = $(this).attr('id');
ind = id.search(/\d+$/);
$(this).attr('id', id.substr(0,ind)+id_counter);
});
clone.find('[for]').each(function() {
id = $(this).attr('for');
ind = id.search(/\d+$/);
$(this).attr('for', id.substr(0,ind)+id_counter);
});
I know I can find elements that have either the id attribute or the for attribute, but then how do I know which one I need to set?

If all you're trying to do is reduce code, you could...
function doTheseThings(element) {
clone.find(element).each(function() {
var id = $(this).attr('id');
var ind = id.search(/\d+$/);
$(this).attr('id', id.substr(0,ind)+id_counter);
});
}
doTheseThings('[id]');
doTheseThings('[for]');
EVEN BETTER:
I decided to meld my first answer with some of #SHiNKiROU's ideas and add some more Jquery-ish syntax:
var items = ['[id]', '[for]'];
$.each(items, function (index, element) {
clone.find(element).each(function() {
var id = $(this).attr('id');
var ind = id.search(/\d+$/);
$(this).attr('id', id.substr(0,ind)+id_counter);
});
});

var list = ['id', 'for']
for (var i in list) {
var v = list[i];
clone.find('[' + v + ']').each(function() {
id = $(this).attr(v);
ind = id.search(/\d+$/);
$(this).attr(v, id.substr(0,ind)+id_counter);
});
}
delete list;

Well, the redundency is in that the same code applies to the different attributes, so you factor those out:
$.each(['id', 'for'], function (ignr, attr) {
clone.find('['+attr+']').each(function() {
val = $(this).attr(attr);
ind = val.search(/\d+$/);
$(this).attr(attr, val.substr(0,ind)+id_counter);
});
});
Then, if you want, you could remove a local variable to shorten it, but I'm not sure this is an improvement:
$.each(['id', 'for'], function (ignr, attr) {
clone.find('['+attr+']').each(function() {
val = $(this).attr(attr);
$(this).attr(attr, val.substr(0,val.search(/\d+$/))+id_counter);
});
});

Related

Click Delete link for each duplicate href?

In the following code, I have some divs with <a> tags, each containing a href of /User.aspx?ID=[some ID]. I wish to click the Delete <a> tag under the parent of the parent of the parent of any divs with duplicate ID's in the href.
Here is my code:
var z = 0;
var info = {};
$("a:contains('Delete')").each(function() {
z++;
var x = $(this).parent().parent().parent().find("span.UserLink a").attr("href");
var id = x.replace("/User.aspx?ID=", "");
info[z] = id;
console.log(info[z]);
});
var uniqueIds = {};
$.each(info, function(i, el){
if($.inArray(el, uniqueIds) === -1) { uniqueIds.push(el) }
else { $("html").find("span.UserLink a[href='/User.aspx?ID='"+info[i]+"']").parent().parent().parent().find("a:contains('Delete')").click() }
});
Use arrays, not objects
Maybe just a typo, I think you wanted to use arrays for info and uniqueIds
var info = [];
var uniqueIds = [];
jQuery.each already provides an index
You don't need z
$("a:contains('Delete')").each(function(index) {
var x = $(this).parent().parent().parent().find("span.UserLink a").attr("href");
var id = x.replace("/User.aspx?ID=", "");
info[index] = id;
console.log(info[z]);
});
Use meaningful names
x and info aren't very good names, you could try (for example) userLinkHref and foundIds
You could store the delete button in the first loop and use it in the second loop
var foundDeleteLinks = [];
$("a:contains('Delete')").each(function() {
var $deleteLink = $(this);
var userLinkHref = $deleteLink.parent().parent().parent().find("span.UserLink a").attr("href");
var id = userLinkHref.replace("/User.aspx?ID=", "");
foundDeleteLinks.push({id:id,$deleteLink:$deleteLink});
console.log(id);
});
var uniqueIds = [];
$.each(foundDeleteLinks, function(i, deleteLink){
var id = deleteLink.id;
if($.inArray(id, uniqueIds) === -1) { uniqueIds.push(id) }
else {
deleteLink.$deleteLink.click();
}
});
You may be able to do it in one loop
var foundIds = [];
$("a:contains('Delete')").each(function() {
var $deleteLink = $(this);
var userLinkHref = $deleteLink.parent().parent().parent().find("span.UserLink a").attr("href");
var id = userLinkHref.replace("/User.aspx?ID=", "");
if($.inArray(id, foundIds) === -1) { foundIds.push(id) }
else { $deleteLink.click(); }
});
I hope that helps enough to find your problems.

Extracting all CSS classes and ID's in a given HTML block

I have a textarea where I paste a block of HTML code. When I hit submit, I want to extract all CSS classes and ID's from that block and throw them into an array.
So far I have the submit working as well as the regular expression, but i don't know how to filter through the whole block and extract all instances where text matches my regular expression.
index.html
<body>
<textarea id="codeInput"></textarea>
<button id="submitCode">submit</button>
<script src="functions.js"></script>
</body>
function.js
$(function() {
$('#submitCode').click(function() {
var codeInput = $('textarea#codeInput').val();
console.log(codeInput);
});
});
$('#submitCode').click(function() {
var codeInput = $('textarea#codeInput').val();
var codeHTML = $('<div>', { html: codeInput }); // Parse the input as HTML
var allIds = [];
var allClasses = [];
codeHTML.find('[id]').each(function() {
allIds.push(this.id);
});
codeHTML.find('[class]').each(function() {
allClasses = allClasses.concat(this.className.split(' '));
});
console.log("IDs: " + allIds.join(', '));
console.log("Classes: " + allClasses.join(', '));
});
Make your function.js something like this:
$(function() {
$('#submitCode').click(function() {
var codeInput = $('textarea#codeInput').val();
var $input = $(codeInput);
var attrs: {
'class': [],
'id': []
};
$input.find('*').each(function() {
attrs.class.push($(this).attr('class'));
attrs.id.push($(this).attr('id'));
});
attrs.class.push($input.attr('class'));
attrs.id.push($input.attr('id'));
});
});
That goes through each element in the input code, and removes their class and id attributes, by first going through all the children of the container element in the input, and then afterwards doing the same for the container element in the input.
Personally I like Barmar's solution the best, but this works (jsfiddle):
$('#submitCode').click(function() {
var codeInput = $('#codeInput').val();
var ids = codeInput.match(/id="(.*?)"/);
var classes = codeInput.match(/class="(.*?)"/);
var output = classes[1].split(" ");
output.push( ids[1] );
console.log(output);
});
$(function() {
$('#submitCode').click(function() {
var ids = [], classes = [];
$("[id],[class]").each(function(i, el) {
var id, c;
if (id = $(this).attr('id')) {
ids.push(id);
}
if (c = $(el).attr('class')) {
classes.push(c);
}
});
console.log(ids, classes);
});
});
<textarea id="codeInput">
<div id="hello"><div class="w"></div></div>
<div id="world"></div>
<div class="my-class"></div>
</textarea>
<button id="submitCode">submit</button>
$(function() {
$('#submitCode').click(function() {
var CSS_CLASSES = [];
var CSS_IDS = [];
var el = document.createElement( 'div' );
var text = $("#codeInput").val();
el.innerHTML = text;
var nodes = el.getElementsByTagName('*');
for(var i = 0; i < nodes.length; i++) {
var node = nodes[i];
if(node.id.length > 0) {
CSS_IDS.push(node.id);
}
if(node.className.length > 0) {
CSS_CLASSES.push(node.className);
}
}
console.log(CSS_CLASSES);
console.log(CSS_IDS);
});
});
http://jsfiddle.net/zeZ93/6/
I had this very same challenge and modified the code by #Ryan to extract all (unique) classes and IDs, including when multiple classes are applied to the same element. It's very useful and works for any URL.
See http://jsfiddle.net/pixelfast/4uftwbm0/57/
Thank you.
<!-- HTML -->
<textarea id="codeInput" cols=50 rows=10></textarea>
<button id="submitCode">submit</button>
<!-- jQuery -->
var remoteURL = "https://getbootstrap.com";
function url_content(url) {
return $.get(url);
}
url_content(remoteURL).success(function(data) {
$('#codeInput').val(data);
});
$(function() {
$('#submitCode').click(function() {
var CSS_CLASSES = [];
var CSS_IDS = [];
var el = document.createElement('div');
var text = $("#codeInput").val();
el.innerHTML = text;
var nodes = el.getElementsByTagName('*');
for (var i = 0; i < nodes.length; i++) {
var node = nodes[i];
if (node.id.length > 0) {
CSS_IDS.push(node.id);
}
if (node.className.length > 0) {
var x = node.className.split(" ")
$.each(x, function(index, val) {
if (val != '') {
CSS_CLASSES.push(val);
}
});
}
}
console.log("CLASSES FOUND: ", unique(CSS_CLASSES));
console.log("IDs FOUND: ", unique(CSS_IDS));
});
});
function unique(list) {
var result = [];
$.each(list, function(i, e) {
if ($.inArray(e, result) == -1) result.push(e);
});
return result;
}

OnChange event doesn't seem to get hooked correctly

Probable something really stupid but suppose I have 2 elements that match $('[id$=_product_id]') why are the change events not matched properly?
var numberPattern = /\d+/g;
$('[id$=_product_id]').each(function(idx, elem) {
recordId = elem.id.match(numberPattern)
productId = elem.value;
console.log(recordId);
$("#client_order_order_lines_attributes_" + recordId + "_product_id").on("change", function(e) {
console.log(recordId);
})
});
I created a fiddle that demonstrates this http://jsfiddle.net/hLYpE/1/
What am I missing?
You need to declare those variables, that's the danger of implicit globals.
var recordId = elem.id.match(numberPattern);
var productId = elem.value;
Demo: http://jsfiddle.net/elclanrs/hLYpE/4/
You should change the code adding the var in front of the name of the variables:
var numberPattern = /\d+/g;
$('[id$=_product_id]').each(function(idx, elem) {
var recordId = elem.id.match(numberPattern),
productId = elem.value;
console.log(recordId);
$("#client_order_order_lines_attributes_" + recordId + "_product_id").on("change", function(e) {
console.log(recordId);
})
});

how to change an element type using jquery

I have the the following code
<b class="xyzxterms" style="cursor: default; ">bryant keil bio</b>
How would I replace the b tag to a h1 tag but keep all other attributes and information?
Here's one way you could do it with jQuery:
var attrs = { };
$.each($("b")[0].attributes, function(idx, attr) {
attrs[attr.nodeName] = attr.nodeValue;
});
$("b").replaceWith(function () {
return $("<h1 />", attrs).append($(this).contents());
});
Example: http://jsfiddle.net/yapHk/
Update, here's a plugin:
(function($) {
$.fn.changeElementType = function(newType) {
var attrs = {};
$.each(this[0].attributes, function(idx, attr) {
attrs[attr.nodeName] = attr.nodeValue;
});
this.replaceWith(function() {
return $("<" + newType + "/>", attrs).append($(this).contents());
});
};
})(jQuery);
Example: http://jsfiddle.net/mmNNJ/
Not sure about jQuery. With plain JavaScript you could do:
var new_element = document.createElement('h1'),
old_attributes = element.attributes,
new_attributes = new_element.attributes;
// copy attributes
for(var i = 0, len = old_attributes.length; i < len; i++) {
new_attributes.setNamedItem(old_attributes.item(i).cloneNode());
}
// copy child nodes
do {
new_element.appendChild(element.firstChild);
}
while(element.firstChild);
// replace element
element.parentNode.replaceChild(new_element, element);
DEMO
Not sure how cross-browser compatible this is though.
A variation could be:
for(var i = 0, len = old_attributes.length; i < len; i++) {
new_element.setAttribute(old_attributes[i].name, old_attributes[i].value);
}
For more information see Node.attributes [MDN].
#jakov and #Andrew Whitaker
Here is a further improvement so it can handle multiple elements at once.
$.fn.changeElementType = function(newType) {
var newElements = [];
$(this).each(function() {
var attrs = {};
$.each(this.attributes, function(idx, attr) {
attrs[attr.nodeName] = attr.nodeValue;
});
var newElement = $("<" + newType + "/>", attrs).append($(this).contents());
$(this).replaceWith(newElement);
newElements.push(newElement);
});
return $(newElements);
};
#Jazzbo's answer returned a jQuery object containing an array of jQuery objects, which wasn't chainable. I've changed it so that it returns an object more similar to what $.each would have returned:
$.fn.changeElementType = function (newType) {
var newElements,
attrs,
newElement;
this.each(function () {
attrs = {};
$.each(this.attributes, function () {
attrs[this.nodeName] = this.nodeValue;
});
newElement = $("<" + newType + "/>", attrs).append($(this).contents());
$(this).replaceWith(newElement);
if (!newElements) {
newElements = newElement;
} else {
$.merge(newElements, newElement);
}
});
return $(newElements);
};
(Also did some code cleanup so it passes jslint.)
Only way I can think of is to copy everything over manually: example jsfiddle
HTML
<b class="xyzxterms" style="cursor: default; ">bryant keil bio</b>
Jquery/Javascript
$(document).ready(function() {
var me = $("b");
var newMe = $("<h1>");
for(var i=0; i<me[0].attributes.length; i++) {
var myAttr = me[0].attributes[i].nodeName;
var myAttrVal = me[0].attributes[i].nodeValue;
newMe.attr(myAttr, myAttrVal);
}
newMe.html(me.html());
me.replaceWith(newMe);
});
#Andrew Whitaker: I propose this change:
$.fn.changeElementType = function(newType) {
var attrs = {};
$.each(this[0].attributes, function(idx, attr) {
attrs[attr.nodeName] = attr.nodeValue;
});
var newelement = $("<" + newType + "/>", attrs).append($(this).contents());
this.replaceWith(newelement);
return newelement;
};
Then you can do things like: $('<div>blah</div>').changeElementType('pre').addClass('myclass');
I like the idea of #AndrewWhitaker and others, to use a jQuery plugin -- to add the changeElementType() method. But a plugin is like a blackbox, no mater about the code, if it is litle and works fine... So, performance is required, and is most important than code.
"Pure javascript" have better performance than jQuery: I think that #FelixKling's code have better performance than #AndrewWhitaker's and others.
Here a "pure Javavascript" (and "pure DOM") code, encapsulated into a jQuery plugin:
(function($) { // #FelixKling's code
$.fn.changeElementType = function(newType) {
for (var k=0;k<this.length; k++) {
var e = this[k];
var new_element = document.createElement(newType),
old_attributes = e.attributes,
new_attributes = new_element.attributes,
child = e.firstChild;
for(var i = 0, len = old_attributes.length; i < len; i++) {
new_attributes.setNamedItem(old_attributes.item(i).cloneNode());
}
do {
new_element.appendChild(e.firstChild);
}
while(e.firstChild);
e.parentNode.replaceChild(new_element, e);
}
return this; // for chain... $(this)? not working with multiple
}
})(jQuery);
Here is a method I use to replace html tags in jquery:
// Iterate over each element and replace the tag while maintaining attributes
$('b.xyzxterms').each(function() {
// Create a new element and assign it attributes from the current element
var NewElement = $("<h1 />");
$.each(this.attributes, function(i, attrib){
$(NewElement).attr(attrib.name, attrib.value);
});
// Replace the current element with the new one and carry over the contents
$(this).replaceWith(function () {
return $(NewElement).append($(this).contents());
});
});
With jQuery without iterating over attributes:
The replaceElem method below accepts old Tag, new Tag and context and executes the replacement successfully:
replaceElem('h2', 'h1', '#test');
function replaceElem(oldElem, newElem, ctx) {
oldElems = $(oldElem, ctx);
//
$.each(oldElems, function(idx, el) {
var outerHTML, newOuterHTML, regexOpeningTag, regexClosingTag, tagName;
// create RegExp dynamically for opening and closing tags
tagName = $(el).get(0).tagName;
regexOpeningTag = new RegExp('^<' + tagName, 'i');
regexClosingTag = new RegExp(tagName + '>$', 'i');
// fetch the outer elem with vanilla JS,
outerHTML = el.outerHTML;
// start replacing opening tag
newOuterHTML = outerHTML.replace(regexOpeningTag, '<' + newElem);
// continue replacing closing tag
newOuterHTML = newOuterHTML.replace(regexClosingTag, newElem + '>');
// replace the old elem with the new elem-string
$(el).replaceWith(newOuterHTML);
});
}
h1 {
color: white;
background-color: blue;
position: relative;
}
h1:before {
content: 'this is h1';
position: absolute;
top: 0;
left: 50%;
font-size: 5px;
background-color: black;
color: yellow;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="test">
<h2>Foo</h2>
<h2>Bar</h2>
</div>
Good Luck...
Javascript solution
Copy the attributes of old element to the new element
const $oldElem = document.querySelector('.old')
const $newElem = document.createElement('div')
Array.from($oldElem.attributes).map(a => {
$newElem.setAttribute(a.name, a.value)
})
Replace the old element with the new element
$oldElem.parentNode.replaceChild($newElem, $oldElem)
Here is my version. It's basically #fiskhandlarn's version, but instead of constructing a new jQuery object, it simply overwrites the old elements with the newly created ones, so no merging is necessary.
Demo: http://jsfiddle.net/0qa7wL1b/
$.fn.changeElementType = function( newType ){
var $this = this;
this.each( function( index ){
var atts = {};
$.each( this.attributes, function(){
atts[ this.name ] = this.value;
});
var $old = $(this);
var $new = $('<'+ newType +'/>', atts ).append( $old.contents() );
$old.replaceWith( $new );
$this[ index ] = $new[0];
});
return this;
};

getting the names of elements in JS/jQuery

I have some checkbox inputs like so:
<input type="checkbox" name="1" class="filter"/>
<input type="checkbox" name="2" class="filter"/>
...etc...
I'm trying to write a function where any time a checkbox is selected, it generates a string with all the names concatenated. Here's what I have so far:
$('.filter').click(function(event){
var filters = $('.filter').toArray();
var fstr = "";
for (f in filters)
{
fstr = fstr+","+f.name;
}
alert(fstr);
});
The names keep coming up as 'undefined', though (i.e. the alert returns ,undefined,undefined,undefined,undefined,undefined,undefined). How do I access the names?
Here's how it's meant to be done:
$('.filter').click(function (event)
{
var fstr = '';
$('.filter[name]').each(function ()
{
fstr += ',' + $(this).attr('name');
});
alert(fstr);
});
You can use .map() to get what you're after, like this:
$('.filter').click(function(event){
var names = $('.filter').map(function () {
return $(this).attr("name");
}).get().join(',');
alert(names);
});
Just change $('.filter') to $('.filter:checked') if you want a list containing only the checked ones.
$('.filter').click(function(event){
var filters = $('.filter').toArray();
var fstr = "";
for (f in filters)
{
fstr = fstr+","+filters[f].name;
}
alert(fstr);
});
Why are you doing it that way anyway?
$('.filter').click(function(event){
var str = '';
$('.filter').each(function () {
str += $(this).attr('name') +",";
});
alert(str);
});
(function($filters) {
$filters.click(function(event) {
var filters = $filters.toArray();
var fstr = [];
for (var i = filters.length - 1; i > -1; --i) {
fstr.push(filters[i].name);
}
fstr = fstr.join(",");
alert(fstr);
}
})($('.filter'));
Try $(f).name.
How about $(elem).attr("name")?
I'm thinking the convert to array is your problem, try:
var filters = $('.filter');
for(var nI = 0; nI < filters.length; nI++)
filters.eq(nI).attr('name');

Categories