how to handle combo boxes of ExtJS in selenium webdriver - extjs

Hi i have a ExtJS based UI. I have come to know that in ExtJS the combo box is not a real combo box but a combination of input text field, image of drop down box and a list. Now i am able to identify the control but i am stuck at selecting the value from the list. In the HTML source i see that the list is appearing as a seperate div and gets attached at the end of the source when we click on the drop down. find below the HTML source of the drop down control.
{
<div id="ext-gen678" class="x-form-field-wrap x-form-btn-plugin-wrap" style="width: 556px;">
<div id="ext-gen676" class="x-form-field-wrap x-form-field-trigger-wrap x-trigger-wrap-focus" style="width: 521px;">
<input id="ext-gen677" type="hidden" name="GHVOg:#concat#~inputFld~ISGP_UNIV:ft_t_isgp.prnt_iss_grp_oid:0" value="">
<input id="GHVOg:Mixh8:0" class="x-form-text x-form-field gs_dropDown_input gs_req x-form-invalid x-form-focus" type="text" autocomplete="off" size="24" style="width: 504px;">
<img id="trigger-GHVOg:Mixh8:0" class="x-form-trigger x-form-arrow-trigger" alt="" src="../../ext/resources/images/default/s.gif">
}
find below the HTML source of the drop down list:
<div id="ext-gen726" class="x-layer x-combo-list x-resizable-pinned" style="position: absolute; z-index: 12007; visibility: visible; left: 294px; top: 370px; width: 554px; height: 123px; font-size: 11px;">
<div id="ext-gen727" class="x-combo-list-inner" style="width: 554px; margin-bottom: 8px; height: 114px;">
<div class="x-combo-list-item"></div>
<div class="x-combo-list-item">12h Universe</div>
<div class="x-combo-list-item">1h Universe</div>
<div class="x-combo-list-item">24h Universe</div>
<div class="x-combo-list-item">2h Universe</div>
<div class="x-combo-list-item x-combo-selected">4h Universe</div>
Now i have problem selecting the value from the list as the div element of the list is not attached to the control.
Also please refer the screen shot, where i have multiple similar controls [Named "Add Security to Universe"]
In the screen shot you can see multiple drop downs [Add security to Universe] highlighted and all the drop downs have same value appearing in the list. so how can i identify these values from the drop down list.
I was wondering how ExtJS maintains mapping of the drop down div elements with the combo Box widget so that i could use the same logic for identifying the list. Can anyone tell me how can i go about doing this thing in selenium webdriver?

Did you notice that there will be only one visible x-combo-list on the page? (Let me know if you can open up two combo lists at the same time)
Therefore you only need to care about the visible one x-combo-list.
Css selector: .x-combo-list[style*='visibility: visible;'] .x-combo-list-item
Xpath: //*[contains(#class, 'x-combo-list') and contains(#style, 'visibility: visible;')]//*[contains(#class, 'x-combo-list-item')]
// untested java code, just for the logic
public void clickComboItem(WebElement input, String target) {
input.click(); // click input to pop up the combo list
List<WebElement> comboItems = driver.findElements(By.cssSelector(".x-combo-list[style*='visibility: visible;'] .x-combo-list-item"));
for (int i = 0; i <= comboItems.size(); i++) {
WebElement item = comboItems.get(i);
if (item.getText().eqauls(target)) {
item.click();
break;
}
}
}
// compilable C# version
public void ClickComboItem(IWebElement input, string target) {
input.Click();
IList<IWebElement> comboItems = driver.findElements(By.CssSelector(".x-combo-list[style*='visibility: visible;'] .x-combo-list-item"));
comboItems.First(item => item.Text.Trim() == target).Click();
}

What i can suggest is :
you catch all your inputs like :
List<WebElement> inputList = driver.findElements(By.cssSelector("input cssSelector")); // you must complete this cssSelector
WebElement input = inputList.get(0); // get the 1st input
input.click(); //click on the first input and the option list appears.
you catch all "options" like :
List<WebElement> optionList = driver.findElements(By.cssSelector(".x-combo-list-item")); // get all options
WebElement option = optionList.get(1);
option.click();
input.sendKeys(option.getText()); //getText() get the html inner value
This is just an example in Java and you can actually use a loop foreach if you want to automat this populate for all your inputs.

