I need to find all elements in a page by attribute value only (ignoring the key) using jquery.
Is there a way to do this easily?
Currently, I am just iterating on all elements in the page, on every property etc..
You can use $.expr, Element.attributes, Array.prototype.some()
$.expr[":"].attrValue = function(el, idx, selector) {
return [].some.call(el.attributes, function(attr) {
return attr.value === selector[selector.length - 1]
})
};
// filter element having attribute with `value` set to `"abc"`
$(":attrValue(abc)").css("color", "blue");
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js">
</script>
<div title="abc">abc</div>
<div title="def">def</div>
<div title="ghi">ghi</div>
<div title="jkl">jkl</div>
Use brackets []
var ElementsWithAttributeKeyTest = $('[attributeKey="Test"]');
Or pass an object with the attribute name and value as parameter to this function:
var getElemsByAttribute = function(obj) {
if (obj) {
if (obj.attributeKey && obj.attributeValue) {
return $('[' + obj.attributeKey + '="' + obj.attributeValue + '"]');
}
}
}
var attrObj = {
attributeKey: 'data-color',
attributeValue: 'red'
}
getElemsByAttribute(attrObj).css('color', 'red');
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.1/jquery.min.js"></script>
<span data-color="red">Red</span>
<span data-color="red">Red</span>
<span data-color="green">Green</span>
<span data-color="blue">Blue</span>
<span data-color="red">Red</span>
<span data-color="green">Green</span>
If you want to search all attributes values you can use this small plugin:
$.fn.search_by_attr_value = function(regex) {
return this.filter(function() {
var found = false;
$.each(this.attributes, function() {
if (this.specified && this.value.match(regex)) {
found = true;
return false;
}
});
return found;
});
};
and you can use it like this:
$('*').search_by_attr_value(/^some value$/);
Based on this answer
You could define new function take as parameter the value you want to filter with (e.g get_elements_by_value(filter)), then inside this function parse all the elements of the page using $('*').each(), after that parse the attributes of every element el of those elements using attribute attributes like below :
$.each(el.attributes, function(){ })
Then inside the each loop you could make your condition and push the matched values with the passed filter inside matched[] that should be returned.
Check working example below, hope this helps.
function get_elements_by_value(filter){
var matched=[];
$('*').each(function(index,el) {
$.each(el.attributes, function() {
if( this.value===filter )
matched.push(el);
})
})
return $(matched);
}
get_elements_by_value('my_value').css('background-color','green');
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div title="my_value">AA</div>
<div title="def">BB</div>
<input type='text' name='my_value' value='CC'/>
<p class='my_value'>DD</p>
<span title="test">EE</span>
One of the common patterns I've come across in my many years of coding is the structuring/binding of the data coming from the server response (XMLHttpRequest). This problem of creating elements and appending them in a particular order as well as binding (attributes,events,content) is what I'm am trying to achieve here.
For example purposes and simplicity I am trying to create a tr --- td nested structure as well as bind the attributes from the var instructs object (table-row,table-data).
JSON Response (dummy data)
var response =[{"employeeNumber":"1002","lastName":"Murphy","firstName":"Diane","extension":"x5800","email":"dmurphy#classicmodelcars.com","officeCode":"1","reportsTo":null,"jobTitle":"President"},{"employeeNumber":"1056","lastName":"Patterson","firstName":"Mary","extension":"x4611","email":"mpatterso#classicmodelcars.com","officeCode":"1","reportsTo":"1002","jobTitle":"VP Sales"},{"employeeNumber":"1076","lastName":"Firrelli","firstName":"Jeff","extension":"x9273","email":"jfirrelli#classicmodelcars.com","officeCode":"1","reportsTo":"1002","jobTitle":"VP Marketing"},{"employeeNumber":"1088","lastName":"Patterson","firstName":"William","extension":"x4871","email":"wpatterson#classicmodelcars.com","officeCode":"6","reportsTo":"1056","jobTitle":"Sales Manager (APAC)"},{"employeeNumber":"1102","lastName":"Bondur","firstName":"Gerard","extension":"x5408","email":"gbondur#classicmodelcars.com","officeCode":"4","reportsTo":"1056","jobTitle":"Sale Manager (EMEA)"},{"employeeNumber":"1143","lastName":"Bow","firstName":"Anthony","extension":"x5428","email":"abow#classicmodelcars.com","officeCode":"1","reportsTo":"1056","jobTitle":"Sales Manager (NA)"},{"employeeNumber":"1165","lastName":"Jennings","firstName":"Leslie","extension":"x3291","email":"ljennings#classicmodelcars.com","officeCode":"1","reportsTo":"1143","jobTitle":"Sales Rep"},{"employeeNumber":"1166","lastName":"Thompson","firstName":"Leslie","extension":"x4065","email":"lthompson#classicmodelcars.com","officeCode":"1","reportsTo":"1143","jobTitle":"Sales Rep"},{"employeeNumber":"1188","lastName":"Firrelli","firstName":"Julie","extension":"x2173","email":"jfirrelli#classicmodelcars.com","officeCode":"2","reportsTo":"1143","jobTitle":"Sales Rep"},{"employeeNumber":"1216","lastName":"Patterson","firstName":"Steve","extension":"x4334","email":"spatterson#classicmodelcars.com","officeCode":"2","reportsTo":"1143","jobTitle":"Sales Rep"},{"employeeNumber":"1286","lastName":"Tseng","firstName":"Foon Yue","extension":"x2248","email":"ftseng#classicmodelcars.com","officeCode":"3","reportsTo":"1143","jobTitle":"Sales Rep"},{"employeeNumber":"1323","lastName":"Vanauf","firstName":"George","extension":"x4102","email":"gvanauf#classicmodelcars.com","officeCode":"3","reportsTo":"1143","jobTitle":"Sales Rep"},{"employeeNumber":"1337","lastName":"Bondur","firstName":"Loui","extension":"x6493","email":"lbondur#classicmodelcars.com","officeCode":"4","reportsTo":"1102","jobTitle":"Sales Rep"},{"employeeNumber":"1370","lastName":"Hernandez","firstName":"Gerard","extension":"x2028","email":"ghernande#classicmodelcars.com","officeCode":"4","reportsTo":"1102","jobTitle":"Sales Rep"},{"employeeNumber":"1401","lastName":"Castillo","firstName":"Pamela","extension":"x2759","email":"pcastillo#classicmodelcars.com","officeCode":"4","reportsTo":"1102","jobTitle":"Sales Rep"},{"employeeNumber":"1501","lastName":"Bott","firstName":"Larry","extension":"x2311","email":"lbott#classicmodelcars.com","officeCode":"7","reportsTo":"1102","jobTitle":"Sales Rep"},{"employeeNumber":"1504","lastName":"Jones","firstName":"Barry","extension":"x102","email":"bjones#classicmodelcars.com","officeCode":"7","reportsTo":"1102","jobTitle":"Sales Rep"},{"employeeNumber":"1611","lastName":"Fixter","firstName":"Andy","extension":"x101","email":"afixter#classicmodelcars.com","officeCode":"6","reportsTo":"1088","jobTitle":"Sales Rep"},{"employeeNumber":"1612","lastName":"Marsh","firstName":"Peter","extension":"x102","email":"pmarsh#classicmodelcars.com","officeCode":"6","reportsTo":"1088","jobTitle":"Sales Rep"},{"employeeNumber":"1619","lastName":"King","firstName":"Tom","extension":"x103","email":"tking#classicmodelcars.com","officeCode":"6","reportsTo":"1088","jobTitle":"Sales Rep"},{"employeeNumber":"1621","lastName":"Nishi","firstName":"Mami","extension":"x101","email":"mnishi#classicmodelcars.com","officeCode":"5","reportsTo":"1056","jobTitle":"Sales Rep"},{"employeeNumber":"1625","lastName":"Kato","firstName":"Yoshimi","extension":"x102","email":"ykato#classicmodelcars.com","officeCode":"5","reportsTo":"1621","jobTitle":"Sales Rep"},{"employeeNumber":"1702","lastName":"Gerard","firstName":"Martin","extension":"x2312","email":"mgerard#classicmodelcars.com","officeCode":"4","reportsTo":"1102","jobTitle":"Sales Rep"}];
Binding (instructions)
var instructs={
tag:"tr",
attributes:{class:"table-row"},
props:{
email:{
tag:"td",
content: null,
attributes:{class:"table-data",id:"table-data-id"}
},
employeeNumber:{
tag:"td",
attributes:{class:"table-data"},
content: null,
props:{
x:{
tag: "input",
attributes:{class:"table-input"},
content: "test"
}
}
},
extension:{
tag:"td",
content: null,
attributes:{class:"table-data"}
},
firstName:{
tag:"td",
content: null,
attributes:{class:"table-data"}
},
jobTitle:{
tag:"td",
content: null,
attributes:{class:"table-data"}
},
lastName:{
tag:"td",
content: null,
attributes:{class:"table-data"}
},
officeCode:{
tag:"td",
content: null,
attributes:{class:"table-data"}
},
reportsTo:{
tag:"td",
content: null,
attributes:{class:"table-data"}
}
}
};
My Function (assemble)
function assemble(r,s,n){
var n = n || new DocumentFragment();
if(typeof r !== 'string'){ //HAS CHILDREN
r.forEach((o)=>{
for(y in s){
switch(y){
case "tag":
var tag = document.createElement(s[y]);
n.appendChild(tag);
break;
case "attributes":
for(a in s[y]) tag.setAttribute(a,s[y][a]);
break;
case "content":
if(s.content === null){
//append current property value
}
else{
tag.innerHTML = s.content;
}
break;
case "props":
for(k in o) assemble(k,s[y][k],tag); //EXECUTE PER CHILDREN
break;
}
}
});
}
else{
for(x in s){
switch(x){
case "tag":
var tag = document.createElement(s[x]);
n.appendChild(tag);
break;
case "content":
if(s.content === null){
//append current property value
}
else{
tag.innerHTML = s.content;
}
break;
case "attributes":
for(a in s[x]) tag.setAttribute(a,s[x][a]);
break;
case "props":
for(c in s[x]) assemble(r,s[x][c],tag);
break;
}
}
return n;
}
return n;
}
document.addEventListener('DOMContentLoaded',()=>{
var data = assemble(response,instructs);
console.log(data);
});
The end result I'm looking for is an array/fragment of nested tr>td both with a class attribute and the values append to the innerHTML.
<tr class ="table-row">
<td class="table-data">how do I bind the response values?</td>
<td class="table-data">how do I bind the response values?</td>
<td class="table-data">how do I bind the response values?</td>
<td class="table-data">how do I bind the response values?</td>
<td class="table-data">how do I bind the response values?</td>
<td class="table-data">how do I bind the response values?</td>
<td class="table-data">how do I bind the response values?</td>
</tr>
QUESTION:
How can I bind the property values from the response to the innerHTML of the td's?
Give a try to the following one. Sorry, I started refactoring Yours and in the end I had rewritten it. Take care that the content is always taken as innerHTML, it would not be a bad idea at all to differentiate text(append createTextNode func outcome) and html(innerHTML prop). In your data instructs.props.employeeNumber.props.x.content should be moved into instructs.props.employeeNumber.props.x.attributes.value seen how works the input[text] tag. Btw I see there are a lot of stuff that could be discussed here. Hope it helps!
function assemble (data, instr) {
var n = document.createDocumentFragment(), i;
function create(d) {
var objData = d;
return (function _(_instr, _key, _n) {
var innerHTML = !!_key && _key in objData ? objData[_key] : null,
tag = null, i;
if ('tag' in _instr) {
tag = document.createElement(_instr.tag);
tag.innerHTML = 'content' in _instr && !!_instr.content ? _instr.content : innerHTML;
if ('attributes' in _instr) {
for (i in _instr.attributes) tag.setAttribute(i, _instr.attributes[i]);
}
//recur finally
if ('props' in _instr) {
for (i in _instr.props) _(_instr.props[i], i, tag);
}
!!_n && _n.appendChild(tag);
}
return tag;
})(instr, null);
}
return (function (){
for (i in data) {
n.appendChild(create(data[i]));
}
return n;
})();
}
Though my answer does not address the code context from the question, but for structuring and binding json response i usually use regex templating. I find it simple in declaration and concise. ex,
html:
<table>
<tbody id="employee">
</tbody>
</table>
js:
var template = function(tpl, data) {
return tpl.replace(/\$\{([^\}]+)?\}/g, function($1, $2) {
return data[$2];
});
};
var rowTemplate = '<tr class ="table-row">\
<td class="table-data">${email}</td>\
<td class="table-data"><input type="text" value=' ${employeeNumber}' /></td>\
<td class="table-data">${firstName}</td>\
<td class="table-data">${lastName}</td>\
<td class="table-data">${email}</td>\
<td class="table-data">${jobTitle}</td>\
<td class="table-data">${extension}</td>\
</tr>';
var response = [{"employeeNumber":"1002","lastName":"Murphy","firstName":"Diane","extension":"x5800","email":"dmurphy#classicmodelcars.com","officeCode":"1","reportsTo":null,"jobTitle":"President"}, ....]
var b = document.getElementById('employee');
for (var i = 0; i < response.length; i++) {
var tr = document.createElement('x');
b.appendChild(tr);
tr.outerHTML = template(rowTemplate, response[i]);
}
fiddle
hope this helps.
I have two input box in angularjs html structure.
When I click on my button or a tag I want my textbox old and new value in angularjs or I want to compare that old and new value are same or not.
I'm using angular 1.4.7.
<input phone-input ng-show="mode == 'edit'" ng-model="leader.work"/>
<input phone-input ng-show="mode == 'edit'" ng-model="leader.mobile"/>
<a ng-show="mode == 'edit'" ng-click="mode = null;save_profile(leader)" style="cursor: pointer" title="Save">Save</a>
$scope.save_profile = function (leader) {
/* How to get/compare both text box old and new value are same or not*/
};
try this
function TodoCrtl($scope) {
$scope.newValue = 'Initial Text';
$scope.save_profile = function(newvalue, oldvalue) {
//Here you can access old value and new value from scope.
$scope.new = 'New Value :' + $scope.newValue;
$scope.old = 'Old Value :' + $scope.oldValue;
//After accessing update the scope old value to new value with function parameters
$scope.newValue = newvalue;
$scope.oldValue = newvalue;
};
$scope.changeValue = function() {
$scope.newValue = 'Dynamic Change';
};
}
<!DOCTYPE html>
<html ng-app>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.1.5/angular.min.js"></script>
<meta charset=utf-8 />
<title>ng-click</title>
</head>
<body>
<div ng-controller="TodoCrtl">
<input type=text ng-model="newValue" ng-init="oldValue=newValue">
<button ng-click="save_profile(newValue,oldValue)">Save</button>
<div>{{new}}</div>
<div>{{old}}</div>
<br>
<button ng-click="changeValue()">Change Dynamic</button>
</div>
</body>
</html>
The simplest possible approach which will work in all occasions is to make a copy of the leader when you load it and compare current leader with the copy you made when you press the button.
function TodoCtrl($scope) {
// initialization
var originalLeader = null;
$scope.leader = null;
$scope.mode = 'edit';
// this is where you get your leader data, in my example
// I simply set it to demo data but you can load the
// data using AJAX for example
var loadLeader = function() {
var leaderData = {
mobile: '000',
work: '111'
};
originalLeader = angular.copy(leaderData);
$scope.leader = leaderData;
}
// loadLeader will be invoked on page load
loadLeader();
$scope.save_profile = function (leader) {
// you have access to your original data and current data,
// you can compare them and do whatever you want with them
console.log('originalLeader ', originalLeader);
console.log('leader ', leader);
// for example
if ( leader.mobile != originalLeader.mobile ) {
alert('Mobile has changed from ' + originalLeader.mobile + ' to ' + leader.mobile);
}
};
}
Some answers suggested to use $scope.$watch, you can implement your solution using that but you need to be careful as the $scope.$watch callback will be invoked on each change. To illustrate what I mean let's add something like this to your code:
$scope.$watch('leader.mobile', function(newVal,oldVal) {
console.log('newVal ', newVal);
console.log('oldVal ', oldVal);
});
Let the leader.mobile be 000 at the init time.
You type 1 to the text box, now leader.mobile is 0001, the callback function will be invoked and the log will be:
newVal 0001
oldVal 000
Now you press backspace and delete 1 you previously typed, the leader.mobile variable is now 000 and the log is:
newVal 000
oldVal 0001
Your current data is same as starting data but the $scope.$watch was invoked twice and is difficult to determine if data has really changed or not. You would need to implement some additional code for that, something like this:
// be careful about this, you need to set to null if you reload the data
var originalMobile = null;
$scope.$watch('leader.mobile', function(newVal,oldVal) {
// set originalMobile to value only if this is first
// invocation of the watch callback, ie. if originalMobile
// was not set yet
if ( originalMobile == null ) {
originalMobile = oldVal;
}
});
$scope.save_profile = function(leader) {
if ( leader.mobile != originalMobile ) {
// ...
}
}
You can use the $watch function. The link below will show you how to implement it. You can get an old and new value with it.
How do I use $scope.$watch and $scope.$apply in AngularJS?