PrimeFaces Paginator JumpToPageDropdown Bug - datatable

I am migrating from PrimeFaces 6.2 to 8.0. In my project I am using multiple Primeface DataTables with a Paginator.
MCVE:
Create a data list with more than 10 elements. Then click through the different page numbers. When any page number bigger than 10 is clicked or selected from the JumpToPageDropdown the console will log the following error:
Error in Chrome console:
jquery.js.xhtml?ln=primefaces&v=8.0:2 Uncaught Error: Syntax error, unrecognized expression: option[value=\31 0]
at Function.se.error (jquery.js.xhtml?ln=primefaces&v=8.0:2)
at se.tokenize (jquery.js.xhtml?ln=primefaces&v=8.0:2)
at se.compile (jquery.js.xhtml?ln=primefaces&v=8.0:2)
at se.select (jquery.js.xhtml?ln=primefaces&v=8.0:2)
at se (jquery.js.xhtml?ln=primefaces&v=8.0:2)
at Function.se.matches (jquery.js.xhtml?ln=primefaces&v=8.0:2)
at Function.k.filter (jquery.js.xhtml?ln=primefaces&v=8.0:2)
at k.fn.init.k.fn.<computed> [as children] (jquery.js.xhtml?ln=primefaces&v=8.0:2)
at c.updateUI (components.js.xhtml?ln=primefaces&v=8.0:58)
at c.updateTotalRecords (components.js.xhtml?ln=primefaces&v=8.0:58)
Used component:
<p:dataTable id="datatable"value="#{backendController.dataList}"
var="data" paginator="true" rows="1" paginatorPosition="top"
pageLinks="20"
paginatorTemplate="{CurrentPageReport} {FirstPageLink} {PreviousPageLink} {PageLinks} {JumpToPageDropdown} {NextPageLink} {LastPageLink}">
-> Add a few <p:column> tags.
</p:dataTable>
Temporary fix:
I was able to identify that JumpToPageDropdown is causing the error. When this is removed from the paginatorTemplate the error will not be shown anymore when clicking on pages greater than 10. Unfortunately I want to use this dropdown.
Further research:
PrimeFaces uses these JS files for their components and hence also for the JumpToPageDropdown:
PrimeFaces 6.2: components.js.xhtml?ln=primefaces&v=6.2 PrimeFaces
PrimeFaces 8.0: components.js.xhtml?ln=primefaces&v=8.0
This are the respective calls to update the
Both versions:
this.jtpSelect = this.jq.children(".ui-paginator-jtp-select");
PrimeFaces 8.0:
this.jtpSelect.children("option[value=" + $.escapeSelector(this.cfg.page) + "]").prop("selected", "selected")
PrimeFaces 6.2:
this.jtpSelect.children("option[value=" + (this.cfg.page) + "]").prop("selected", "selected")
The difference is that in version 8.0 the value of this.cfg.page is passed to the function $.escapeSelector().
this.cfg.page uses zero-based numbering. Page 1 is 0, page 10 is 9 and so on. In version 6.2 this.jtpSelect.children() would be called with option[value=10] when the selected page is 11. But in version 8.0 $.escapeSelector() would format the same input to \31 0 and results in option[value=\31 0].
This in consequence leads to the error mentioned above. See this line: Uncaught Error: Syntax error, unrecognized expression: option[value=\31 0]
Recommendation:
If this is a bug $.escapeSelector() should be removed from this function in PrimeFaces 8.0, there is apparently no need for escaping: Numbers do not need to be escaped.

