We are trying to use this repeater:
https://www.jqueryscript.net/form/Form-Fields-Repeater.html
It seems there is problem if you look at the demo.
https://www.jqueryscript.net/demo/Form-Fields-Repeater/
Try on demo to add for example four groups.(press three times add button).
Then remove all four groups by pressing Delete button.
Then press add again to add a new group.
If you inspect the name element it is:
The problem is with test[4]name. Normally it should be test[0]name.
It seems that when you delete element does not delete the counting.
So if you play a little bit with delete/add buttons counting is wrong.
Javascript of this is :
jQuery.fn.extend({
createRepeater: function () {
var addItem = function (items, key) {
var itemContent = items;
var group = itemContent.data("group");
var item = itemContent;
var input = item.find('input,select');
input.each(function (index, el) {
var attrName = $(el).data('name');
var skipName = $(el).data('skip-name');
if (skipName != true) {
$(el).attr("name", group + "[" + key + "]" + attrName);
} else {
if (attrName != 'undefined') {
$(el).attr("name", attrName);
}
}
})
var itemClone = items;
$("<div class='items'>" + itemClone.html() + "<div/>").appendTo(repeater);
};
/* find elements */
var repeater = this;
var items = repeater.find(".items");
var key = 0;
var addButton = repeater.find('.repeater-add-btn');
var newItem = items;
if (key == 0) {
items.remove();
addItem(newItem, key);
}
/* handle click and add items */
addButton.on("click", function () {
key++;
addItem(newItem, key);
});
}
});
Have anyone ever used this repeater? or have a version of this that works correct?
Desired Functionality: On selecting a checkbox, a span is created with an id & data attribute same as the checkbox and appended to a div. On clicking the 'x' on this span should uncheck the checkbox and remove the span as well.
Issue: On selecting the checkbox, an additional span with an 'undefined' label is created.
JSFIDDLE
var filtersApplied = [];
$('.ps-sidebar').on('change', 'input[type=checkbox]', function () {
var me = $(this);
console.log('me', me);
if (me.prop('checked') === true) {
filtersApplied.push([
...filtersApplied,
{ id: me.attr('id'), data: me.attr('data-filter-label') }
]);
} else {
filtersApplied = filtersApplied.map(function (item, index) {
return item.filter(function (i) {
return i.id !== item[index].id;
});
});
}
if (filtersApplied.length === 0) {
$('.ps-plans__filters').hide();
$('.ps-plans__filters-applied').html('');
} else {
$('.ps-plans__filters').show();
var filtersAppliedHtml = '';
filtersApplied.map(function (elements) {
console.log('items', elements);
return elements.map(function (el, i) {
console.log('item', el);
return (filtersAppliedHtml +=
'<span class="ps-plans__filter" id="' + el.id + '_' + i +'">' +el.data +
'<span class="icon-remove-circle remove-filter" data-filter="' +el.data +'"> X</span></span>');
});
});
console.log('filtersAppliedHtml', filtersAppliedHtml);
console.log($('.ps-plans__filters-applied').html(filtersAppliedHtml));
}
});
Your undefined label is because of the ...filtersApplied
if (me.prop('checked') === true) {
filtersApplied.push([
//this ...filtersApplied
{ id: me.attr('id'), data: me.attr('data-filter-label') }
]);
Note that filtersApplied is an array and you're making a push(), this method inserts a value in the end of the array, so your ...filtersApplied makes no sense. Just remove it and you'll be fine. You can se more here https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/push
there are few thing that need to be fixed.
when adding an element you should not push filtersApplied along with new object. instead you better do arr = [...arr, obj];
when remove an item you could apply a filter instead based on me.attr('id'). with map you would get undefined values;
after that you would map only once to build your html content, not twice;
var filtersApplied = [];
$('.ps-sidebar').on('change', 'input[type=checkbox]', function () {
var me = $(this);
if (me.prop('checked') === true) {
filtersApplied = [
...filtersApplied,
{ id: me.attr('id'), data: me.attr('data-filter-label') }
];
} else {
filtersApplied = filtersApplied.filter(function (item, index) {
return me.attr('id') !== item.id;
});
}
if (filtersApplied.length === 0) {
$('.ps-plans__filters').hide();
$('.ps-plans__filters-applied').html('');
} else {
$('.ps-plans__filters').show();
var filtersAppliedHtml = '';
filtersApplied.map(function (el, i) {
return (filtersAppliedHtml +=
'<span class="ps-plans__filter" id="' +
el.id +
'_' +
i +
'">' +
el.data +
'<span class="icon-remove-circle remove-filter" data-filter="' +
el.data +
'"> X</span></span>');
});
$('.ps-plans__filters-applied').html(filtersAppliedHtml);
}
});
I'm pretty new to js and having a hard time figuring out the best way to generate a custom url depending on what links are selected. You can view what I have done here. http://jsfiddle.net/1fz50z1y/26/ I will also paste my info here.
var products = [];
var quantity = [];
qstring = '';
$('input.product-radio, select.products-howmany').change(function() {
var $this = $(this)
var $product = $(this).closest('.product-options-left');
var $radio = $product.find('input.product-radio');
var $select = $product.find('select.products-howmany')
var qid = $select.val();
var pid = $radio.val();
currentStatus = $radio.prop('checked'),
theString = '';
qString = '';
pString = '';
if (currentStatus) {
products.push(pid);
quantity.push(qid);
if ($product.find('div.quantity').removeClass('q-hidden')) {
//code
}
} else {
products.splice(products.indexOf(pid), 1);
quantity.splice(quantity.indexOf(qid), 1);
$product.find('div.quantity').addClass('q-hidden');
}
if ((products.length > -1) || (quantity.length > -1)) {
if ((products.length === 0) || (quantity.length === 0)) {
console.log("Q Length: " + quantity.length);
pString += products[0];
qString += quantity[0];
console.log("qString = " + quantity);
} else {
pString = products.join('-p');
qString = quantity.join('_q');
if (quantity.length > 1) {
qString = quantity.splice(quantity.indexOf(qid), 1);
pString = products.splice(products.indexOf(pid), 1);
}
console.log("+ Q Length: " + quantity.length);
console.log("ADDING " + "p" + pString + "_q" + qString);
}
if ((qString == 'undefined') || (pString == 'undefined')) {
$('a.options-cart').prop("href", "#");
} else {
//$('a.options-cart').prop("href", "/cart/add/p" + theString + "_q" + qstring + "?destination=/cart");
//$('a.options-cart').prop("href", "/cart/add/p" + theString + "?destination=/cart");
$('a.options-cart').prop("href", "/cart/add/p" + pString + "_q" + qString + "?destination=/cart");
}
}
});
$('a.options-cart').click(function() {
alert(qstring);
var $this = $(this);
href = $this.attr('href');
if (href == '#') {
alert("You must select a product.");
return false;
}
});
When you click on the add link icon it displays a drop down where you can select the quantity. So changing the quantity should also update the link and how it is created. I am trying to figure out how to create the link so the end result looks like so.
cart/add/p123_q1?destination=/cart this is how it would look with a single item. Where p = the product ID and q = the quantity. Unclicking the add to cart should remove those items and changing the drop down should update the quantity. If there is more than one item it should append to the link like so. cart/add/p123_q1-p234_q2-p232_q4?destination=/cart and then unclicking or changing quantity on any of those items should reflect the change in the link. I am not sure if I am going about this all wrong but I have been trying forever and many different routes to go about trying to achieve this effect. If anyone could please help me figure this out I would be greatly appreciated!
I was able to get this to work properly using this piece of code. Hope this maybe helps someone.
$('input.product-radio, select.products-howmany').change(function () {
var $product = $(this).closest('.product-options-left');
var $radio = $product.find('input.product-radio');
var $select = $product.find('select.products-howmany')
$product.find('div.quantity').toggleClass('q-hidden', !$radio.prop('checked'));
$product.find('label.quantity').toggleClass('q-hidden', !$radio.prop('checked'));
var string = $('.product-radio')
.filter(':checked')
.map(function(){
return $(this)
.closest('.product-options-left')
.find('.products-howmany')
.val();
})
.get()
.join('-');
$('a.options-cart').prop("href", "/cart/add/" + string + "?destination=/cart");
});
$('a.options-cart').click(function() {
alert(qstring);
var $this = $(this);
href = $this.attr('href');
if (href == '#') {
alert("You must select a product.");
return false;
}
});
In below code I am dynamically generating td elements in for loop.
jQuery("#dialog_load_content").load(url, function() {
var clientName = jQuery('#client option:selected').text();
var clientId = Number(jQuery('#client option').filter(function() {return jQuery(this).html() == clientName;}).val());
var navDate = jQuery('input:checkbox:checked.signOff').closest('tr').find('td:eq(2)').html();
var fundName = jQuery('input:checkbox:checked.signOff').closest('tr').find('td:eq(0)').html();
var fundId = Number(jQuery('#fund option').filter(function() {return jQuery(this).html() == fundName;}).val());
jQuery.post('getNavPackReportsStatus', {clientId: clientId, fundId: fundId, periodEndDate: navDate}, function(data) {
var reports = data;
for(var count = 0; count< reports.length; count++) {
jQuery('#wrkbkRptTable tbody').append('<tr>' +
'<td><input type="checkbox" id="'+reports[count].reportStoreId+'" name="'+reports[count].reportName+'" checked/></td>'+
'<td>' + (count + 1) + '</td>'+
'<td>' + reports[count].reportGroupDisplayName + '</td>'+
'<td>' + reports[count].reportName + '</td>'+
'<td id="chkReportID">' + ((reports[count].reportStoreId == null || reports[count].reportStoreId == '') ? '<font color="red">Not Available</font>' : '<font color="green">Available</font>') + '</td>'+
'</tr>');
}
});
});
I tried to disable check box and uncheck check box using this, but it's not working
jQuery('#wrkbkRptTable input:checked').each(function() {
var test=jQuery('#wrkbkRptTable input:checked').attr('id');
if(test==null || test=='' || test=='undefined')
{
alert(test);
jQuery("#"+test+"").prop( "disabled", true );
}
});
I want to disable check box and uncheck it using first td (id attribute value) like
this: if (id == null then disable & uncheck it)
Try this
jQuery.each( jQuery('#wrkbkRptTable input:checked'), function(_, item) {
var item = jQuery(item);
var id = item.attr('id');
if (typeof id == 'undefined') {
item.attr('checked', false);
item.attr('disabled', true);
}
} )
This code will receive all checked checkboxes. Then it will test if item's ID is present. If not, it will uncheck current checkbox and disable it.
try something like this
$(document).ready(function() {
jQuery('#wrkbkRptTable input:checked').each(function() {
var test = this.id;
if(test==null || test=='undefined' || test.trim()=='')
{
this.checked = false;
this.disabled = true;
}
});
});
Use firebug aor chrome debugger to debug this.Your code for disabling checkbox is right.
I guess the conditions in if are not quite fit
if(test==null || test=='' || test=='undefined')
{
// means not got test
}
try this
if(test)
{
// ur codes
}
jQuery('#wrkbkRptTable input:checked').each(function() {
var test=jQuery(this).attr('id');
if(!test)
{
alert(test);
jQuery(this).prop( "disabled", true );
}
});
I want to create something like a recorder whichs tracks all actions of a user. For that, i need to identify elements the user interacts with, so that i can refer to these elements in a later session.
Spoken in pseudo-code, i want to be able to do something like the following
Sample HTML (could be of any complexity):
<html>
<body>
<div class="example">
<p>foo</p>
<span>bar</span>
</div>
</body>
</html>
User clicks on something, like the link. Now i need to identify the clicked element and save its location in the DOM tree for later usage:
(any element).onclick(function() {
uniqueSelector = $(this).getUniqueSelector();
})
Now, uniqueSelector should be something like (i don't mind if it is xpath or css selector style):
html > body > div.example > span > a
This would provide the possibility to save that selector string and use it at a later time, to replay the actions the user made.
How is that possible?
Update
Got my answer: Getting a jQuery selector for an element
I'll answer this myself, because i found a solution which i had to modify. The following script is working and is based on a script of Blixt:
jQuery.fn.extend({
getPath: function () {
var path, node = this;
while (node.length) {
var realNode = node[0], name = realNode.name;
if (!name) break;
name = name.toLowerCase();
var parent = node.parent();
var sameTagSiblings = parent.children(name);
if (sameTagSiblings.length > 1) {
var allSiblings = parent.children();
var index = allSiblings.index(realNode) + 1;
if (index > 1) {
name += ':nth-child(' + index + ')';
}
}
path = name + (path ? '>' + path : '');
node = parent;
}
return path;
}
});
Same solution like that one from #Alp but compatible with multiple jQuery elements.
jQuery('.some-selector') can result in one or many DOM elements. #Alp's solution works only with the first one. My solution concatenates all the patches with , if necessary.
jQuery.fn.extend({
getPath: function() {
var pathes = [];
this.each(function(index, element) {
var path, $node = jQuery(element);
while ($node.length) {
var realNode = $node.get(0), name = realNode.localName;
if (!name) { break; }
name = name.toLowerCase();
var parent = $node.parent();
var sameTagSiblings = parent.children(name);
if (sameTagSiblings.length > 1)
{
var allSiblings = parent.children();
var index = allSiblings.index(realNode) + 1;
if (index > 0) {
name += ':nth-child(' + index + ')';
}
}
path = name + (path ? ' > ' + path : '');
$node = parent;
}
pathes.push(path);
});
return pathes.join(',');
}
});
If you want just handle the first element do it like this:
jQuery('.some-selector').first().getPath();
// or
jQuery('.some-selector:first').getPath();
I think a better solution would be to generate a random id and then access an element based on that id:
Assigning unique id:
// or some other id-generating algorithm
$(this).attr('id', new Date().getTime());
Selecting based on the unique id:
// getting unique id
var uniqueId = $(this).getUniqueId();
// or you could just get the id:
var uniqueId = $(this).attr('id');
// selecting by id:
var element = $('#' + uniqueId);
// if you decide to use another attribute other than id:
var element = $('[data-unique-id="' + uniqueId + '"]');
(any element).onclick(function() {
uniqueSelector = $(this).getUniqueSelector();
})
this IS the unique selector and path to that clicked element. Why not use that? You can utilise jquery's $.data() method to set the jquery selector. Alternatively just push the elements you need to use in the future:
var elements = [];
(any element).onclick(function() {
elements.push(this);
})
If you really need the xpath, you can calculate it using the following code:
function getXPath(node, path) {
path = path || [];
if(node.parentNode) {
path = getXPath(node.parentNode, path);
}
if(node.previousSibling) {
var count = 1;
var sibling = node.previousSibling
do {
if(sibling.nodeType == 1 && sibling.nodeName == node.nodeName) {count++;}
sibling = sibling.previousSibling;
} while(sibling);
if(count == 1) {count = null;}
} else if(node.nextSibling) {
var sibling = node.nextSibling;
do {
if(sibling.nodeType == 1 && sibling.nodeName == node.nodeName) {
var count = 1;
sibling = null;
} else {
var count = null;
sibling = sibling.previousSibling;
}
} while(sibling);
}
if(node.nodeType == 1) {
path.push(node.nodeName.toLowerCase() + (node.id ? "[#id='"+node.id+"']" : count > 0 ? "["+count+"]" : ''));
}
return path;
};
Reference: http://snippets.dzone.com/posts/show/4349
Pure JavaScript Solution
Note: This uses Array.from and Array.prototype.filter, both of which need to be polyfilled in IE11.
function getUniqueSelector(node) {
let selector = "";
while (node.parentElement) {
const siblings = Array.from(node.parentElement.children).filter(
e => e.tagName === node.tagName
);
selector =
(siblings.indexOf(node)
? `${node.tagName}:nth-of-type(${siblings.indexOf(node) + 1})`
: `${node.tagName}`) + `${selector ? " > " : ""}${selector}`;
node = node.parentElement;
}
return `html > ${selector.toLowerCase()}`;
}
Usage
getUniqueSelector(document.getElementsByClassName('SectionFour')[0]);
getUniqueSelector(document.getElementById('content'));
While the question was for jQuery, in ES6, it is pretty easy to get something similar to #Alp's for Vanilla JavaScript (I've also added a couple lines, tracking a nameCount, to minimize use of nth-child):
function getSelectorForElement (elem) {
let path;
while (elem) {
let subSelector = elem.localName;
if (!subSelector) {
break;
}
subSelector = subSelector.toLowerCase();
const parent = elem.parentElement;
if (parent) {
const sameTagSiblings = parent.children;
if (sameTagSiblings.length > 1) {
let nameCount = 0;
const index = [...sameTagSiblings].findIndex((child) => {
if (elem.localName === child.localName) {
nameCount++;
}
return child === elem;
}) + 1;
if (index > 1 && nameCount > 1) {
subSelector += ':nth-child(' + index + ')';
}
}
}
path = subSelector + (path ? '>' + path : '');
elem = parent;
}
return path;
}
I found for my self some modified solution. I added to path selector #id, .className and cut the lenght of path to #id:
$.fn.extend({
getSelectorPath: function () {
var path,
node = this,
realNode,
name,
parent,
index,
sameTagSiblings,
allSiblings,
className,
classSelector,
nestingLevel = true;
while (node.length && nestingLevel) {
realNode = node[0];
name = realNode.localName;
if (!name) break;
name = name.toLowerCase();
parent = node.parent();
sameTagSiblings = parent.children(name);
if (realNode.id) {
name += "#" + node[0].id;
nestingLevel = false;
} else if (realNode.className.length) {
className = realNode.className.split(' ');
classSelector = '';
className.forEach(function (item) {
classSelector += '.' + item;
});
name += classSelector;
} else if (sameTagSiblings.length > 1) {
allSiblings = parent.children();
index = allSiblings.index(realNode) + 1;
if (index > 1) {
name += ':nth-child(' + index + ')';
}
}
path = name + (path ? '>' + path : '');
node = parent;
}
return path;
}
});
This answer does not satisfy the original question description, however it does answer the title question. I came to this question looking for a way to get a unique selector for an element but I didn't have a need for the selector to be valid between page-loads. So, my answer will not work between page-loads.
I feel like modifying the DOM is not idel, but it is a good way to build a selector that is unique without a tun of code. I got this idea after reading #Eli's answer:
Assign a custom attribute with a unique value.
$(element).attr('secondary_id', new Date().getTime())
var secondary_id = $(element).attr('secondary_id');
Then use that unique id to build a CSS Selector.
var selector = '[secondary_id='+secondary_id+']';
Then you have a selector that will select your element.
var found_again = $(selector);
And you many want to check to make sure there isn't already a secondary_id attribute on the element.
if ($(element).attr('secondary_id')) {
$(element).attr('secondary_id', (new Date()).getTime());
}
var secondary_id = $(element).attr('secondary_id');
Putting it all together
$.fn.getSelector = function(){
var e = $(this);
// the `id` attribute *should* be unique.
if (e.attr('id')) { return '#'+e.attr('id') }
if (e.attr('secondary_id')) {
return '[secondary_id='+e.attr('secondary_id')+']'
}
$(element).attr('secondary_id', (new Date()).getTime());
return '[secondary_id='+e.attr('secondary_id')+']'
};
var selector = $('*').first().getSelector();
In case you have an identity attribute (for example id="something"), you should get the value of it like,
var selector = "[id='" + $(yourObject).attr("id") + "']";
console.log(selector); //=> [id='something']
console.log($(selector).length); //=> 1
In case you do not have an identity attribute and you want to get the selector of it, you can create an identity attribute. Something like the above,
var uuid = guid();
$(yourObject).attr("id", uuid); // Set the uuid as id of your object.
You can use your own guid method, or use the source code found in this so answer,
function guid() {
function s4() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
}
return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
s4() + '-' + s4() + s4() + s4();
}
My Vanilla JavaScript function:
function getUniqueSelector( element ) {
if (element.id) {
return '#' + element.id;
} else if (element.tagName === 'BODY') {
return 'BODY';
} else {
return `${getUniqueSelector(element.parentElement)} > ${element.tagName}:nth-child(${myIndexOf(element)})`;
}
}
function myIndexOf( element ) {
let index = 1;
// noinspection JSAssignmentUsedAsCondition
while (element = element.previousElementSibling) index++;
return index;
}
You may also have a look at findCssSelector. Code is in my other answer.
You could do something like this:
$(".track").click(function() {
recordEvent($(this).attr("id"));
});
It attaches an onclick event handler to every object with the track class. Each time an object is clicked, its id is fed into the recordEvent() function. You could make this function record the time and id of each object or whatever you want.
$(document).ready(function() {
$("*").click(function(e) {
var path = [];
$.each($(this).parents(), function(index, value) {
var id = $(value).attr("id");
var class = $(value).attr("class");
var element = $(value).get(0).tagName
path.push(element + (id.length > 0 ? " #" + id : (class.length > 0 ? " .": "") + class));
});
console.log(path.reverse().join(">"));
return false;
});
});
Working example: http://jsfiddle.net/peeter/YRmr5/
You'll probably run into issues when using the * selector (very slow) and stopping the event from bubbling up, but cannot really help there without more HTML code.
You could do something like that (untested)
function GetPathToElement(jElem)
{
var tmpParent = jElem;
var result = '';
while(tmpParent != null)
{
var tagName = tmpParent.get().tagName;
var className = tmpParent.get().className;
var id = tmpParent.get().id;
if( id != '') result = '#' + id + result;
if( className !='') result = '.' + className + result;
result = '>' + tagName + result;
tmpParent = tmpParent.parent();
}
return result;
}
this function will save the "path" to the element, now to find the element again in the future it's gonna be nearly impossible the way html is because in this function i don't save the sibbling index of each element,i only save the id(s) and classes.
So unless each and every-element of your html document have an ID this approach won't work.
Getting the dom path using jquery and typescript functional programming
function elementDomPath( element: any, selectMany: boolean, depth: number ) {
const elementType = element.get(0).tagName.toLowerCase();
if (elementType === 'body') {
return '';
}
const id = element.attr('id');
const className = element.attr('class');
const name = elementType + ((id && `#${id}`) || (className && `.${className.split(' ').filter((a: any) => a.trim().length)[0]}`) || '');
const parent = elementType === 'html' ? undefined : element.parent();
const index = (id || !parent || selectMany) ? '' : ':nth-child(' + (Array.from(element[0].parentNode.children).indexOf(element[0]) + 1) + ')';
return !parent ? 'html' : (
elementDomPath(parent, selectMany, depth + 1) +
' ' +
name +
index
);
}
Pass the js element (node) to this function.. working little bit..
try and post your comments
function getTargetElement_cleanSelector(element){
let returnCssSelector = '';
if(element != undefined){
returnCssSelector += element.tagName //.toLowerCase()
if(element.className != ''){
returnCssSelector += ('.'+ element.className.split(' ').join('.'))
}
if(element.id != ''){
returnCssSelector += ( '#' + element.id )
}
if(document.querySelectorAll(returnCssSelector).length == 1){
return returnCssSelector;
}
if(element.name != undefined && element.name.length > 0){
returnCssSelector += ( '[name="'+ element.name +'"]' )
}
if(document.querySelectorAll(returnCssSelector).length == 1){
return returnCssSelector;
}
console.log(returnCssSelector)
let current_parent = element.parentNode;
let unique_selector_for_parent = getTargetElement_cleanSelector(current_parent);
returnCssSelector = ( unique_selector_for_parent + ' > ' + returnCssSelector )
console.log(returnCssSelector)
if(document.querySelectorAll(returnCssSelector).length == 1){
return returnCssSelector;
}
}
return returnCssSelector;
}