In Protractor, how does one handle repeated content, from say a table? For example, given the following code, that kicks out a table with 3 columns: Index, Name and Delete-Button in each row:
<table class="table table-striped">
<tr ng-repeat="row in rows | filter : search" ng-class="{'muted':isTemp($index)}">
<td>{{$index+1}}</td>
<td>{{row}}</td>
<td>
<button class="btn btn-danger btn-mini" ng-click="deleteRow(row)" ng-hide="isTemp($index)"><i class="icon-trash icon-white"></i></button>
</td>
</tr>
</table>
And in my test I need to click the delete button based on a given name. What's the best way to find this in Protractor?
I know I could grab the rows.colum({{row}}) text, get the index of that, and then click on the button[index], but I'm hoping for a more elegant solution.
For example, in Geb, you could pass a row locator to a module, that would then dice up each row with column indicators. And that solution has me eyeing Protractors map method...
You can use map or filter. The api would look like this:
var name = 'some name';
// This is like element.all(by.css(''))
return $$('.table.table-stripe tr').filter(function(row) {
// Get the second column's text.
return row.$$('td').get(2).getText().then(function(rowName) {
// Filter rows matching the name you are looking for.
return rowName === name;
});
}).then(function(rows) {
// This is an array. Find the button in the row and click on it.
rows[0].$('button').click();
});
http://angular.github.io/protractor/#/api?view=ElementArrayFinder.prototype.filter
Here is how I am doing something similar in my application using Protractor against a Kendo grid:
I have a page object that has the following functions:
// Query all table rows (tr) inside the kendo grid content container
this.getGrid = function () {
return element.all(by.css('.k-grid-content tr'));
};
// Use the given rows element finder to query for the delete button within the context of the row
this.getDeleteButtonInRow = function (row) {
return row.element(by.css(".btn.delete"));
};
Then I use these functions in my test like so:
// Verify that a delete button appears in every row of the grid
var grid = pageObj.getGrid();
grid.each(function (row) {
var deleteButton = downloadsPage.getDeleteButtonInRow(row);
expect(deleteButton.isDisplayed()).toBe(true);
});
Here's my solution, based on #Andres solution, that I used in my page object:
this.deleteFriend = function(nameString) {
return this.rows.filter(function(row) {
// find the row with the name we want...
return row.$$('td').get(1).getText().then(function(name) {
return name === nameString;
});
}).then(function(filteredRows) {
filteredRows[0].$('i.icon-trash').click();
});
};
Related
I have had a select control where I could select from user names. Now I want to convert that select into combo box so I can give the user ability to enter text as well. The values populated in the combo box are in text property as {FirstName}, {LastName}. I do not want to use additional text as it shows at end of the row wih lot of space in between. My issue with Combo box is :
Values get populated but how do I filter? There is already some logic that is written on change method. I want to do custom filtering on the values. For example: If I write "P" it should show all the values that have P in the text (First name and last name). Where to write filter function? Also I found custom filtering code in demokit, I want to use it - but when I use it in inti method under delegate, I get error - this.get....setfilterfunction().. is not a function
View.xml
<ComboBox items= "{path:'items>/name'}" id="A"
selectedKey="{item>/Header/Name}" change="nameSelected">
<core:ListItem key="{order>NameID}" text="{order>LastName} ,{order>FirstName}"/>
</ComboBox>
Controller.js
_initializeData: function () {
var name = this.getView().byId("A");
name.addEventDelegate({
onAfterRendering: function() {
this.getView().byId("A").setFilterFunction(function(sTerm, oItem) {
return oItem.getText().match(new RegExp(sTerm, "i")) ||
oItem.getKey().match(new RegExp(sTerm, "i"));
});
}
},
nameSelected: function () {
......some logic processing..
}
You have to bind this in your function. I guess.
Can you access this.getView().byId("A") , when you put a break point in the onAfterRendering part ?
Try this solution: You are not working in the right scope.
_initializeData: function () {
var name = this.getView().byId("A");
name.addEventDelegate({
onAfterRendering: this.setFilter.bind(this) });
},
setFilter: function() {
this.getView().byId("A").setFilterFunction(function(sTerm, oItem) {
return oItem.getText().match(new RegExp(sTerm, "i")) ||
oItem.getKey().match(new RegExp(sTerm, "i"));
});
},
I am trying to implement List in material-ui. And i am trying to display randomly generated array of elements, they are being displayed with scrollbar list as i want. But i want to select that particular List Item which is the issue as i m unable to select it, even by using on click. So can anyone help me in this.
Here is my code:
var MuiListElement = React.createClass(
{
handleClick() {
console.log("secondList clicked")
},
render()
{
let faker = require('faker')
let myItems = []
for(let i = 0; i < 5000; i++)
{
let name = faker.Name.findName()
myItems.push(<ListItem onClick={this.handleClick()} key={i.toString()}>{name}</ListItem>)
}
return(
<div style={{width:'400px'}}>
<Paper style={{maxHeight: 200, overflow: 'auto'}}>
<List selectable='true'>
{myItems}
</List>
</Paper>
</div>
)
}
}
)
this.handleClick() calls handleClick function during your render, you should call it when the user clicks the list item. You wan't to pass the function to onClick, not call the function.
myItems.push(
<ListItem
onClick={this.handleClick} //pass function, don't call function
key={i}> //note: you can pass key as number
{name}
</ListItem>)
Now let me predict the future, you would like to know what item was clicked. You can use bind.
myItems.push(
<ListItem
onClick={this.handleClick.bind(this, i)} //bind returns a function
key={i}>
{name}
</ListItem>)
now you know what was passed
handleClick(i) {
console.log("secondList clicked", i)
},
However, every time you render, you are creating a new function and will slightly degrade performance.
See this discussion,
React js onClick can't pass value to method
Let me know if you don't understand.
I am trying to build a filter for my portfolio website. Checkboxes that let you pick a technology (react, redux, jquery etc.) to display a piece of work(s) that contain(s) that/those technologies. So every time the user clicks on a box, I want to add the value (JavaScript, Redux, React etc.) to an array that I use in another function to check against my portfolio pieces and filter out what isn't there.
I am finding this very difficult and I think it should be quite simple. Can someone point me in the right direction? Is there a way to simply have a function trigger (onChange callback?) that reads the checked/unchecked status of my form input elements and then updates my state array accordingly? Can I get the status of all the checkboxes simply in React? Do I need to have individual state of checked/unchecked for my checkboxes?
It seems that jQuery makes it pretty possible with selectors with:
$('input[type="checkbox"]:checked').each(function () {}
If you don't care about the order and you just want to append the items to the array as they appear we could definitely do exactly what you suggest in your question. On the change event of the checkbox check if the box is checked or or unchecked (event.target.checked returns true if checked or false if unchecked) and handle the array logic accordingly. this is a simple representation of how that could work:
import React, { Component } from 'react'
import { connect } from 'react-redux'
class Portfolio extends Component {
constructor() {
super()
// initialize your options array on your state
this.state = {
options: []
}
}
onChange(e) {
// current array of options
const options = this.state.options
let index
// check if the check box is checked or unchecked
if (e.target.checked) {
// add the numerical value of the checkbox to options array
options.push(+e.target.value)
} else {
// or remove the value from the unchecked checkbox from the array
index = options.indexOf(+e.target.value)
options.splice(index, 1)
}
// update the state with the new array of options
this.setState({ options: options })
}
render() {
return (
<main className='portfolio'>
<form>
<div className="input-group">
<label>cb1</label>
<input type="checkbox" value={1} onChange={this.onChange.bind(this)} />
</div>
<div className="input-group">
<label>cb2</label>
<input type="checkbox" value={2} onChange={this.onChange.bind(this)} />
</div>
<div className="input-group">
<label>cb3</label>
<input type="checkbox" value={3} onChange={this.onChange.bind(this)} />
</div>
</form>
<div className="selected-items">
{this.state.options.map(number =>
<p key={number}>item: {number}</p>
)}
</div>
</main>
)
}
}
if you DO care about order, if you can append numerical values to the array like I did in this example you could easily give your checkboxes sorted numerical values and you could sort the array before updating your state so it's always in a certain order regardless of the order they are checked.
onChange(e) {
// current array of options
const options = this.state.options
let index
// check if the check box is checked or unchecked
if (e.target.checked) {
// add the numerical value of the checkbox to options array
options.push(+e.target.value)
} else {
// or remove the value from the unchecked checkbox from the array
index = options.indexOf(+e.target.value)
options.splice(index, 1)
}
// sort the array
options.sort()
// update the state with the new array of options
this.setState({ options: options })
}
Here's how I'm doing it:
// util.js
import getPath from 'lodash/get';
import setIn from 'lodash/fp/set';
export function linkMultiCheck(name, value) {
return {
checked: getPath(this.state, name, []).includes(value),
onChange: ev => {
let values = getPath(this.state, name, []);
if(ev.target.checked) {
values = [...values, value];
} else {
values = values.filter(v => v !== value);
}
this.setState(setIn(name, values));
},
}
}
// form.js
<ul>
{options.branches.map(branch => (
<li key={branch.id} className="checkbox">
<label>
<input type="checkbox" name={this.id} {...this::linkMultiCheck('formData.branchIds',branch.id)}/>
{branch.id}
</label>
</li>
))}
</ul>
i.e., if a checkbox is checked, append it to the current array of values. If it's unchecked, filter it out.
I'm using lodash here so that we can set deeply nested state values using dot notation.
I am able to do this using an ID prefix as the selector, but I need to be able to do it with classes instead. It's an each function for opening up different modal windows on the same page. I need to avoid using ID names because I have some modal windows that will have multiple links on the same page, and when using IDs, only the first link will work.
So here's the function as it works with IDs:
$('div[id^=ssfamodal-help-]').each(function() {
var sfx = this.id,
mdl = $(this),
lnk = $('.link-' + sfx),
cls = $('.ssfamodal-close'),
con = $('.ssfamodal-content');
lnk.click(function(){
mdl.show();
});
cls.click(function(){
mdl.hide();
});
mdl.click(function() {
mdl.hide();
});
con.click(function() {
return false;
});
});
and I'm trying to change it to classes instead, like:
$('div[class^=ssfamodal-help-]').each(function() {
var sfx = this.attr('class'),
etc.
But I cannot get it to work without using IDs. Is it possible?
EDIT Fixed error with semi-colon at end of Vars, and updated Fiddle with the fix. Still not working though.
Here's a Fiddle
** UPDATE **
To be clearer, I need to be able to refer to the same modal more than once on the same page. E.g.:
MODAL 1
MODAL 2
MODAL 3
MODAL 4
LINK TO MODAL 1
LINK TO MODAL 2
LINK TO MODAL 3
LINK TO MODAL 4
OTHER STUFF
LINK TO MODAL 1
LINK TO MODAL 4
LINK TO MODAL 3
OTHER STUFF
LINK TO MODAL 2
ETC.
When using classes get rid of the ID habit :
className1, className2, className3 ... etc
simply use
className
HTML:
<div class="ssfamodal-help-base ssfamodal-backdrop">
<div id="help-content" class="ssfamodal-content">
<span class="ssfamodal-close">[x]</span>
Howdy
</div>
</div>
<div class="ssfamodal-help-base ssfamodal-backdrop">
<div id="help-content" class="ssfamodal-content">
<span class="ssfamodal-close">[x]</span>
Howdy Ho
</div>
</div>
<span class="link-ssfamodal-help-base">One</span>
<span class="link-ssfamodal-help-base">Two</span>
LIVE DEMO
var $btn = $('.link-ssfamodal-help-base'),
$mod = $('.ssfamodal-help-base'),
$X = $('.ssfamodal-close');
$btn.click(function(i) {
var i = $('[class^="link-"]').index(this); // all .link-** but get the index of this!
// Why that?! cause if you only do:
// var i = $('.link-ssfamodal-help-base').index();
// you'll get // 2
// cause that element, inside a parent is the 3rd element
// but retargeting it's index using $('className').index(this);
// you'll get the correct index for that class name!
$('.ssfamodal-help-base').eq(i).show() // Show the referenced element by .eq()
.siblings('.ssfamodal-help-base').hide(); // hide all other elements (with same class)
});
$X.click(function(){
$(this).closest('.ssfamodal-help-base').hide();
});
From the DOCS:
http://api.jquery.com/eq/
http://api.jquery.com/index/
http://api.jquery.com/closest/
Here I created a quite basic example on how you can create a jQuery plugin of your own to handle modals: http://jsbin.com/ulUPIje/1/edit
feel free to use and abuse.
The problem is that class attributes can consist of many classes, rather than IDs which only have one value. One solution, which isn't exactly clean, but seems to work is the following.
$('div').filter(function () {
var classes = $(this).attr('class').split(/\s+/);
for (var i = 0; i < classes.length; i++)
if (classes[i].indexOf('ssfamodal-help-') == 0)
return true;
return false;
}).each(function() {
// code
});
jsFiddle
Or, equivalently
$('div').filter(function () {
return $(this).attr('class').split(/\s+/).some(function (e) {
return e.indexOf('ssfamodal-help-') == 0;
});
}).each(function() {
// code
});
jsFiddle
If there is one-to-one relationship between the modal helps and the modal links which it appears there is...can simplfy needing to match class values by using indexing.
For this reason you don't need unique class names, rather they just overcomplicate things. Following assumes classes stay unique however
var $helps=$('div[id^=ssfamodal-help-]');
var $help_links=$('div[id^=link-ssfamodal-help-]');
$help_links.click(function(){
var linkIndex= $help_links.index(this);
$helps.hide().eq( linkIndex ).show();
});
/* not sure if this is what's wanted, but appeared original code had it*/
$helps.click(function(){
$(this).hide()
})
/* close buttons using traverse*/
$('.ssfamodal-close').click(function(){
$(this).closest('div[id^=ssfamodal-help-]' ).hide();
});
Also believe that this code is a little more readable than original apporach
DEMO
Can you try this,
$('div[class^=ssfamodal-help-]').each(function() {
var sfx = $(this).attr('class');
console.log(sfx);
/*console log:
ssfamodal-help-base ssfamodal-backdrop
ssfamodal-help-base2 ssfamodal-backdrop
*/
});
Demo: http://jsfiddle.net/xAssR/51/
why don't you write like
$('div.classname').each(function() {
// you can write your desired code here
var sfx = this.attr('class');
var aa= this.attr('id');
});
or
$('.classname').each(function() {
// you can write your desired code here
var sfx = this.attr('class');
var aa= this.attr('id');
});
where classname is the name of the class used for the div in html
Thanks.
I want to run JavaScript function just after user select a value using autocomplete textbox bootstrap Typeahead.
I'm searching for something like selected event.
$('.typeahead').on('typeahead:selected', function(evt, item) {
// do what you want with the item here
})
$('.typeahead').typeahead({
updater: function(item) {
// do what you want with the item here
return item;
}
})
For an explanation of the way typeahead works for what you want to do here, taking the following code example:
HTML input field:
<input type="text" id="my-input-field" value="" />
JavaScript code block:
$('#my-input-field').typeahead({
source: function (query, process) {
return $.get('json-page.json', { query: query }, function (data) {
return process(data.options);
});
},
updater: function(item) {
myOwnFunction(item);
var $fld = $('#my-input-field');
return item;
}
})
Explanation:
Your input field is set as a typeahead field with the first line: $('#my-input-field').typeahead(
When text is entered, it fires the source: option to fetch the JSON list and display it to the user.
If a user clicks an item (or selects it with the cursor keys and enter), it then runs the updater: option. Note that it hasn't yet updated the text field with the selected value.
You can grab the selected item using the item variable and do what you want with it, e.g. myOwnFunction(item).
I've included an example of creating a reference to the input field itself $fld, in case you want to do something with it. Note that you can't reference the field using $(this).
You must then include the line return item; within the updater: option so the input field is actually updated with the item variable.
first time i've posted an answer on here (plenty of times I've found an answer here though), so here's my contribution, hope it helps. You should be able to detect a change - try this:
function bob(result) {
alert('hi bob, you typed: '+ result);
}
$('#myTypeAhead').change(function(){
var result = $(this).val()
//call your function here
bob(result);
});
According to their documentation, the proper way of handling selected event is by using this event handler:
$('#selector').on('typeahead:select', function(evt, item) {
console.log(evt)
console.log(item)
// Your Code Here
})
What worked for me is below:
$('#someinput').typeahead({
source: ['test1', 'test2'],
afterSelect: function (item) {
// do what is needed with item
//and then, for example ,focus on some other control
$("#someelementID").focus();
}
});
I created an extension that includes that feature.
https://github.com/tcrosen/twitter-bootstrap-typeahead
source: function (query, process) {
return $.get(
url,
{ query: query },
function (data) {
limit: 10,
data = $.parseJSON(data);
return process(data);
}
);
},
afterSelect: function(item) {
$("#divId").val(item.id);
$("#divId").val(item.name);
}
Fully working example with some tricks. Assuming you are searching for trademarks and you want to get the selected trademark Id.
In your view MVC,
#Html.TextBoxFor(model => model.TrademarkName, new { id = "txtTrademarkName", #class = "form-control",
autocomplete = "off", dataprovide = "typeahead" })
#Html.HiddenFor(model => model.TrademarkId, new { id = "hdnTrademarkId" })
Html
<input type="text" id="txtTrademarkName" autocomplete="off" dataprovide="typeahead" class="form-control" value="" maxlength="100" />
<input type="hidden" id="hdnTrademarkId" />
In your JQuery,
$(document).ready(function () {
var trademarksHashMap = {};
var lastTrademarkNameChosen = "";
$("#txtTrademarkName").typeahead({
source: function (queryValue, process) {
// Although you receive queryValue,
// but the value is not accurate in case of cutting (Ctrl + X) the text from the text box.
// So, get the value from the input itself.
queryValue = $("#txtTrademarkName").val();
queryValue = queryValue.trim();// Trim to ignore spaces.
// If no text is entered, set the hidden value of TrademarkId to null and return.
if (queryValue.length === 0) {
$("#hdnTrademarkId").val(null);
return 0;
}
// If the entered text is the last chosen text, no need to search again.
if (lastTrademarkNameChosen === queryValue) {
return 0;
}
// Set the trademarkId to null as the entered text, doesn't match anything.
$("#hdnTrademarkId").val(null);
var url = "/areaname/controllername/SearchTrademarks";
var params = { trademarkName: queryValue };
// Your get method should return a limited set (for example: 10 records) that starts with {{queryValue}}.
// Return a list (of length 10) of object {id, text}.
return $.get(url, params, function (data) {
// Keeps the current displayed items in popup.
var trademarks = [];
// Loop through and push to the array.
$.each(data, function (i, item) {
var itemToDisplay = item.text;
trademarksHashMap[itemToDisplay] = item;
trademarks.push(itemToDisplay);
});
// Process the details and the popup will be shown with the limited set of data returned.
process(trademarks);
});
},
updater: function (itemToDisplay) {
// The user selectes a value using the mouse, now get the trademark id by the selected text.
var selectedTrademarkId = parseInt(trademarksHashMap[itemToDisplay].value);
$("#hdnTrademarkId").val(selectedTrademarkId);
// Save the last chosen text to prevent searching if the text not changed.
lastTrademarkNameChosen = itemToDisplay;
// return the text to be displayed inside the textbox.
return itemToDisplay;
}
});
});