Solution:
After digging deep into the code I found a hack to fix the bug:
window.PrimeFaces.widget.Paginator.prototype.updateUI = function(){
if (this.cfg.page === 0) {
this.disableElement(this.firstLink);
this.disableElement(this.prevLink)
} else {
this.enableElement(this.firstLink);
this.enableElement(this.prevLink)
}
if (this.cfg.page === (this.cfg.pageCount - 1)) {
this.disableElement(this.nextLink);
this.disableElement(this.endLink)
} else {
this.enableElement(this.nextLink);
this.enableElement(this.endLink)
}
var a = (this.cfg.rowCount === 0) ? 0 : (this.cfg.page * this.cfg.rows) + 1
, c = (this.cfg.page * this.cfg.rows) + this.cfg.rows;
if (c > this.cfg.rowCount) {
c = this.cfg.rowCount
}
var e = this.cfg.currentPageTemplate.replace("{currentPage}", this.cfg.page + 1).replace("{totalPages}", this.cfg.pageCount).replace("{totalRecords}", this.cfg.rowCount).replace("{startRecord}", a).replace("{endRecord}", c);
this.currentReport.text(e);
if (this.cfg.prevRows !== this.cfg.rows) {
this.rppSelect.filter(":not(.ui-state-focus)").children("option").filter('option[value="' + $.escapeSelector(this.cfg.rows) + '"]').prop("selected", true);
this.cfg.prevRows = this.cfg.rows
}
if (this.jtpSelect.length > 0) {
if (this.jtpSelect[0].options.length != this.cfg.pageCount) {
var d = "";
for (var b = 0; b < this.cfg.pageCount; b++) {
d += '<option value="' + b + '">' + (b + 1) + "</option>"
}
this.jtpSelect.html(d)
}
this.jtpSelect.children("option[value=" + (this.cfg.page) + "]").prop("selected", "selected")
}
if (this.jtpInput.length > 0) {
this.jtpInput.val(this.cfg.page + 1)
}
this.updatePageLinks()
}
Explanation:
The function updateUI of the Paginator widget causes the error. It can be accessed by window.PrimeFaces.widget.Paginator.prototype.updateUI and then be overwritten with the corrected version.
The changed line in the hack:
this.jtpSelect.children("option[value=" + (this.cfg.page) + "]").prop("selected", "selected")
The original line in PrimeFaces 8.0:
this.jtpSelect.children("option[value=" + $.escapeSelector(this.cfg.page) + "]").prop("selected", "selected").

Related

p:selectOneRadio with required="true", if you submit not selected, does not turn its p:selectOneRadio to red

I have this p:selectOneRadio:
<p:selectOneRadio
value="#{bean.val}"
id="val"
layout="custom"
required="true"
>
<f:selectItem itemLabel="" itemValue="A" />
<f:selectItem itemLabel="" itemValue="B" />
</p:selectOneRadio>
and two p:radioButtons. If I submit the form without selecting any radio, the validation stops the submit, but p:radioButtons does NOT turn to red.
Indeed I inspected with the dev tools of the browser, and the radios has NO ui-state-error class.
It seems it's an error due to the old version of PrimeFaces I use. Can't I select the radio with a custom validator from UIComponent and set the class manually?
I'm using Primefaces 3.4.1 with Mojarra 2.1.7
It was fixed in PrimeFaces 6.2. See: https://github.com/primefaces/primefaces/issues/2464
OP is using PF 3.4 a 9 year old version.
I found a solution. In the submit button put:
onclick="setTimeout(function() { Util.checkCustomRadios(containersSel, endOfRadioNames); }, 350);"
This is Util.checkCustomRadios():
Util.checkCustomRadios = function (containersSelector, radioName) {
"use strict";
var errorClass = "ui-state-error";
var $containers = $(containersSelector);
var $container;
var $radios;
var radio;
var checked;
var $radioWrapper;
var j;
for (var i=0; i<$containers.length; i++) {
$container = $containers.eq(i);
$radios = $container.find('[name$="' + radioName + '"]:radio');
if (! $radios.length) {
continue;
}
checked = false;
for (j=0; j<$radios.length; j++) {
radio = $radios[j];
if (radio.checked) {
checked = true;
}
}
$radioWrapper = $container.find('.ui-radiobutton-box');
if (checked) {
$radioWrapper.removeClass(errorClass);
}
else {
$radioWrapper.addClass(errorClass);
}
}
};
containersSel could be the id of a single container or a selector of multiple containers, like #mytable tr.
endOfRadioNames is the last part of the name of the radios, before a colon.
Adjust the setTimeout value as you need :-)

Do not automatically expand all subgrid rows when clicking grouping column's expand icon

