I would like to get the value of the selector used from a selenium webelement in javascript.
If I have the object:
var el = browser.driver.findElement(by.id('testEl'));
I'd like to get that text 'testEl' and use it elsewhere. I don't care what the selector type is(by id, by css, etc.), just what the text is.
In protractor, I could do this:
console.log(el.locator().value);
This would return the text 'testEl'.
But with just selenium non-protractor selectors, I am told that .value() is not a function.
Is there any way to pull this locator/selector value out?
EDIT:
The goal: Grab the locator text from a selenium webElement
The use: For functions like getVisibleElement
The situation: Working against a page where there can be an unknown number of elements of a certain selector, some hidden and some not(but an area above them hidden, no hidden tag on this particular portion of the element to work with), and I'd like to get just the visible ones.
In protractor:
function getVisibleElementByPageObject(protractorWebElement){
var allElementsOfSelector = element.all(by.css(protractorWebElement.locator().value));
var displayedElement = allElementsOfSelector .filter(function(elem) {
return elem.isDisplayed('cannot find' + ' ' + protractorWebElement.locator().value);
}).first();
return displayedElement ;
}
var exampleEl = $('[name="test"]');
var visibleExampleEl = getVisibleElementByPageObject(exampleEl);
I'd like to get the locator so that if I ever change what the selector is, I would need to only change it in one place - the page object where the element is declared.
I can use a var to store the string and pass that along anytime I declare an element or try to use something like the above, but that just means using a new standard of setting up page objects. It would be very convenient to access the locator in selenium like protractor does above.
You don't need to get the locator from the webElement, you should already have it the first time you use it to find the element. If your page objects aren't already set up that way, you're doing it wrong.
In webdriver.js, they show this example:
* var link = element.findElement(firstVisibleLink);
*
* function firstVisibleLink(element) {
* var links = element.findElements(By.tagName('a'));
* return webdriver.promise.filter(links, function(link) {
* return links.isDisplayed();
* }).then(function(visibleLinks) {
* return visibleLinks[0];
* });
* }
So you should be able to:
var firstElement = function(locator) {
return browser.driver.findElement(locator);
};
var firstVisible = function(locator) {
var elements = browser.driver.findElements(locator);
return browser.driver.promise.filter(elements, function(el) {
return el.isDisplayed();
}).then(function(visibleElements) {
return visibleElements[0];
});
};
var testLocator = by.css('[name="test"]');
var E1 = firstElement(testLocator);
var E1v = firstVisible(testLocator);
Related
I am trying to make a form where a customer can choose parson and automatically show the cost depend on parson number. So for that, I used jQuery form change function and calculate the cost inside of the function. My all logic is working ok but the issue is when increasing number then showing multiple costs.
Visual look:
Always I want to show the last one/ updated one
Blow my code:
var adultsSingleChage = 200;
var childrenSingleCharge = 100;
var infantsSingleCharge = 50;
$('#absbt_form input').change(function(){
var adults = $("#absbt_adults").val();
var adultsCharge = adults * adultsSingleChage;
var children = $("#absbt_children").val();
var childrenCharge = children * childrenSingleCharge;
var infants = $("#absbt_infants").val();
var infantsCharge = infants * infantsSingleCharge;
var totalCharge = adultsCharge + childrenCharge + infantsCharge;
console.log(totalCharge);
$('.total_cost').append(totalCharge);
});
I know I did the maximum of logic but for last logic, I'm confused.
How can I just show the last one?
append adds new content to existing one - that's the reason of having previous values.
Instead of using append use text:
$('.total_cost').text(totalCharge);
The problem is with the code you are appending which means you are adding the text into the element instead of replacing it. You can use the following two methods:
text(): $('.total_cost').text("iota")
html(): $('.total_cost').html("iota")
Note: Use id selector or with class use $($('.total_cost')[0])
Use html(totalCharge) instead of append(totalCharge)
Here is my code
$('#addmaxes').on('click',function(){
dlMax = $('#dlcurrentmax-input').val().trim();
ftsqMax = $('#ftsqcurrentmax-input').val().trim();
per = 95;
dlTrainingMax = (dlMax * per) / 100;
frsqTrainingMax = (ftsqMax * per) / 100;
per= 75;
var weekDl = (dlTrainingMax * per) / 100;
var rnWeekDl = Math.floor(weekDl);
per2= 60;
var weekDef = (dlTrainingMax * per2) / 100;
var rnDefWeek = Math.floor(weekDef);
var weekSq = (frsqTrainingMax * per2) / 100;
var rnWeekSq = Math.floor(weekSq);
var newDivOne = $('<div>');
var newDivTwo = $('<div>');
newDivOne.addClass('trainingProgram');
newDivTwo.addClass('trainingProgram');
newDivOne.html('<h3>Week 1');
newDivTwo.html('Deadlift (75%): 3x5 at ' + rnWeekDl + '<br/>Deficit Deadlift (60%): 8x3(90sec rest between sets) at ' + rnDefWeek + '<br/>Front Squat (60%): 3x8 at ' + rnWeekSq + '<br/>Stiff-leg deadlift: 3x8 ' + '<br/>Bent over rows: 3x8' + '<br/>Lat pulldowns: 3x12');
$('#program').append(newDivOne);
$('#program').append(newDivTwo);
});
so basically I copy and paste this function multiple times and change the added html that is in each function to display the workout for that week.(so each copied function represents a workout week)
One way I found to simplify the code is by creating an array of objects like this:
const PERCENT_FOR_MAX = 95;
var percentagesPerWeek = [
{
deadlift: 75,
frontSquat: 50,
},
{
deadlift: 80,
frontSquat: 65,
},
/// etc
];
and then add a for loop to my function like this:
$('#addmaxes').on('click',function(){
for(var i = 0; i < percentagesPerWeek.length; i++) {
dlMax = $('#dlcurrentmax-input').val().trim();
ftsqMax = $('#ftsqcurrentmax-input').val().trim();
dlTrainingMax = (dlMax * PERCENT_FOR_MAX) / 100;
frsqTrainingMax = (ftsqMax * PERCENT_FOR_MAX) / 100;
var deadliftPercent = percentagesPerWeek[i].deadlift;
var weekDl = (dlTrainingMax * deadliftPercent) / 100;
var rnWeekDl = Math.floor(weekDl);
var frontSquatPercent = percentagesPerWeek[i].frontSquat;
var weekDef = (dlTrainingMax * frontSquatPercent) / 100;
var rnDefWeek = Math.floor(weekDef);
var weekSq = (frsqTrainingMax * frontSquatPercent) / 100;
var rnWeekSq = Math.floor(weekSq);
var newDivOne = $('<div>');
var newDivTwo = $('<div>');
newDivOne.addClass('trainingProgram');
newDivTwo.addClass('trainingProgram');
newDivOne.html('<h3>Week 2</h3>');
newDivTwo.html('Deadlift ('+deadliftPercent+'%): 3x5 at ' + rnWeekDl +
'<br/>Deficit Deadlift (65%): 8x3(90sec rest between sets) at ' + rnDefWeek +
'<br/>Front Squat ('+frontSquatPercent+'%): 3x8 at ' + rnWeekSq +
'<br/>Stiff-leg deadlift: 3x8 ' +
'<br/>Bent over rows: 3x8' +
'<br/>Lat pulldowns: 3x12'
);
$('#program').append(newDivOne);
$('#program').append(newDivTwo);
}});
but the problem I'm running into is that the html that is in each function is going to be the same each week, and I need it to be customized each week,because workouts change. How can I do this. I am new to web development and have only been doing this for a month. So i'm sorry if my context is not making sense, and also this is my first time posting to this forum. Thanks in advance. :)
If you're only beginning to learn JavaScript try to avoid using jQuery - it's a crutch you don't really need for web development on modern web browsers. Also the native browser API is more performant.
Your click handler function doesn't have to be included explicitly in the event listener. The Separation of Concerns (SoC) principle states that you should avoid co-locating different concerns within the design/code. This means you should separate the functions which handle the event, from the event listeners which trigger the handlers.
You should set up the handler as a named function that you pass in, like this.
var addMaxesHandler = function(event) {
// handle the '#addmaxes' click event here
// assuming you need integer values
var dlMax = Math.parseInt(document.getElementById('dlcurrentmax-input').value, 10);
var ftsqMax = Math.parseInt(document.getElementById('ftsqcurrentmax-input').value, 10);
var presentationData = doTheCalculations(percentagesPerWeek, dlMax, ftsqMax);
presentTheResults(presentationData);
};
// attach the listener to the HTML element
var el = document.getElementById('addmaxes');
el.addEventListener('click', addMaxesHandler);
It also means you can remove it later if you want using removeEventListener(). You cannot do this with the anonymous function.
Remember the Single Responsibility Principle (SRP)? In your case you should have your functions perform one task and one task only - don't mix the calculations with the presentation. Keep breaking the problem down into smaller pieces and compose the results together.
function doTheCalculations(dataArr, dl, ftsq) {
// need a container for holding the results of calculations
var calculationResults = [];
// iterate through the 'percentagesPerWeek' array
dataArr.forEach(function(values, index){
// values is the array element e.g. { deadlift: 75, frontSquat: 50 }
// index is the array index e.g. 0 for the first element of the array
calculationResults.push(doTheMathsFunc(values, dl, ftsq));
});
return calculationResults;
}
I haven't implemented it, but the doTheMathsFunc() should perform the calculations and return an object or array containing the values to be used in the presentation. At this point you have returned another array of objects which you can used to create your HTML.
function presentTheResults(resultsArr) {
// this function will iterate through the array and build the HTML
// use DocumentFragment object for efficiency
var fragment = document.createDocumentFragment();
// get some HTML content
resultsArr.forEach(function(result, index){
var weekFragment = getWeekHtmlBlock(result, (index + 1));
fragment.appendChild(weekFragment);
});
// get a handle on the DOM container element
var container = document.getElementById('program');
// add the DocumentFragment to the DOM
container.appendChild(fragment);
}
That function just stitches the HTML fragments together and attaches the result to the DOM. The HTML fragments are created by another function getWeekHtmlBlock().
// build the block of HTML content for the week
function getWeekHtmlBlock(content, week) {
// content : object containing the information
// week : the week number
var fragment = document.createDocumentFragment();
// create HTML elements
var outerDiv = document.createElement('div');
var innerDiv = document.createElement('div');
// add the CSS class
outerDiv.classList.add('trainingProgram');
// add the H3 tag : e.g. 'Week 1'
var h3 = document.createElement('h3');
h3.textContent = "Week " + week;
outerDiv.appendChild(h3);
// iterate through the 'content' object and build the HTML elements
// these should be appended to 'innerDiv'
// TO DO !
innerDiv.appendChild( getInstructionsHtml(content, week) );
// append the HTML elements
outerDiv.appendChild(innerDiv);
fragment.appendChild(outerDiv);
// return the HTML
return fragment;
}
Hopefully by now you get the idea - keep solving smaller pieces of the problem by using a function.
If you need to change the instructions to be presented for each week then it might be a good idea to create an array of objects containing instructions for each one. The first element of the array would contain the instructions for the first week and so on.
Template literals/Template strings are a feature of ES2015 so you should be able to take advantage of them now.
Deconstructing assignment is also a JavaScript feature added in ES2015.
// example array of instructions
var weeklyInstructionsArray = [
[`Deadlift (${deadliftPercent}%): 3x5 at ${rnWeekDl}`,
`Deficit Deadlift (65%): 8x3 (90sec rest between sets) at ${rnDefWeek}`,
`Front Squat (${frontSquatPercent}%): 3x8 at ${rnWeekSq}`,
`Stiff-leg deadlift: 3x8`,
`Bent over rows: 3x8`,
`Lat pulldowns: 3x12`]
];
function getInstructionsHtml(data, weekNum) {
var instructions = weeklyInstructionsArray[weekNum-1];
// Using deconstructing assignment
// NOTE: The variable names must match those in the template literals above
var {deadliftPercent, rnWeekDl, rnDefWeek, frontSquatPercent, rnWeekSq} = data;
// stitch the array of template literals together in a single text node
var htmlContent = document.createTextNode( instructions.join('<br/>') );
return htmlContent;
}
Hopefully this answer is detailed enough to show you how to go about solving the issue.
I need to get the number only from an attribute (The number is dynamic). The button/anchor looks like this:
Delete Dish
The part I need to dissect is this bit 'bc_inventorybundle_menu_product_0' I only want the number, for use in another function (Delete a LI with an ID of menuitem0_dish)
The code I use for selecting ID's elsewhere is:
function getNum(element, attrPrefix) {
//set prefix, get number
var prefix = attrPrefix;
var num = element.attr("id").substring((prefix.length));
return num;
}
It works great on ID's but I cant seem to get it to work for Attributes instead of ID's
So User clicks delete button bc_inventorybundle_menu_product_0 then jQuery removes the < li id="menuitem0_dish">
I can't add an ID to the button so I have to use the attribute of the button. As I'm sure you can tell I'm a complete noob when it comes to JS/JQuery.
EDIT
Having read all the answers I feel I may need to elaborate a little.
I think the biggest issue is registering when the Button/Anchor is clicked.
What I currently have is this, which I know must be wrong:
$(document).on('click', 'data("field")', function(event) {
deleteDish(this);
});
function getbutNum(element, attrPrefix) {
//set prefix, get number
var prefix = attrPrefix;
var butnum = element.data("field").substring(prefix.length); //Changed as per suggestions
return butnum;
}
function deleteDish(field) {
var numbut = getbutNum();
//Delete the UL/LI
console.log("Num But" + numbut);
}
Asides from all else this gives me an error of 'unrecognized expression: data("field")'
Have you tried selecting your actual data attribute:
var num = element.attr("data-field").substring(prefix.length);
Or:
var num = element.data("field").substring(prefix.length);
EDIT
First add a class to your anchor element (I'm going under the assumption that you have more than one of these):
Delete Dish
Then:
$(".delete-dish").on("click", function (e) {
e.preventDefault();
var fieldData = $(this).data("field"),
num = fieldData.substring(fieldData.lastIndexOf("_") + 1);
console.log("Num But" + num);
});
Here is a fiddle to demonstrate
Using the attribute name that contains your input should work:
function getNum(element, attrPrefix) {
//set prefix, get number
var prefix = attrPrefix;
var num = element.attr("data-field").substring((prefix.length));
return num;
}
http://jsfiddle.net/zf3hmo4q/
Considering you want to parse attributes with "data-*" name:
function getNum(element, dataName, dataPrefix) {
var num = element.data(dataName).replace(dataPrefix, "");
return num;
}
console.log(getNum($(".btn"), "field", "bc_inventorybundle_menu_product_"));
Maybe something like this?
var getNumberFromAttribute = function(id, field) {
var field = $(id).data(field);
var parts = field.split("_");
return parts[parts.length - 1]
}
Here's a jsfiddle http://jsfiddle.net/o6go79cL/
UPDATE
You could just pass in the element. The only purpose of the id was to select the object. So you could also just do:
var getNumberFromAttribute = function(elm, field) {
var field = $(elm).data(field);
var parts = field.split("_");
return parts[parts.length - 1]
}
number = getNumberFromAttribute(anchorTag, "field");
I have the following JavaScript line:
<div id="box" name="1" margin="4px" padding="4px" onclick="memory(1)"></div>
With the associated memory() function being:
function memory(a) {
var tmpDar = a-1;
var m = document.getElementsByName(tmpDar);
m.innerHTML = arrA[tmpDar];
}
However, when I try executing the code, the HTML doesn't alter... Can somebody please help me?
document.getElementsByName() returns a NodeList and not a single element!
So in order to set the innerHTML of your div, you have to reference an entry inside that array, e.g., like this:
function memory(a) {
var tmpDar = a-1;
var m = document.getElementsByName(tmpDar);
m[0].innerHTML = arrA[tmpDar];
}
In your code you set the innerHTML property for the NodeList object, which has no (visual) effect in the document.
In general it would be better to use id instead of name. Then you could use document.getElementById() in a way like this:
function memory(a) {
var tmpDar = a-1;
var m = document.getElementById(tmpDar);
m.innerHTML = arrA[tmpDar];
}
document.getElementsByName returns an array. So if the element that you want is unique with this name, you should replace your code by :
function memory(a) {
var tmpDar = a-1;
var m = document.getElementsByName(tmpDar);
m[0].innerHTML = arrA[tmpDar]; // Here I have added index 0
}
your trying to find all elements with a name of 0 as far as I can tell. And there is no 0 name.
Also what the other two said, it returns an array you need to call an index on that array.
So I'm creating a random string value:
var randomString = function(stringLength) {
var i = 0;
stringLength = stringLength || 10;
var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz';
var randomString = '';
for (i = 0; i < stringLength; i += 1) {
var rNum = Math.floor(Math.random() * chars.length);
randomString += chars.substring(rNum, rNum + 1);
}
return randomString;
};
And associating it with a list element on one pane:
var addUniqueId = function($thumbnail) {
var random = randomString(10);
$thumbnail.attr('id', 'rand-' + random);
};
And then cloning it and moving that list element to the right:
var cloneImage = function($thumbnail) {
addUniqueId($thumbnail);
var $lastImgAdded = $('.js-gallery-edit-pane li').last();
var span = $('<span />');
span.addClass('x').text('x');
var $clone = $thumbnail.clone(true, true).appendTo($('.js-gallery-edit-pane')).prepend(span).hide().fadeIn(200).unbind('click');
bindRemoveHandler($clone);
};
Then I add an overlay to the list element on the left to "gray" it out. Everything works at this point.
From there, a user can remove the recently cloned items on the right hand side by clicking the "X" on the image. This works fine and removes that recently cloned image, however, the original overlay is not being found. It should be associated with the random string value, so I'm just looking for that in $('.js-gallery-select-pane').find('#rand-' + string).find('.overlay').remove(); but for some reason it's not finding it...
Any idea why?
JSFiddle Demo
If you put an alert(string) in your code, you will see that the string already includes rand- so in your selector just do:
$('.js-gallery-select-pane').find('#' + string).find('.overlay').remove();
Here is the working JSFiddle
You have two collections of elements with ID pairs in them.
There's three problems though. The first two problems problem are here..
You were taking the complete id of the clone 'rand-etctcetc'
then adding rand- to it again 'rand-rand-etcetcetc' then using it as a selector. $('rand-rand-etcetcetc'). Instead I changed it to just add the neccessary # to the id. You also need to remove the js-gallery-editing class in order to let you add things back to the list on right hand side.
var bindRemoveHandler = function($thumbnail) {
$thumbnail.on('click', function() {
$(this).fadeOut(200, function() {
var string = $(this).attr('id');
$('.js-gallery-select-pane').find('#' + string).removeClass('js-gallery-editing').find('.overlay').remove();
$(this).remove();
updatePictureCount();
});
});
};
You could stop here but, you also have a different problem. The ID attribute is intended to be unique. Try using a custom attribute and the Jquery attribute equals selector. The query you want would look something like this..
$('.js-gallery-select-pane').find('[pairid="' + string +'"]');
Here, have a fiddle: http://jsfiddle.net/s3dCB/