CasperJS querySelectorAll + map.call - javascript

html file
<table id="tbl_proxy_list">
...........
<tr>
......
<td align="left">
<time class="icon icon-check">1 min</time>
</td>
<td align="left">
<div class="progress-bar" data-value="75" title="4625"></div>
</td>
</tr>
</table>
ip.js file
casper.start('http://www.proxynova.com/proxy-server-list/', function() {
var info_text = this.evaluate(function() {
var nodes = document.querySelectorAll('table[id="tbl_proxy_list"] tr');
return [].map.call(nodes, function(node) {
//return node.innerText;
return node;
});
});
var tr_data = info_text.map(function(str) {
var elements = str;
var data = {
ip : elements,
port : elements[1],
lastcheck : elements[2],
speed : elements[3], // <== value is 75..
};
return data;
});
utils.dump(tr_data);
});
casper.run();
return node.innerText is only text.
ip is a text value
port is a text value
lastcheck is a text value
speed is not a text value (data-value="75")
I want to import data-value="75" (speed value is 75).
I do not know what to do.
========================================
It's work.. good. thank you Artjom.
but tr_data echo error.
first, you code modify..
return {
"ip": tr.children[0].innerText.trim(),
"port": tr.children[1].innerText.trim(),
"lastcheck": tr.children[2].innerText.trim(),
"speed": tr.children[3].children[0].getAttribute("data-value")
};
and echo..
//this.echo(tr_data.length);
for(var ii=0; ii<tr_data.length; ii++)
{
this.echo(tr_data[ii]['ip']);
}
at run, blow error..
TypeError: 'null' is not an object (evaluating 'tr_data.length'); what is problem?
I need your help.. thanks.

You cannot pass DOM elements from the page context (inside evaluate callback).
From the docs:
Note: The arguments and the return value to the evaluate function must be a simple primitive object. The rule of thumb: if it can be serialized via JSON, then it is fine.
Returning an array of DOM elements will result in an array of as many undefined values. That means you need to map everything inside the page context and then return the resulting array. You also need only one map.
var tr_data = this.evaluate(function() {
var nodes = document.querySelectorAll('table[id="tbl_proxy_list"] tbody tr');
return Array.prototype.map.call(nodes, function(tr, i) {
if (tr.children.length != 6) {
return null; // skip ads
}
return {
ip: tr.children[0].innerText.trim(),
port: tr.children[1].innerText.trim(),
lastcheck: tr.children[2].innerText.trim(),
speed: tr.children[3].children[0].getAttribute("data-value")
};
}).filter(function(data){
return data !== null; // filter the null out
});;
});
You also might want to trim the excess white space.

Related

AngularJS - A function to return value, if there is no value, return nothing within the cell

I am exporting table data as a pdf using pdfmake.
Depending if there a value set within the table, based on what is filtered within the Search dropdowns, I want to return that value. Otherwise I want to return ''.
As of now if nothing is set within the cell it will return as 'null' I want to return '' instead of null.
Here is the expression I am using from the html
<td style="background-color: #bfdfff; word-wrap: break-word;" class="routeCheckInGrid">
<span id="ContentPlaceHolder1_RouteCheckIn_Editable1_lblAccidentMcGoal">{{monday.accidentFreeDayMCG}} /day</span>
</td>
Tried creating a function that gives me the value or returns ' '.
scope.drivers is where all the data is set.
$scope.accidentFreeMC = function(monday) {
if ($scope.drivers) {
if ($scope.resultsRolledUp) {
return monday.accidentFreeDayMCG;
} else {
return '';
}
}
}
using it in the table
table: {
body: [
[
$scope.accidentFreeMC + ' /day',
],
]
}
What am i missing? Any help is much appreciated.
UPDATE**
Here is a working example of something similar that I would like to achieve.
Here is a row where depending on what Route is filtered in the search, will show data in the total column. If a specific Route is selected then nothing is shown in the cell.
$scope.wasTheLCPAuditCompleted = function(day) {
if ($scope.drivers) {
if ($scope.resultsRolledUp) {
return day.lcpAuditCompletedCount + '/' + day.dayRouteCount
}
if (!$scope.resultsRolledUp && (!$scope.dataEntryView || $scope.isRollup)) {
return day.lcpAuditCompleted == 'Y' ? 'Yes' : 'No';
} else {
return '';
}
}
}

Is it possible to select element by attribute value only?

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>

duplicate check for rows in smart-table

I have a duplicatecheck function which basically compares the current edited row(newRule) with the other rows in the list(ruleList). It works fine because it displays an error when duplicate coloumns are detected, but it resets if ANYOTHER row has duplicates ( my app allows user to edit the rows). I want to the error to stay and not reset, which is inside the else statement ( duplicate.error=false). In my if and else, how do I make it so that the error=true only for that specific row/index. I defined index as a paramter, so that's basically that specific row.
$scope.duplicateCheck = function (newRule, ruleList, duplicate, index) {
var error = false;
//the logic goes here, blah blah blah if duplicates detected in all the columns, error is set to true
if (error) {
duplicate.error = true;
} else {
duplicate.error = false;
duplicate.item = '';
duplicate.index = -1;
}
};
$scope.duplicateAppType = {
error: void(0),
item: '',
index: -1
};
Html (rule= row that is currently being edited also defined in my ng-model, theList=all the rows in the st-table)
<td class='errorClass'
<p ng-show="duplicateAppType.error"{{Duplicate here}}<p>
</td>
<select>
ng-model=rule.names
ng-change=duplicateCheck(rule, theList, duplicateAppType, $index)
</select>
<br>

Structuring and Binding (ajax response)

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 want to compare that old and new value are same or not for input textbox in angularjs?

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?

Categories