After grouping, is there a way for expand/collapse icon of current row not automatically expand/collapse all of the subgrid's rows? Just leave it alone as it was.
var parmColumnName = 'Model';
$('#test').jqGrid('groupingGroupBy'),
parmColumnName,
{
groupCollapse: true,
groupField: ['name']
}
);
//Original setup after playing around with it. (See X5 under BMW)
//Collapse the grouped Make
//Then Expand the grouped Make (All of the model are expanded by default, I do not want it to change and I want it to look like the original snapshot above)
I find your question very interesting, but the solution for the problem is not easy. In my opinion the source code of two jqGrid methods groupingRender and especially groupingToggle should be changed. The solution which I suggest you can see on the demo. The demo overwrites the original code of groupingRender and groupingToggle methods. More full description of my suggestions you will find below.
First of all I want to describe the problem in my words. You used the words "the subgrid's rows" in the text of your question which bring misunderstanding. What you use is multilevel grouping. The first problem in my opinion is the behavior of groupCollapse: true option. In case of multilevel grouping jqGrid collapse currently only data instead of all grouping headers till the top level. The demo uses 3-level grouping and the option groupCollapse: true. It dysplays
instead of intuitively expected
Another problem which you formulate in your question is the current behavior of expending. The problem is that if the user have collapsed the nodes to that all looks compact, like on the last picture which I posted, end then the user expand some node jqGrid expand all children grouping headers of the node till the data. So if one expand for example only "test1" node then all its children nodes will be expanded instead of expending only the next grouping level.
To fix the first problem (opened sub-grouping headers in spite of groupCollapse: true) I changed one line of groupingRender method from
str += "<tr id=\""+hid+"\" role=\"row\" class= \"ui-widget-content jqgroup ui-row-"+$t.p.direction+" "+clid+"\"><td style=\"padding-left:"+(n.idx * 12) + "px;"+"\" colspan=\""+colspans+"\">"+icon+$.jgrid.template(grp.groupText[n.idx], gv, n.cnt, n.summary)+"</td></tr>";
to
str += "<tr id=\""+hid+"\"" +(grp.groupCollapse && n.idx>0 ? " style=\"display:none;\" " : " ") + "role=\"row\" class= \"ui-widget-content jqgroup ui-row-"+$t.p.direction+" "+clid+"\"><td style=\"padding-left:"+(n.idx * 12) + "px;"+"\" colspan=\""+colspans+"\">"+icon+$.jgrid.template(grp.groupText[n.idx], gv, n.cnt, n.summary)+"</td></tr>";
The main problem which you asked was a little more difficult. Below you can find the fixed version of
$.jgrid.extend({
groupingToggle : function(hid){
this.each(function(){
var $t = this,
grp = $t.p.groupingView,
strpos = hid.split('_'),
uidpos,
//uid = hid.substring(0,strpos+1),
num = parseInt(strpos[strpos.length-2], 10);
strpos.splice(strpos.length-2,2);
var uid = strpos.join("_"),
minus = grp.minusicon,
plus = grp.plusicon,
tar = $("#"+$.jgrid.jqID(hid)),
r = tar.length ? tar[0].nextSibling : null,
tarspan = $("#"+$.jgrid.jqID(hid)+" span."+"tree-wrap-"+$t.p.direction),
getGroupingLevelFromClass = function (className) {
var nums = $.map(className.split(" "), function (item) {
if (item.substring(0, uid.length + 1) === uid + "_") {
return parseInt(item.substring(uid.length + 1), 10);
}
});
return nums.length > 0 ? nums[0] : undefined;
},
itemGroupingLevel,
collapsed = false, tspan;
if( tarspan.hasClass(minus) ) {
if(grp.showSummaryOnHide) {
if(r){
while(r) {
if($(r).hasClass('jqfoot') ) {
var lv = parseInt($(r).attr("jqfootlevel"),10);
if( lv <= num) {
break;
}
}
$(r).hide();
r = r.nextSibling;
}
}
} else {
if(r){
while(r) {
itemGroupingLevel = getGroupingLevelFromClass(r.className);
if (itemGroupingLevel !== undefined && itemGroupingLevel <= num) {
break;
}
$(r).hide();
r = r.nextSibling;
}
}
}
tarspan.removeClass(minus).addClass(plus);
collapsed = true;
} else {
if(r){
var showData = undefined;
while(r) {
itemGroupingLevel = getGroupingLevelFromClass(r.className);
if (showData === undefined) {
showData = itemGroupingLevel === undefined; // if the first row after the opening group is data row then show the data rows
}
if (itemGroupingLevel !== undefined) {
if (itemGroupingLevel <= num) {
break;// next item of the same lever are found
} else if (itemGroupingLevel === num + 1) {
$(r).show().find(">td>span."+"tree-wrap-"+$t.p.direction).removeClass(minus).addClass(plus);
}
} else if (showData) {
$(r).show();
}
r = r.nextSibling;
}
}
tarspan.removeClass(plus).addClass(minus);
}
$($t).triggerHandler("jqGridGroupingClickGroup", [hid , collapsed]);
if( $.isFunction($t.p.onClickGroup)) { $t.p.onClickGroup.call($t, hid , collapsed); }
});
return false;
},
});
The demo demonstrates the results of all changes which I suggest. I'll post the changes as pull request to trirand. I hope that the changes will be included in the main code of jqGrid.
UPDATED: I posted the pull request with the changes which I suggested above.
UPDATED 2: My pull request was merged with the main code of jqGrid. The new 4.5.4 version of jqGrid published today includes the changed. The new demo uses jqGrid 4.5.4 and it works like you expect. So to fix the problem which you described in your question you need just update jqGrid.