I use JavaScriptExecutor, my SelectRandomOption looks like this:
public void SelectRandomOption()
{
String randomOptionIndex = "Math.floor(Math.random()*Ext.getCmp('" + ExtJSIdOfComboBox + "').getStore().getCount()-1)";
String randomOptionValue = "Ext.getCmp('" + ExtJSIdOfComboBox + "').getStore().getAt(" + randomOptionIndex + ").getData()['model']";
String jsScript = "Ext.getCmp('" + ExtJSIdOfComboBox + "').setValue(" + randomOptionValue + ");";
js.ExecuteScript(jsScript);
}

I basically used the marked answer above however it needed a bit of adapting for Ext Js 4.1.
It's essentially the same approach but you need to look for a visible div marked with class "x-boundlist"
I used xpath and used queries that looked something like this:
.//div[#class[contains(.,'x-boundlist')]]
and then retrieve and click on an li matching your desired entry:
.//li[normalize-space(text())='combobox entry text']
I've put normalize-space in there as xpath seems to have real problems if you dont trim strings. That methods does a left + right trim and also removes duplicate spaces eg
' blah blah ' would change to 'blah blah'.

Related

Customizing Day Cell content in FullCalendar

I am using fullcalendar with react. I am trying to customize the dayGrid view. According to the Content Injection docs for react I can use custom content for the rendering of both the date and the header cells. The dayCellContent "hook" states that:
Generated content is inserted inside the inner-most wrapper of the day cell. It does not replace the cell.
I've provided an implementation for the dayCellContent and noticed that my content gets injected into the following structure:
<td class="fc-daygrid-day fc-day fc-day-wed fc-day-past rot_time-off_day-cell" data-date="2021-04-07">
<div class="fc-daygrid-day-frame fc-scrollgrid-sync-inner">
<div class="fc-daygrid-day-top">
<a class="fc-daygrid-day-number">
...custom content goes here
</a>
</div>
<div class="fc-daygrid-day-events"></div>
<div class="fc-daygrid-day-bg"></div>
</div>
</td>
Now, the problem is that this structure lets you insert content ONLY in the upper right corner of the date cell due to the positioning of the element. Furthermore, it is in an anchor element.
Example:
function renderDayCell(dayCellContent: DayCellContentArg) {
return (
<div>
{dayCellContent.dayNumberText}
</div>
);
}
Is there a clean way to customize the whole content of the cell somehow? I've seen a couple of sites using fullcalendar that have their content inserted directly into the td. Not sure if this is version dependent or they're using the alternative JS approach based on domNodes or html. I am using version 5.6.0 of fullcalendar.
I had the same requirement although not using React. I solved it using a manual manipulation of the DOM elements as suggested above. I have used jQuery for the select and manipulation. It is posted here in case anyone would like to see an example of how this can be achieved using DOM manipulation.
I implemented dayCellContent to make the day-cell DOM element easily identifiable by wrapping it in a span, with a unique id attribute based on the day of year number:
dayCellContent: function(info, create) {
const element = create('span', { id: "fc-day-span-"+info.date.getDayOfYear() }, info.dayNumberText);
return element;
},
This dayCellContent implementation makes no visible difference to the calendar but makes it easier to identify the elements to be modified in the DOM.
I then implemented dayCellDidMount to do the DOM manipulation by finding the appropriate cells and selecting their parent’s parent:
dayCellDidMount: function(info) {
let element = "<div style='position: absolute; left: 4px; top: 4px;'><a href='https://www.w3schools.com/'>TEST-"+info.dayNumberText+"</a></div>";
$('#fc-day-span-'+info.date.getDayOfYear()).parent().parent().prepend(element);
},
In this case I have just put a link to w3c in the top left of the cell with test text which also includes the day number. It results in cells that look like this:
Clearly the CSS could be improved and should be moved out to the CSS definitions but it illustrates the point.
Warning: This approach makes assumptions about the DOM structure that FullCalendar generates. The generated HTML may change in future versions of the product which could invalidate it. If you go this way then be careful when doing a FullCalendar update.
Note that the getDayOfYear function is from the ext-all.js library. Any way of uniquely identifying the day will work.
ngAfterViewInit(){
// Your CSS as text
var styles =.fc td, .fc th { vertical-align: top; padding: 0; height: 100px; } a{ color:#3d1cba; }
let styleSheet = document.createElement("style");
styleSheet.innerText = styles;
document.head.appendChild(styleSheet);
let arrTD = document.querySelectorAll('td.fc-timeline-slot');
let arrTR= document.querySelectorAll('td.fc-timeline-lane.fc-resource');
let arrInject= document.querySelectorAll('td.fc-timeline-lane.fc-resource>div.fc-timeline-lane-frame');
console.log(arrTR);
let k=-1;
arrTR.forEach(eachTR => {
let i=1;
let str = '';
k++;
let data_resource_id= eachTR.getAttribute('data-resource-id');
console.log(data_resource_id);
arrTD.forEach(eachTD => {
let k=100*(i-1);
i=i+1;
let data_date= eachTD.getAttribute('data-date');
console.log(data_date);
let data_resource_id= eachTR.getAttribute('data-resource-id');
console.log(data_resource_id);
str = str + '<span data-date="'+data_date+'" data-resource-id="'+data_resource_id+'" class="plus_icon" style="position:relative;top: 0px; left: '+k+'px !important;width:500px;height:500px;z-index:3;-moz-border-radius:100px;border:1px solid #ddd;-moz-box-shadow: 0px 0px 8px #fff;">+</span>';
});
arrInject[k].innerHTML=str;
});
let elementList = this.elRef.nativeElement.querySelectorAll('span.plus_icon');
for(let i=0;i<elementList.length;i++){
elementList[i].addEventListener('click', this.plusClick.bind(this));
}
}

Error: ngRepeat:dupes Duplicate Key in Repeater (need track by $index)

https://plnkr.co/edit/bpFi5WuojpNO2rh5vF3T?p=preview
See the README in the Plunker for the following explaination:
I would like the "INJECT NEW" button to create a blank input under
the one that was clicked, not at the end.
The reason they are getting added at the end is because of :
<div ng-repeat="problem in problems track by $index">
The track by $index is breaking the injection.
If I take out the track by $index then I get the error:
https://docs.angularjs.org/error/ngRepeat/dupes?p0=problem%20in%20problems&p1=object:171&p2={%22key%22:null,%22component%22:null,%22$$hashKey%22:%22object:171%22}
How can I have the inject functionality but not get the error?
Can change the method like below and can get it working :
$scope.addMotFault = function(idx) {
if ($scope.problems.length > 1) {
// Now more than one item, we need to
// inject the additional one under the clicked item
// this index + 1
problemPrototype.key = idx + 1;
$scope.problems.splice(idx + 1, 0, angular.copy(problemPrototype));
} else {
// Only one item, so just push new problem
// no need to "inject"
problemPrototype.key = 0;
$scope.problems.push(angular.copy(problemPrototype));
}
};
html:
<div ng-repeat="problem in problems" style="border: 1px #ccc solid; margin:5px; padding: 5px">
It would work i believe.
As per your question I am sending you the required answer.
Please find the attached link and go through the code. you will find the solution.
https://plnkr.co/edit/nhqAnD1hSKuIs9HTsT30?p=preview
you can use ng-if in place $first and can check on the basis on length.
<button ng-click="removeMotFault($index)" ng-if="problems.length > 1">REMOVE</button>

Strange behaviour of angular bootstrap collapse

I faced with strange behaviour of uib-collapse.
Let's assume I have a list of elements and i want each of them to be collapsed. Also i want to refresh its content periodically depend on something.
For example: i have some items and each of them have description which consists of some sections. I can pick item and description sections should be populated with item's description content. The problem is that each time i refresh its content, some sections are collapsing (despite the fact i set uib-collapse to false)
My controller:
var i = 0;
$scope.sections = [0,1,2];
$scope.next = function(nextOffset) {
i+=nextOffset;
$scope.sections = [i, i+1, i+2]
}
My template:
<button ng-click="next(1)" style="margin-bottom: 10px">Next item</button>
<button ng-click="next(2)" style="margin-bottom: 10px">Next next item</button>
<button ng-click="next(3)" style="margin-bottom: 10px">Next next next item</button>
<div ng-repeat="section in sections">
<div uib-collapse="false">
<div class="well well-lg">{{ section }}</div>
</div>
</div>
So when i click first button, only one section does transition. When i click second, 2 section do transition and click to third button leads to all section transition.
See plunkr
Any ideas?
UPD: if $scope.sections is array of object, not of primitives, then all sections have transition in each of 3 cases. It is so ugly...
You are not refreshing the existing content, you are adding new arrays each time, which will make ng-repeat remove the old DOM elements and insert new ones.
If you try with track by $index you will see the difference:
<div ng-repeat="section in primitiveSections track by $index">
Demo: http://plnkr.co/edit/hTsVBrRLa8nWXhaqfhVK?p=preview
Note that track by $index might not be the solution you want in your real application, I just used it for demonstration purposes.
What you probably need is to just modify the existing objects in the array.
For example:
$scope.nextObject = function(nextOffset) {
j += nextOffset;
$scope.objectSections.forEach(function (o, i) {
o.content = j + i;
});
};
Demo: http://plnkr.co/edit/STxy1lAUGnyxmKL7jYJH?p=preview
Update
From the collapse source code:
scope.$watch(attrs.uibCollapse, function(shouldCollapse) {
if (shouldCollapse) {
collapse();
} else {
expand();
}
});
When a new item is added the watch listener will execute, shouldCollapse will always be false in your case so it will execute the expand function.
The expand function will always perform the animation:
function expand() {
element.removeClass('collapse')
.addClass('collapsing')
.attr('aria-expanded', true)
.attr('aria-hidden', false);
if ($animateCss) {
$animateCss(element, {
addClass: 'in',
easing: 'ease',
to: {
height: element[0].scrollHeight + 'px'
}
}).start().finally(expandDone);
} else {
$animate.addClass(element, 'in', {
to: {
height: element[0].scrollHeight + 'px'
}
}).then(expandDone);
}
}
If this is the intended behavior or not I don't know, but this is the reason why it happens.
this is a comment on the original ui-bootstrap library: (and the new uib prefixed directive doesn't comply this comment.)
// IMPORTANT: The height must be set before adding "collapsing" class.
Otherwise, the browser attempts to animate from height 0 (in
collapsing class) to the given height here.
use the deprecated "collapse" directive instead of new "uib-collapse" until it gets fixed.

AngularJS : why after loading more data filter stop working?

there is one filter functionality in my demo I will explain my problem I have one table in which i use infinite scroll is implemented In other words when user moves to bottom it load more data.There is search input field in top .Using this I am able to filter item in table .but I don't know why it is not working
When you search "ubs" and "ing" first time .it works perfectly .But when you load more data other words when user scroll to bottom and load more data the again it try to filter "ubs" and "ing" it not give any result why ?
<label class="item item-input">
<img src="https://dl.dropboxusercontent.com/s/n2s5u9eifp3y2rz/search_icon.png?dl=0">
<input type="text" placeholder="Search" ng-model="query">
</label>
secondly Actually I am implementing infinite scroll so only 100 element display .can we search element from 2000 (which I am getting from service )and display data the search result ?
Update :
Here's a Plunker with everything working together. I have separated all of the pieces into individual JS files, as it was getting unruly:
Plunker
Search
The built in filter will only return results from the current view data that the ng-repeat is displaying. Because you're not loading all of the data into the view at once, you'll have to create your own search functionality.
In the demo, click the search icon to show the search box, then type your search value and press the ENTER key or click the search button to return the results.
Since you want to check whether the user pressed ENTER you have to pass both the event and the querystring to the function, so you can check for the enter keycode. The function should also run when someone clicks or taps the search button. I set ng-model="query" on the input, so query is the reference in the view. Therefore, you'll add ng-click="searchInvoices($event, query)" to your search button, and ng-keyup="searchInvoices($event, query)" to the input. And, finally, to make it easy to clear the input field, add a button that displays when the input is not empty with ng-show="query" and attach a click event with ng-click="query=null; resetGrid()".
Add the searchInvoices function to your controller. It will only run the search if either the query is empty (because you need to reset the view if the person uses the backspace key to empty the input) OR if the user pressed ENTER OR if the event was a click event in case the user clicks the search button. The inner if statement, prevents the search from running if the query is empty and just resets the view. If the query is not empty, against the total dataset and builds an array of matching results, which is used to update the view.
The last line sets the scroll position to the top of the scrollview container. This makes sure that the user sees the results without having to click somewhere in the scrollview container. Make sure you inject the $ionicScrollDelegate into your controller for this to work and set delegate-handle="invoicegrid" on your ion-scroll directive.
$scope.searchInvoices = function(evt, queryval) {
if (queryval.length === 0 || evt.keyCode === 13 || evt.type === 'click') {
if (queryval.length === 0) {
$scope.invoice_records = $scope.total_invoice_records;
} else {
var recordset = $scope.total_invoice_records;
results = [];
var recordsetLength = recordset.length;
var searchVal = queryval.toLowerCase();
var i, j;
for (i = 0; i < recordsetLength; i++) {
var record = recordset[i].columns;
for (j = 0; j < record.length; j++) {
var invoice = record[j].value.toLowerCase();
if (invoice.indexOf(searchVal) >= 0) {
results.push(recordset[i]);
}
}
}
$scope.invoice_records = results;
$ionicScrollDelegate.$getByHandle('invoicegrid').scrollTop();
}
}
};
Lastly, you need to modify the loadMore() function that is used by the infinite scroll directive, so that it doesn't try to load additional data when scrolling through the search results. To do this, you can just pass the query into loadMore on the directive like: on-infinite="loadMore(query)", then in your function, you can just run the broadcast event when the query exists. Also, removing the ngIf will ensure that the list remains dynamic.
$scope.loadMore = function(query) {
if (query || counter >= $scope.total_invoice_records.length) {
$scope.$broadcast('scroll.infiniteScrollComplete');
} else {
$scope.counter = $scope.counter + showitems;
$scope.$broadcast('scroll.infiniteScrollComplete');
}
};
You used filter in wrong way inside ng-repeat like ng-repeat="column in invoice_records | filter:query" instead of ng-repeat="column in invoice_records | query"
<div class="row" ng-repeat="column in invoice_records |filter:query">
<div class="col col-center brd collapse-sm" ng-repeat="field in column.columns" ng-show="data[$index].checked && data[$index].fieldNameOrPath===field.fieldNameOrPath">{{field.value}}</div>
<div class="col col-10 text-center brd collapse-sm"></div>
</div>
Demo Plunkr

tinymce table on textarea. Tab key doesnt move cursor to next cell

I am using TinyMCE plugin on textarea. As i insert table with particular rows and columns, it creates the table. But when the cursor is focused on one cell and on pressing TAB, the cursor wont move to the next cell until i start typing and the cursor is then visible.
this issue happens only on IE9 and works fine on FireFox.
Kindly throw some light upon this issue.
Below is my textarea.
<textarea id="${docAnnotationAttrId}" name="docAnnotation" title="${attribute.description}"
rows="22" cols="80" style="width: 100%; height: 360px"
class="tinymce" readonly=true><c:out value="${attrMap[attrKey].value}"/></textarea>
<script>
var options = {'elementName' : '${docAnnotationAttrId}', 'incidentId' : '${document_uniqueReference.incidentId}',
'contextPath' : '${sharedContextPath}','dictionaries':'${webProperties.tinymceDictionaries}'};
$('#${docAnnotationAttrId}').edit(options);
</script>
I have the same problem, See me code below which i use to solved it.
function handleMyTinyCMETabEventOnChromeIE(){
tinymce.activeEditor.execCommand('mceInsertContent', false, " ");
$('#tabcontrol').blur();
tinymce.activeEditor.execCommand('mceInsertContent', false, "");
return false;
}
...
<textarea id="elm1" name="elm1"></textarea>
<a id='tabcontrol' onfocus="javascript: handleMyTinyCMETabEventOnChromeIE()" tabindex='0' href="#"></a>

Resources