Using Find.BySelector with the :visible filter in WatiN throws error

I am using WatiN 2.1 to drive Internet Explorer for automated integration testing. For one of my tests, I want to select an item in a dynamically created popup menu after clicking on a button that creates the menu. The menu itself is a jQuery plug-in that creates an unordered list with a specific class.
I am using WatiN 2.0's new Find.BySelector method to search via this specific class and that works great. However, in tests where multiple menus are created, I'm having a difficult time selecting the menu that is visible at the time. For this, I thought I would use the :visible filter to limit my results to only those menus that are visible (only one can be visible at a time). However, when I use the following code:
WebBrowser.Current.ElementOfType<List>(Find.BySelector("li.fg-menu:visible"));
I get a WatiN.Core.Exceptions.RunScriptException thrown with the message: "System.Runtime.InteropServices.COMException: Exception from HRESULT: 0x80020101" While searching for this specific HRESULT, people have recommended running Visual Studio as an Administrator, but this does not fix the problem. Without the :visible filter it works fine. When I execute that selector directly in the console window of the browser (using jQuery), it returns what I want.
To fix this, I could use WatiN's built-in ability to execute my own JavaScript to return an element, but I'm wondering if anyone else has been successful at using :visible with Find.BySelector.
For now, I've gone with this solution. It executes the selector in JavaScript using a "real" copy of jQuery already on the page. I've modified it a bit to dynamically insert the helper JavaScript based on whether it's already been inserted. It is also generic so you can specify the type of Element you want returned. Here is all the relevant extension methods for the browser object:
public static bool IsJavascriptEvaluateTrue(this Browser browser, string javascript)
{
var evalResult = browser.Eval("if (" + javascript + ") {true;} else {false;}");
return evalResult == "true";
}
public static TElementType FindViaJQuery<TElementType>(this Browser browser, string cssSelector) where TElementType : Element
{
if (!browser.IsJavascriptEvaluateTrue("typeof('WatinSearchHelper') == 'function'"))
{
var js = "WatinSearchHelper = function () { " +
" var earTagId = 1; " +
" var getElementId = function (cssSelector) { " +
" var resultId = \"_no_element_\"; " +
" var el = $(cssSelector); " +
" if (el.length > 0) { " +
" var firstEl = el[0]; " +
" if (firstEl.id == \"\") { " +
" firstEl.id = \"_watin_search_\" + earTagId++; " +
" } " +
" resultId = firstEl.id; " +
" } " +
" return resultId; " +
" }; " +
" return { getElementId: getElementId }; " +
"} (); ";
browser.Eval(js);
}
var elementId = browser.Eval(string.Format("WatinSearchHelper.getElementId(\"{0}\")", cssSelector));
if (elementId == "_no_element_")
{
throw new ArgumentException("Unable to find element on page with selector '" + cssSelector + "'", "cssSelector");
}
return browser.ElementOfType<TElementType>(Find.ById(elementId));
}

JqGrid next button enabled for no values are returned

We have an issue when no values are returned from Jqgrid. In this case next button of pager (jqGrid) is enabled. We have to disable it when grid is empty. Please help me in finding a solution.
To set the next button as disabled you can do the following:
$('#next_' + yourgridid + '_pager').addClass('ui-state-disabled');
To re-enable the button do:
$('#next_' + yourgridid + '_pager').removeClass('ui-state-disabled');
I suggest doing this in the loadComplete(data) event.
$("#yourgridid").jqGrid({
...
loadComplete: function(data){
var records = $('#yourgridid').jqGrid('getGridParam', 'reccount');
//note: you can also get record count via data.records or data.rows.length in this function
if (records > 0) {
$('#next_' + yourgridid + '_pager').removeClass('ui-state-disabled');
} else {
$('#next_' + yourgridid + '_pager').addClass('ui-state-disabled');
}
},
...
});
loadComplete: function (data) {
var page = $('#mygrid').jqGrid('getGridParam', 'page');
var lastpage = $("#mygrid").getGridParam('lastpage');
//note: you can also get record count via data.records or data.rows.length in this function
if (lastpage > page) {
$('#next_mygridpager').removeClass('ui-state-disabled');
$('#last_mygridpager').removeClass('ui-state-disabled');
} else {
$('#next_mygridpager').addClass('ui-state-disabled');
$('#last_mygridpager').addClass('ui-state-disabled');
}
}
I believe this is a more complete solution because you also do not want the next and last button appear when there are no more pages to move forward.

<input type='textarea' onmousemove='toscheck()' /> isn't working only on Firefox

For some reason, this code works all chrome, safari, and ei but not on firefox.
<script type="text/javascript">
function toscheck(){
if(tos.scrollTop+540 > tos.scrollHeight){
alert(tos.scrollTop + " " + tos.scrollHeight);
}
}
</script>
----------
<textarea name="tos" id="tos" readonly="readonly" onmousemove="toscheck()">text</textarea>
Nothing's triggered on FF but works fine on all other browsers.
However, <textarea.. onmousemove=alert('test')>text</textarea> works fine.
I'm new to javascript so any help would be greatly appreciated.
That should actually not work in any browser. There is a closing bracket missing in your Javascript code:
<script type="text/javascript">
function toscheck() {
if (tos.scrollTop + 540 > tos.scrollHeight) {
alert(tos.scrollTop + " " + tos.scrollHeight);
}
}
</script>
Live example
Other than your missing } to close out your function it seems to work in FF for me.
<script type="text/javascript">
function toscheck(){
if(tos.scrollTop+540 > tos.scrollHeight){
alert(tos.scrollTop + " " + tos.scrollHeight);
}
} // <----- was missing
</script>
Also, in your function you directly go to tos.property.
You'll either need to pass this into the mousemove="toscheck(this)" and have your function setup like this:
<script type="text/javascript">
function toscheck(elem){
if(elem.scrollTop+540 > elem.scrollHeight){
alert(elem.scrollTop + " " + elem.scrollHeight);
}
}
</script>
Or get your textarea from within the function like this:
<script type="text/javascript">
function toscheck(){
var tos = document.getElementById('tos');
if(tos.scrollTop+540 > tos.scrollHeight){
alert(tos.scrollTop + " " + tos.scrollHeight);
}
}
</script>
Best solution, use jQuery and it's automatically cross-browser method or see this page regarding making that code cross-browser compatible due to differences between implementation in re:
Scrolling offset - how much the page has scrolled.
var x,y;
if (self.pageYOffset) // all except Explorer
{
x = self.pageXOffset;
y = self.pageYOffset;
}
else if (document.documentElement && document.documentElement.scrollTop)
// Explorer 6 Strict
{
x = document.documentElement.scrollLeft;
y = document.documentElement.scrollTop;
}
else if (document.body) // all other Explorers
{
x = document.body.scrollLeft;
y = document.body.scrollTop;
}
and
var x,y;
var test1 = document.body.scrollHeight;
var test2 = document.body.offsetHeight
if (test1 > test2) // all but Explorer Mac
{
x = document.body.scrollWidth;
y = document.body.scrollHeight;
}
else // Explorer Mac;
//would also work in Explorer 6 Strict, Mozilla and Safari
{
x = document.body.offsetWidth;
y = document.body.offsetHeight;
}
How big is your actual text box? Maybe it's rendered in a different size than you expect and/or the scrollheight is a little bit different? Maybe it's a font size/resolution/DPI setting issue. To try to solve it, add a small overlapping amount so you actually don't have to scroll to the absolute bottom (won't work in IE for example when using keyboard navigation and Ctrl + End).
if(tos.scrollTop + 565 > tos.scrollHeight){
On a side note, I wouldn't check this in onmousemove as people might use keyboard keys to navigate as well. I'd suggest using onblur and maybe onmouseup and/or onkeyup. onmouseup however might not fire if the mouse button is released while being on the scroll bar.

Resources