LWC Pagination with page number - pagination

I'm a junior developer, I need some help to implement an lwc component which display records in experience site so I need to implement pagination since we have a large number to record.
I implement the 2 buttons "Previous" and "Next" it works fine, but I need the a clickable number o page between the 2 buttons here is the mockup : something like :<- 1 2 3 4 ... ->
here is my code:
<template>
<div class="pastcfps"><br>
<table>
<thead>
<tr>
<th class="pastcfpstitle"></th>
<th class="fullProposalDeadlineTitle"><a data-id="Proposal_Submission_Deadline__c"
onclick={sort}>Closing Date<img src={sortArrowUp} data-id="sortIconFp"></a></th>
</tr>
</thead>
<tbody>
<template if:true={pastcfps}>
<template for:each={pastcfps} for:item="pastcfp">
<tr class="pastcfpstr" key={pastcfp.Id}>
<td key={pastcfp.Id} class="pastcfpstitle">
<p class="pastcfpsRecordType">{pastcfp.RecordType.Name}</p>
<a class="pastcfpsname" data-id={pastcfp.Id} onclick={redirect} is-loading={tableLoadingState}>{pastcfp.Name}</a>
</td>
<td key={pastcfp.Id} class="fullProposalDeadline">{pastcfp.Proposal_Submission_Deadline__c}
</td>
</tr>
</template>
</template>
</tbody>
<template if:true={error}>
<p class="pastcfpsname">There are no past Calls for Proposals</p>
</template>
</table>
</div>
<div style="text-align:center;">
<c-pagination-navigation onprevious={handlePrev} onnext={handleNext}></c-pagination-navigation>
</div>
</template>
import { LightningElement, api, wire, track } from 'lwc';
import getPastCfPs from '#salesforce/apex/CallsForProposalsService.getPastCfPs';
import ARROW_LOGO from '#salesforce/contentAssetUrl/homearrow';
import SORT_ARROW_UP from '#salesforce/resourceUrl/sortArrowUp';
import SORT_ARROW_DOWN from '#salesforce/resourceUrl/sortArrowDown';
export default class RecentOpenCallsForProposals extends LightningElement {
arrowLogo = ARROW_LOGO;
sortArrowUp = SORT_ARROW_UP;
sortArrowDown = SORT_ARROW_DOWN;
sortedDirection = 'asc';
sortedColumn;
#track tableLoadingState = false;
#track pastcfps;
#track error
#track offset=0;
#track Prevoffset=0;
limit = 12;
#wire(getPastCfPs,{ offset: '$offset', l : '$limit' }) wiredCfps({ data, error }) {
this.tableLoadingState = false;
if (data) {
this.pastcfps = data;
console.log(' type of data =====| ' + typeof data);
console.log(' type of data 0 =====| ' + typeof data[0]);
console.log(' type of data =====| ' + typeof this.pastcfps);
console.log(' type of data 0 =====| ' + typeof this.pastcfps[0]);
let dataString = JSON.stringify(data[0]);
console.log(' dataString ========| ' + dataString);
console.log(' Name ========| ' + data[0].Name);
console.log(' Status__c ========| ' + data[0].Status__c);
console.log(' CreatedDate ========| ' + data[0].CreatedDate);
console.log(' Proposal_Submission_Deadline__c ========| ' + data[0].Proposal_Submission_Deadline__c);
this.error = undefined;
if(this.pastcfps.length == 0)
this.offset= this.Prevoffset;
} else if (error) {
this.error = error;
this.pastcfps = undefined;
}
}
handlePrev (_event) {
//window.clearTimeout(this.delayTimeout);
if(this.offset - this.limit >=0)
{
this.tableLoadingState = true;
this.Prevoffset=this.offset;
this.offset = this.offset - this.limit;
}
}
handleNext (_event) {
//window.clearTimeout(this.delayTimeout);
this.tableLoadingState = true;
this.Prevoffset=this.offset;
this.offset = this.offset + this.limit;
}
redirect(event) {
var id = event.currentTarget.dataset.id;
if (id) {
window.location = 'call-for-proposal/' + id;
}
}
sort(e) {
if (this.sortedColumn === e.currentTarget.dataset.id) {
this.sortedDirection = this.sortedDirection === 'asc' ? 'desc' : 'asc';
} else {
this.sortedDirection = 'asc';
}
let table = JSON.parse(JSON.stringify(this.pastcfps));
var reverse = this.sortedDirection === 'asc' ? 1 : -1;
try {
table.sort((a, b) => { return new Date(a[e.currentTarget.dataset.id]) > new Date(b[e.currentTarget.dataset.id]) ? 1 * reverse : -1 * reverse });
} catch (error) {
console.log(error);
}
this.sortedColumn = e.currentTarget.dataset.id;
this.pastcfps = table;
if (e.currentTarget.dataset.id == 'Proposal_Submission_Deadline__c') {
let existingIcon = this.template.querySelectorAll('img[data-id="sortIconFp"]');
if (existingIcon[0]) {
existingIcon[0].parentNode.removeChild(existingIcon[0]);
}
let nodes = this.template.querySelectorAll('a[data-id="' + e.currentTarget.dataset.id + '"]');
var icon = document.createElement('IMG');
if (this.sortedDirection === 'asc') { icon.setAttribute('src', this.sortArrowUp); }
if (this.sortedDirection === 'desc') { icon.setAttribute('src', this.sortArrowDown); }
icon.setAttribute('data-id', 'sortIconFp');
if (nodes[0]) { nodes[0].appendChild(icon); }
}
}
}
#AuraEnabled(cacheable=true)
public static List<Call_for_Proposal__c> getPastCfPs(Integer offset, Integer l){
String recordTypeName = [SELECT Id, Name, Status__c, CreatedDate, RecordTypeId, RecordType.Name, Proposal_Submission_Deadline__c FROM Call_for_Proposal__c WHERE Status__c = 'Closed' ].get(0).RecordType.Name;
system.debug('recordTypeName === ' + recordTypeName);
return [SELECT Id, Name, Status__c, CreatedDate, RecordTypeId, RecordType.Name, Proposal_Submission_Deadline__c FROM Call_for_Proposal__c WHERE Status__c = 'Closed' limit :l offset :offset];
}

Related

Update cell update without scroll to top

I have an issue where on updating a cell with cellEdited, The page jumps back to the top of the page when the mutator or formatter is called.
I've tried setting table.redraw(false) and then the formatter/mutator doesn't seem to do anything.
When a user updates the proficiency level the formatter or mutator should add a button if it's above 3. It works perfectly. However it jumps to the top of the screen.
Any help appreciated.
var evidenceMutator = function(value, data, type, params, component){
skillsTable.redraw(false);
var claimedProficiency = data.Proficiency;
var skillClaimed = data.SkillID;
var Evidence = data.Evidence;
var memberID = $("#user").data("memberid");
if (claimedProficiency >= 3 ) {
// has provided evidence
if (Evidence == 1) {
return '<a class="btn btn-outline-success btn-xs btn-slim evidence" href="#" id="'+ skillClaimed + '" onclick="openEvidenceModal(this, this.id, '+ memberID +')"> <i class="fas fa-check-circle cssover"></i> Edit Evidence </a>';
} else { // needs to provide
return '<a class="btn btn-outline-danger btn-xs btn-slim evidence" href="#" id="'+ skillClaimed + '" onclick="openEvidenceModal(this, this.id, '+ memberID +')"> <i class="fas fa-times-circle cssover"></i> Add Evidence </a>';
}
} else {
// cell.getElement().style.color = "#CCD1D1";
//cell.getElement().style.fontStyle ="italic";
// cell.getElement().style.fontSize = "0.75em";
return ' No Evidence Needed';
}
}
function complianceTick(cell) {
var isCompliant = cell.getData().Compliance;
var Evidence = cell.getData().Evidence;
var claimedProficiency = cell.getData().Proficiency;
// style all with sucesss and only change if we need to
cell.getElement().style.color = "#33CC66";
cell.getElement().style.fontSize = "0.85em";
cell.getElement().style.fontStyle = "italic";
if (claimedProficiency >= 3 ) {
if (Evidence == '1') {
return '<i class="fas fa-check-circle"></i>';
} else {
cell.getElement().style.color = "#FF0000";
cell.getElement().style.fontSize = "0.85em";
cell.getElement().style.fontWeight = "bold";
return '<i class="fas fa-times-circle"></i>';
}
} else {
return '';
}
}
var skillsTable = new Tabulator("#SkillsTable", {
index: "SkillID",
height:"100%",
headerVisible:false,
autoResize:true,
layout:"fitColumns", //fit columns to width of table
groupBy:"SkillCatID",
groupHeader: groupMaker,
initialSort:[ //set the initial sort order of the data
{column:"RowOrder", dir:"asc"}
],
columns:[ //define the table columns
{title:"", field:"SkillID", visible:false},
{title:"Skill", field:"SkillName", visible:true, headerSort:false, formatter: skillName},
{title:"", field:"SkillCatID", visible:false},
{title:"", field:"SkillCatName", visible:false},
{title:"My Level", field:"Proficiency", visible:true, editor:inputEditor, formatter:prof, width:"7%", headerSort:false, align:"center",
cellEdited:function(cell, row, success){ // EDITING FUNCTION
var $value = cell.getValue();
var $field = cell.getField();
var $row = cell.getRow();
var $id = $row.getData().SkillID;
var integer = parseInt($value, 10);
if ($value != "" && integer < 5 && integer >= 0) {
// update record
updateRecord($id, $field, $value, $row);
//$row.update({"Proficiency" : $value});
//$row.update({"Evidencer" : $value});
skillsTable.updateRow($id, {id:$id,"Proficiency":$value});
skillsTable.updateRow($id, {id:$id,"Evidencer":$value});
} else {
cell.restoreOldValue();
if ($value != "") {
alert ("Values should be between 0 and 4, the cell has been restored to its previous value.")
}
}
},
},
{title:"Target", field:"MinLevel", visible:false, headerSort:false},
{title:"", field:"Proficiency", visible:true, formatter: skillDec, headerSort:false},
{title:"Evidence", field:"Evidencer", visible:true, hozAlign:"center", formatter: evidenceCell, mutator: evidenceMutator, width:"10.5%", headerSort:false},
{title:"", field:"RowOrder", visible:false, sorter:"number"},
{title:"disc", field:"DisciplineID", visible:false, headerFilter:true}
],
});
UPDATE
I think I've narrowed down the issue to the formatter:
```
function evidenceCell(cell, formatterParms, onRendered) {
var claimedProficiency = cell.getData().Proficiency;
var skillClaimed = cell.getData().SkillID;
var Evidence = cell.getData().Evidence;
var memberID = $("#user").data("memberid");
if (claimedProficiency >= 3 ) {
// has provided evidence
if (Evidence == 1) {
return '<a class="btn btn-outline-success btn-xs btn-slim evidence" href="#" id="'+ skillClaimed + '" onclick="openEvidenceModal(this, this.id, '+ memberID +')"> <i class="fas fa-check-circle cssover"></i> Edit Evidence </a>';
} else { // needs to provide
return '<a class="btn btn-outline-danger btn-xs btn-slim evidence" href="#" id="'+ skillClaimed + '" onclick="openEvidenceModal(this, this.id, '+ memberID +')"> <i class="fas fa-times-circle cssover"></i> Add Evidence </a>';
}
} else {
cell.getElement().style.color = "#CCD1D1";
cell.getElement().style.fontStyle ="italic";
cell.getElement().style.fontSize = "0.75em";
return 'No Evidence Needed';
}
}
```
It looks like the issue is with the cell.getData(). It seems to trigger the scroll to the top of page. I think it may be a bug with the newer version. Testing 4.2 and it seems to not be an issue. However, I have issues with modals going to the top of the page with the older version.
The issue seems to be with the virtual DOM. In my case I can luckily turn it off by using the following:
virtualDom:false,
I'm not sure this would be a good fix for people with hundreds of rows.

_isRowLoaded and _loadMoreRows not getting called react virtualized

My _loadMoreRows and _isRowLoaded are not getting called, so loadedRowsMap remains empty and I am unable to identify the rows loaded to avoid making HTTP request .
Here's my complete code :
import React, { Component } from 'react';
import { connect } from 'react-redux';
import {formatDate} from '../../helper/date';
import { recentActivitiAction } from '../actions/dashboardAction';
import { BeatLoader } from 'react-spinners';
import {AutoSizer, List, CellMeasurer, InfiniteLoader, CellMeasurerCache} from 'react-virtualized';
import styles from '../../css/AutoSizer.module.css';
import Skeleton from 'react-skeleton-loader';
const STATUS_LOADING = 1;
const STATUS_LOADED = 2;
const mapStateToProps = (state) => {
return {
recentActList: state.dashboardReducer.recentActList,
activitiLoading: state.dashboardReducer.activitiLoading
}
}
const mapDispatchToProps = (dispatch) => {
return {
getRecentActivites: (postData, callback) => {
dispatch(recentActivitiAction(postData, callback));
}
};
}
class RecentActivitiComp extends Component {
constructor(props) {
super(props);
this.state = {
loadedRowCount: 0,
loadedRowsMap: {},
loadingRowCount: 0,
};
this.cache = new CellMeasurerCache({
fixedWidth: true,
defaultHeight: 100
});
this._timeoutIdMap = {};
this._isRowLoaded = this._isRowLoaded.bind(this);
this._loadMoreRows = this._loadMoreRows.bind(this);
this.renderRow = this.renderRow.bind(this);
this.onRowsRendered = this.onRowsRendered.bind(this);
this.noRowsRenderer = this.noRowsRenderer.bind(this);
}
componentWillUnmount() {
Object.keys(this._timeoutIdMap).forEach(timeoutId => {
clearTimeout(timeoutId);
});
}
componentDidMount() {
var postData = {
"userName": "admin",
"queryType": "GET_RECENT_PROJECTS",
"data": {
pageStart: 1,
pageEnd: 20
}
};
this.props.getRecentActivites(postData, this.recentActResponse.bind(this));
}
updateDimensions() {
this.cache.clearAll();
this.activitiList.recomputeRowHeights();
}
recentActResponse(response) {
if (response.status === "FAILED") {
// handle error
}
}
_fieldGenerator(row, index) {
var formattedDate = formatDate(row.lastModified),
output = '', JSX = '';
if(formattedDate) {
formattedDate = formattedDate.split('-');
output = (
<div className="project-info-value byline">
<span>{formattedDate[0]}<sup>{formattedDate[1]}</sup> {formattedDate[2]} {formattedDate[3]}</span> by <a>{row.modifiedBy}</a>
</div>
)
} else {
output = (
<div className="project-info-value byline">
<span>Invalid Date by </span> <a>{row.modifiedBy}</a>
</div>
)
}
if(row.action === "upcoming-release") {
JSX =
<li key={index}>
<div className="block">
<div className="block_content">
<h2 className="title">{row.action}</h2>
{output}
<p className="excerpt">{row.notes}<a> Read More</a></p>
</div>
</div>
</li>
} else if(row.action === "created") {
JSX =
<li key={index}>
<div className="block">
<div className="block_content">
<h2 className="title">{row.type} <a>{row.name}</a> {row.action}</h2>
{output}
<p className="excerpt"></p>
</div>
</div>
</li>
} else if(row.action === "modified") {
JSX =
<li key={index}>
<div className="block">
<div className="block_content">
<h2 className="title">{row.type} <a>{row.name}</a> {row.action}</h2>
{output}
<p className="excerpt"></p>
</div>
</div>
</li>
} else {
JSX =
<li key={index}>
<div className="block">
<div className="block_content">
<h2 className="title"><a>{row.person}</a> added to <a>{row.addedTo}</a></h2>
{output}
<p className="excerpt"></p>
</div>
</div>
</li>
}
return JSX;
}
renderRow({ index, key, style, parent }) {
var JSX = '', content = '';
const list = this.props.recentActList
const {loadedRowsMap} = this.state;
if (loadedRowsMap[index] === STATUS_LOADED) {
const row = list[index];
JSX = this._fieldGenerator(row, index);
content = (
JSX
);
} else {
content = (
<div className={styles.placeholder} style={{width: 480}} />
);
}
return (
<CellMeasurer
cache={this.cache}
columnIndex={0}
key={key}
parent={parent}
rowIndex={index}
>
{({ measure }) => (
<div key={key} style={{...style}} onLoad={measure}>
{content}
</div>
)}
</CellMeasurer>
);
}
_isRowLoaded({index}) {
const {loadedRowsMap} = this.state;
return !!loadedRowsMap[index]; // STATUS_LOADING or STATUS_LOADED
}
_loadMoreRows({startIndex, stopIndex}) {
const {loadedRowsMap, loadingRowCount} = this.state;
const increment = stopIndex - startIndex + 1;
for (var i = startIndex; i <= stopIndex; i++) {
loadedRowsMap[i] = STATUS_LOADING;
}
this.setState({
loadingRowCount: loadingRowCount + increment,
});
const timeoutId = setTimeout(() => {
const {loadedRowCount, loadingRowCount} = this.state;
delete this._timeoutIdMap[timeoutId];
for (var i = startIndex; i <= stopIndex; i++) {
loadedRowsMap[i] = STATUS_LOADED;
}
this.setState({
loadingRowCount: loadingRowCount - increment,
loadedRowCount: loadedRowCount + increment,
});
promiseResolver();
}, 1000 + Math.round(Math.random() * 2000));
this._timeoutIdMap[timeoutId] = true;
let promiseResolver;
return new Promise(resolve => {
promiseResolver = resolve;
});
}
noRowsRenderer() {
return <div className={styles.noRows}>No rows</div>;
}
onRowsRendered({overscanStartIndex, overscanStopIndex, startIndex, stopIndex}) {
const list = this.props.recentActList.length ? this.props.recentActList : [];
const {loadedRowsMap} = this.state;
console.log(startIndex, stopIndex, this.state.loadedRowCount);
// if(startIndex + 10 === list.length && this._isRowLoaded(startIndex) !== STATUS_LOADED) {
// var postData = {
// "userName": "admin",
// "queryType": "GET_RECENT_PROJECTS",
// "data": {
// pageStart: 1,
// pageEnd: 10
// }
// };
// this.props.getRecentActivites(postData, this.recentActResponse.bind(this));
// }
}
render() {
const list = this.props.recentActList.length ? this.props.recentActList : [];
const {loadedRowCount, loadingRowCount} = this.state;
return (
<div className="recent left_panel">
<div className="x_panel">
<div className="x_title sub_title">
<h2>Recent Activites</h2>
</div>
<div className="x_content">
<div className="dashboard-widget-content">
<ul className="list-unstyled timeline widget">
<InfiniteLoader
isRowLoaded={this._isRowLoaded}
loadMoreRows={this._loadMoreRows}
rowCount={list.length}>
{({onRowsRendered, registerChild}) => (
<div className={styles.list}>
<AutoSizer onResize={this.updateDimensions.bind(this)}>
{({width, height}) => (
<List
ref={(ref) => {
this.activitiList = ref;
registerChild(ref);
}}
noRowsRenderer={this.noRowsRenderer}
onRowsRendered={this.onRowsRendered}
deferredMeasurementCache={this.cache}
width={width}
height={height}
deferredMeasurementCache={this.cache}
rowHeight={this.cache.rowHeight}
rowRenderer={this.renderRow}
rowCount={list.length} /* Initially render 20 records */
/>
)}
</AutoSizer>
</div>
)}
</InfiniteLoader>
</ul>
{/* <div className="align-right">
<a href="http://karthik.jivox.com/studio/eam/production/index.php#"
className="btn-jivox-1">
<span className="btn-icon">
<i className="fas fa-chevron-down" aria-hidden="true"></i>
</span> Show More Activities
</a>
</div> */}
</div>
</div>
</div>
</div>
);
}
}
const RecentActiviti = connect(mapStateToProps, mapDispatchToProps)(RecentActivitiComp);
export default RecentActiviti;
As you can see I am making API call at didMount phase and therefore populating my redux store with the data. Data is coming fine. But isRowLoaded and loadMoreRows are not getting called.
I have debugged the sample code of infiniteLoader.example.js, there during the inital render those two functions are called and properly set loadedRowsMap.
What I am doing wrong here ? :-( Any help would be greatly appreciated .
I have resolved the issue after making some changes.
const mapStateToProps = (state) => {
return {
myList: state.dashboardReducer.myList
}
}
const list = this.props.myList.length ? this.props.recentActList : [];
const rowCount = list.length + (moreToBeLoaded ? 1 : 0);
To know why 1 more row loaded pls check this post react-virtualized InfiniteLoader/List - working example using AJAX
So the issue in my code was I was writing my custom onRowsRendered function and it was not handling the response correctly. Now I changed the code to use the one passed by InfiniteLoader.
Hope that helps.
<InfiniteLoader
isRowLoaded={this._isRowLoaded}
loadMoreRows={this._loadMoreRows}
rowCount={rowCount}>
{({onRowsRendered, registerChild}) => (
<div className={styles.list}>
<AutoSizer onResize={this.updateDimensions.bind(this)}>
{({width, height}) => (
<ActiviitiList
ref={(ref) => {
this.activitiList = ref;
registerChild(ref);
}}
width={width}
height={height}
onRowsRendered={onRowsRendered}
rowCount={rowCount}
rowHeight={this.cache.rowHeight}
rowRenderer={this._rowRenderer}
overscanRowCount={3}
deferredMeasurementCache={this.cache}
/>
)}
</AutoSizer>
</div>
)}
</InfiniteLoader>
(The code you've posted has a lot going on; you'll probably get better responses if you remove the parts that aren't related to your question.)
My advice is to take a look at the values you're getting for list.length, which you're passing to InfiniteLoader's rowCount prop. InfiniteLoader will only call loadMoreRows if rowCount is higher than the number of rows it has data for.
For example: during the first render, the value is 0, because you have not fetched any data yet. This prevents a call to loadMoreRows during the first render.
I also noticed that you are keeping loadedRowCount and loadingRowCount in state, but you never use them for anything. I don't think that's related to your issue, but it's also probably not intentional.

Phantom JS Selenium IDE : How to resize the browser window

I'm using Selenium IDE to record a simple test with my web app , my html generated scènario is like the following the following:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head profile="http://selenium-ide.openqa.org/profiles/test-case">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link rel="selenium.base" href="http://pagamac.dev/" />
<title>addContact</title>
</head>
<body>
<table cellpadding="1" cellspacing="1" border="1">
<thead>
<tr><td rowspan="1" colspan="3">addContact</td></tr>
</thead><tbody>
<tr>
<td>open</td>
<td>/</td>
<td></td>
</tr>
<tr>
<td>type</td>
<td>id=edit-name</td>
<td>admin</td>
</tr>
<tr>
<td>click</td>
<td>id=edit-pass</td>
<td></td>
</tr>
<tr>
<td>type</td>
<td>id=edit-pass</td>
<td>admin</td>
</tr>
<tr>
<td>clickAndWait</td>
<td>id=edit-submit</td>
<td></td>
</tr>
<tr>
<td>clickAndWait</td>
<td>css=li.civicrm > a</td>
<td></td>
</tr>
<tr>
<td>click</td>
<td>//*[#id="civicrm-menu"]/li[4]</td>
<td></td>
</tr>
<tr>
<td>clickAndWait</td>
<td>//*[#id="root-menu-div"]/div[5]/ul/li[1]/div/a</td>
<td>5000</td>
</tr>
<tr>
<td>select</td>
<td>//*[#id="prefix_id"]</td>
<td>M.</td>
</tr>
<tr>
<td>click</td>
<td>id=first_name</td>
<td></td>
</tr>
<tr>
<td>type</td>
<td>id=first_name</td>
<td>franc</td>
</tr>
<tr>
<td>click</td>
<td>id=last_name</td>
<td></td>
</tr>
<tr>
<td>type</td>
<td>id=last_name</td>
<td>ribery</td>
</tr>
<tr>
<td>select</td>
<td>//*[#id="email_1_location_type_id"]</td>
<td>Autre</td>
</tr>
<tr>
<td>click</td>
<td>id=email_1_email</td>
<td></td>
</tr>
<tr>
<td>type</td>
<td>id=email_1_email</td>
<td>rib#gmail.com</td>
</tr>
<tr>
<td>click</td>
<td>//*[#id="Email_1_IsBulkmail"]</td>
<td></td>
</tr>
<tr>
<td>click</td>
<td>//*[#id="email[1][on_hold]"]</td>
<td></td>
</tr>
<tr>
<td>type</td>
<td>id=phone_1_phone</td>
<td>8888888888</td>
</tr>
<tr>
<td>clickAndWait</td>
<td>id=_qf_Contact_upload_view-bottom</td>
<td></td>
</tr>
</tbody></table>
</body>
</html>
I'm using a specific plugin : selenium-html-js-converter (you find it here:
https://www.npmjs.com/package/selenium-html-js-converter )
this plugin converts my html test to a js one , like the following :
"use strict";
/* jslint node: true */
var assert = require('assert');
var browser, element, currentCommand = '',
options = {
timeout: 30000,
retries: 0,
screenshotFolder: 'screenshots/test_add_SimpleContact',
baseUrl: 'http://pagamac.dev/'
};
module.exports = function testAddSimpleContact(_browser, _options) {
browser = _browser;
var acceptNextAlert = true;
getRuntimeOptions(_options);
try {
currentCommand = 'open("/", "")';
browser.get(addBaseUrl("/"));
currentCommand = 'type("id=edit-name", "admin")';
browser.elementById("edit-name").clear();
browser.elementById("edit-name").sendKeys("admin");
currentCommand = 'click("id=edit-pass", "")';
browser.elementById("edit-pass").click();
currentCommand = 'type("id=edit-pass", "admin")';
browser.elementById("edit-pass").clear();
browser.elementById("edit-pass").sendKeys("admin");
currentCommand = 'clickAndWait("id=edit-submit", "")';
doAndWait(function() {
browser.elementById("edit-submit").click();
});
currentCommand = 'clickAndWait("css=li.civicrm > a", "")';
doAndWait(function() {
browser.elementByCssSelector("li.civicrm > a").click();
});
currentCommand = 'click("//*[#id="civicrm-menu"]/li[4]", "")';
browser.elementByXPath("//*[#id=\"civicrm-menu\"]/li[4]").click();
currentCommand = 'clickAndWait("//*[#id="root-menu-div"]/div[5]/ul/li[1]/div/a", "5000")';
doAndWait(function() {
browser.elementByXPath("//*[#id=\"root-menu-div\"]/div[5]/ul/li[1]/div/a").click();
});
currentCommand = 'select("//*[#id="prefix_id"]", "M.")';
browser.elementByXPath("//*[#id=\"prefix_id\"]").elementByXPath('option[text()="M."][1]').click();
currentCommand = 'click("id=first_name", "")';
browser.elementById("first_name").click();
currentCommand = 'type("id=first_name", "franc")';
browser.elementById("first_name").clear();
browser.elementById("first_name").sendKeys("franc");
currentCommand = 'click("id=last_name", "")';
browser.elementById("last_name").click();
currentCommand = 'type("id=last_name", "ribery")';
browser.elementById("last_name").clear();
browser.elementById("last_name").sendKeys("ribery");
currentCommand = 'select("//*[#id="email_1_location_type_id"]", "Autre")';
browser.elementByXPath("//*[#id=\"email_1_location_type_id\"]").elementByXPath('option[text()="Autre"][1]').click();
currentCommand = 'click("id=email_1_email", "")';
browser.elementById("email_1_email").click();
currentCommand = 'type("id=email_1_email", "rib#gmail.com")';
browser.elementById("email_1_email").clear();
browser.elementById("email_1_email").sendKeys("rib#gmail.com");
currentCommand = 'click("//*[#id="Email_1_IsBulkmail"]", "")';
browser.elementByXPath("//*[#id=\"Email_1_IsBulkmail\"]").click();
currentCommand = 'click("//*[#id="email[1][on_hold]"]", "")';
browser.elementByXPath("//*[#id=\"email[1][on_hold]\"]").click();
currentCommand = 'type("id=phone_1_phone", "8888888888")';
browser.elementById("phone_1_phone").clear();
browser.elementById("phone_1_phone").sendKeys("8888888888");
currentCommand = 'clickAndWait("id=_qf_Contact_upload_view-bottom", "")';
doAndWait(function() {
browser.elementById("_qf_Contact_upload_view-bottom").click();
});
} catch (e) {
var failedScreenShot = options.screenshotFolder + '/Exception#' + currentCommand.replace(/\(.+/, '') + '.png';
try {
createFolderPath(options.screenshotFolder);
browser.saveScreenshot(failedScreenShot);
} catch (e) {
e.message = 'Failure in Selenium command "' + currentCommand + '": ' + e.message + ' (Could not save screenshot after failure occured)';
throw e;
}
e.message = 'Failure in Selenium command "' + currentCommand + '": ' + e.message + ' (Screenshot was saved to ' + failedScreenShot + ')';
throw e;
}
};
/**
* A hacky way to implement *AndWait Selenese commands. Native wd doesn't offer
* commands equivalent to e.g. clickAndWait or dragAndDropAndWait (actually
* neither dragAndDrop nor AndWait exist in wd) or clickAndWait. We work around
* it wrapping all *AndWait commands in code that first taints the document body
* with a class, then runs the base command, and waits for a new document ready
* state without the tainted body.
*
* #param {function} code The code to execute
* #param {WdSyncClient.browser} wdBrowser (optional) Browser instance
* #return {void}
*/
function doAndWait(code, wdBrowser) {
if (typeof wdBrowser !== 'object') {
wdBrowser = browser;
}
wdBrowser.execute('document.body.className += " SHTML2JSC"');
code();
withRetry(function() {
if (wdBrowser.execute("return document.readyState") !== 'complete' || wdBrowser.hasElementByCssSelector('body.SHTML2JSC'))
throw new Error('Page did not load in time');
}, wdBrowser);
}
/**
* Implements waitForPageToLoad selenese command. As opposed to the Selenium
* IDE implementation, this one actually waits for all resources to have been
* loaded.
*
* #param {WdSyncClient.browser} wdBrowser (optional) Browser instance.
* #return {void}
*/
function waitForPageToLoad(wdBrowser) {
if (typeof wdBrowser !== 'object') {
wdBrowser = browser;
}
withRetry(function() {
if (wdBrowser.execute("return document.readyState") !== 'complete')
throw new Error('Page did not load in time');
});
}
function getRuntimeOptions(opts) {
if (!opts) return;
if (typeof opts.lbParam === 'object') {
options.lbParam = opts.lbParam;
}
if (opts.baseUrl && typeof opts.baseUrl === 'string') {
options.baseUrl = opts.baseUrl;
if (opts.forceBaseUrl && typeof opts.forceBaseUrl === 'boolean') {
options.forceBaseUrl = opts.forceBaseUrl;
}
}
if (opts.screenshotFolder && typeof opts.screenshotFolder === 'string') {
options.screenshotFolder = opts.screenshotFolder;
}
if (opts.timeout && isNumber(opts.timeout)) {
options.timeout = opts.timeout;
}
if (opts.retries && isNumber(opts.retries)) {
options.retries = opts.retries;
}
}
function isNumber(val) {
return typeof val === 'number' && !isNaN(val);
}
function isAlertPresent(wdBrowser) {
if (typeof wdBrowser !== 'object') {
wdBrowser = browser;
}
try {
wdBrowser.alertText();
return true;
} catch (e) {
return false;
}
}
function closeAlertAndGetItsText(acceptNextAlert, wdBrowser) {
if (typeof wdBrowser !== 'object') {
wdBrowser = browser;
}
try {
var alertText = wdBrowser.alertText();
if (acceptNextAlert) {
wdBrowser.acceptAlert();
} else {
wdBrowser.dismissAlert();
}
return alertText;
} catch (ignore) {}
}
function isEmptyArray(arr) {
return arr instanceof Array && arr.length === 0;
}
function waitFor(checkFunc, expression, timeout, pollFreq, wdBrowser) {
if (typeof wdBrowser !== 'object') {
wdBrowser = browser;
}
if (!isNumber(timeout)) {
timeout = options.timeout;
}
if (!isNumber(pollFreq)) {
pollFreq = 200;
}
var val;
var timeLeft = timeout;
while (!val) {
val = checkFunc();
if (val)
break;
if (timeLeft < 0) {
throw new Error('Timed out after ' + timeout + ' msecs waiting for expression: ' + expression);
}
wdBrowser.sleep(pollFreq);
timeLeft -= pollFreq;
}
return val;
}
function createFolderPath(path) {
var fs = require('fs');
var folders = path.split(/[/\\]+/);
path = '';
while (folders.length) {
/* This works for both absolute and relative paths, as split on an absolute path will have resulted in an array with the first bit empty. Safe for absolute Windows paths as well: */
path += folders.shift() + '/';
if (!fs.existsSync(path)) {
fs.mkdirSync(path);
} else if (!fs.statSync(path).isDirectory()) {
throw new Error("Cannot create directory '" + path + "'. File of same name already exists.");
}
}
}
/**
* Prefix a (relative) path with a base url.
*
* If the path itself is an absolute one including a domain, it'll be returned as-is, unless force is set to true, in
* which case the existing domain is replaced with the base.
*
* When optional arguments are when omitted, values from glocal options object are used.
*
* #param {string} path The path to prefix with the base url
* #param {string} base (optional) The base url
* #param {bool} force (optional) If true, force prefixing even if path is an absolute url
* #return {string} The prefixed url
*/
function addBaseUrl(path, base, force) {
if (typeof base !== 'string') {
base = options.baseUrl;
}
if (typeof force !== 'boolean') {
force = options.forceBaseUrl;
}
if (path.match(/^http/)) {
if (force) {
return path.replace(/^http(s?):\/\/[^/]+/, base).replace(/([^:])\/\/+/g, '$1/');
}
return path;
}
return (base + '/' + path).replace(/([^:])\/\/+/g, '$1/');
}
/**
* Focuses the topmost window on the stack of handles in the browser.
*
* After a WdSyncClient.browser.close() wd does not automatically restore focus
* to the previous window on the stack, so you may execute this function to
* ensure that subsequent tests won't be targeting a defunct window handle.
*
* #param {WdSyncClient.browser} wdBrowser (optional) Browser instance.
* #return {void}
*/
function refocusWindow(wdBrowser) {
if (typeof wdBrowser !== 'object') {
wdBrowser = browser;
}
var handles = wdBrowser.windowHandles();
if (handles.length) {
try {
wdBrowser.window(handles[handles.length - 1]);
} catch (e) {
console.warn('Failed to automatically restore focus to topmost window on browser stack. Error:', e);
}
}
}
/**
* Tries to execute an Error throwing function, and if an error is thrown, one
* or more retries are attempted until <timeout> msecs have passed.
*
* Pauses between retries are increasing in length. The pause before the final
* retry will be half the total timeout. The pause before the second-to-last
* will be half of the last one's, and so forth. The first attempt will have the
* same pause as that of the first retry.
*
* Optional arguments use glocal values when omitted
*
* #param {function} code The code to execute
* #param {WdSyncClient.browser} wdBrowser (optional) Browser instance
* #param {number} retries (optional) The max number of retries
* #param {number} timeout (optional) The max number of msecs to keep trying
* #return {mixed} Whatever the code block returns
*/
function withRetry(code, wdBrowser, retries, timeout) {
if (typeof wdBrowser !== 'object') {
wdBrowser = browser;
}
if (!isNumber(retries)) {
retries = options.retries;
}
if (!isNumber(timeout)) {
timeout = options.timeout;
}
var durations = [timeout];
var err;
while (retries) {
durations[0] = Math.ceil(durations[0] / 2);
durations.unshift(durations[0]);
--retries;
}
for (var i = 0; i < durations.length; ++i) {
try {
return code();
} catch (e) {
err = e;
wdBrowser.sleep(durations[i]);
}
}
throw (err);
}
/**
* Triggers a keyboard event on the provided wd browser element.
*
* #param {WD Element} element Target DOM element to trigger the event on
* #param {string} event Keyboard event (keyup|keydown|keypress)
* #param {keyCode} key Charcode to use
* #return {void}
*/
function keyEvent(element, event, keyCode) {
browser.execute(functionBody(function() {
var element = arguments[0];
var event = arguments[1];
var keyCode = arguments[2];
var ev = window.document.createEvent('KeyboardEvent');
if (ev.initKeyEvent)
ev.initKeyEvent(event, true, true, window, 0, 0, 0, 0, 0, keyCode);
else
ev.initKeyboardEvent(event, true, true, window, 0, 0, 0, 0, 0, keyCode);
return element.dispatchEvent(ev);
}), [element.rawElement, event, keyCode]);
}
function functionBody(func) {
return func.toString().replace(/^function[^{]+{/, '').replace(/}[^}]*$/, '');
}
/* User extensions */
function typeRedactor(target, value, element) {
/* .execute takes just a function body as a string to be eval'ed: */
var className = browser.execute(functionBody(function() {
var element = arguments[0];
var text = arguments[1];
var callback = arguments[2];
/* Once done, we tag redactor with a class, so we know when we finished: */
var className = "seleniumDoTypeRedactor-" + (new Date()).getTime();
var keyEvent = function(element, event, keyCode) {
var ev = window.document.createEvent('KeyboardEvent');
if (ev.initKeyEvent)
ev.initKeyEvent(event, true, true, window, 0, 0, 0, 0, 0, keyCode);
else
ev.initKeyboardEvent(event, true, true, window, 0, 0, 0, 0, 0, keyCode);
return element.dispatchEvent(ev);
};
keyEvent(element, 'keydown', 0);
keyEvent(element, 'keyup', 0);
element.textContent = 'redactor';
setTimeout(function() {
keyEvent(element, 'keydown', 0);
keyEvent(element, 'keyup', 0);
element.textContent = text;
setTimeout(function() {
keyEvent(element, 'keydown', 0);
keyEvent(element, 'keyup', 0);
element.className += ' ' + className;
}, 50);
}, 50);
return className;
}), [element.rawElement /* Important! element is mangled by wd-sync; we need the raw wd element */ , value]);
waitFor(function() {
return browser.hasElementByCssSelector('.' + className);
}, 'browser.hasElementByCssSelector(".' + className + '") [to mark completion of typeRedactor execution]');
}
finally to run this test i'm using node & phantom.js : first i run phantom.js ; then i execute my test.
My problem is when i do this ; my test , whitch runs well with within selenium IDE (in the html format) wouldn't work fine within phantom.js
After some debugging, i see that phatom.js is running under a wrong window sizes (weak with and height) -> By the way my web page is loosing some elements when resized to smaller.
I'm looking to how control the phantom.js browser windows sizes : width & heignt
Note : i'm note using the classic webdriver (you can notice it is my js test file)
suggestions ?
Faced the similar situation a couple of days ago. Browsed through a lot of discussion forums/discussions. The one suggestion I got regarding window resizing is this, you have to
beforeEach(function() {
browser.driver.manage().window().setSize(1280, 1024);
});
Having said that, this solution didn't serve my purpose. I had to perform some tricky measures.
Let me know if it works else I will suggest you more options.

How to select all checkboxes in JHipster

I created test Spring Boot + AngularJS app to test checkboxes:
html:
... <thead>
<tr>
<th><input type="checkbox" ng-model="isAllSelected"
ng-click="selectAll()"></th>
<th>Lp.</th>
<th>ID</th>
<th>Name</th>
<th>Parent Id</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="test in tests">
<td><input type="checkbox" ng-model="test.checked"
ng-change="optionSelected()" /></td>
<td>{{$index+1}}.</td>
<td>{{test.id}}</td>
<td>{{test.name}}</td>
<td>{{test.parentId}}...
test_controller.js:
(function(angular) {
var AppTestController = function($scope, Test) {
var vm = this;
vm.tests = [];
vm.loadAll = loadAll;
loadAll();
function loadAll() {
Test.query(function(result) {
vm.tests = result;
});
}
vm.selectAll = function() {
var toggleStatus = vm.isAllSelected;
angular.forEach(vm.tests, function(itm) {
itm.checked = toggleStatus;
});
}
vm.optionSelected = function() {
vm.isAllSelected = vm.tests
.every(function(itm) {
return itm.checked;
})
}
};
AppTestController.$inject = [ '$scope', 'Test' ];
angular.module("myApp.test_controller").controller(
"AppTestController", AppTestController);
}(angular));
This works for me as spring Boot app, but when I do the same in JHipster it doesn't work.
How can I get it to work in JHipster?
This is what I currently used and it works!:
.html
<thead>
<tr jh-sort="vm.predicate" ascending="vm.reverse" callback="vm.transition()">
<!--th jh-sort-by="id"><span data-translate="global.field.id">ID</span> <span class="glyphicon glyphicon-sort"></span></th-->
<th><input type="checkbox" icheck ng-change="vm.selectAll()" ng-model="vm.checkAll[vm.page]"></th>
<th jh-sort-by="id"><span data-translate="global.field.id">ID</span></th>
---
</tr>
</thead>
<tbody>
<tr ng-repeat="school in vm.schools track by school.id">
<td><input type="checkbox" icheck ng-model="vm.checkboxes[school.id]" ng-change="vm.select(school)"/></td>
<td>{{($index + 1) + (vm.page - 1) * vm.itemsPerPage}}</td>
...
</tr>
</tbody>
.js
vm.checkAll = [];
var map = {};
vm.checkboxes = [];
vm.selectedItems = [];
vm.selectAll = selectAll;
vm.select = select;
function selectAll () {
var value = vm.checkAll[vm.page];
angular.forEach(vm.schools, function(item) {
if (angular.isDefined(item.id)) {
if(vm.checkboxes[item.id] != value) {
vm.checkboxes[item.id] = value;
vm.select(item);
}
}
});
};
function select (item) {
var value = vm.checkboxes[item.id];
if(value) {
vm.selectedItems.push(item);
if(map[vm.page] == null) map[vm.page] = 1;
else map[vm.page] = map[vm.page] + 1;
if(map[vm.page] == vm.schools.length) {
vm.checkAll[vm.page] = true;
}
} else {
vm.selectedItems.splice(item, 1);
if(map[vm.page] == null) map[vm.page] = 0;
else map[vm.page] = map[vm.page] - 1;
if(map[vm.page] < vm.schools.length) {
vm.checkAll[vm.page] = false;
}
}
};

Custom pagination in Swiper

i'm using Swiper and want custom pagination. This question was answered here, but i misunderstood, how to make that pagination clickable, nothing worked. What am i doing wrong?
$(document).ready(function () {
var mySwiper = new Swiper('.swiper-container', {
nextButton: '.swiper-button-next'
, prevButton: '.swiper-button-prev'
, pagination: '.swiper-pagination'
, paginationClickable: true
, paginationHide: false
, paginationType: 'custom'
, paginationElement: 'div'
, paginationCustomRender: function (swiper, current, total) {
var names = [];
$(".swiper-wrapper .swiper-slide").each(function (i) {
names.push($(this).data("name"));
});
var text = "";
for (let i = 1; i <= total; i++) {
if (current == i) {
text += "<div class='swiper-pagination-container swiper-pagination-container-active'><div class='swiper-pagination-icon swiper-pagination-icon-active'></div><div>" + names[i] + "</div></div>";
}
else {
text += "<div class='swiper-pagination-container'><div class='swiper-pagination-icon'></div><div>" + names[i] + "</div></div>";
}
}
return text;
}
});
$(".swiper-pagination-container").on("click", function () {
mySwiper.slideTo($(".swiper-pagination-container").index(this) + 1);
});
}
The difference is that i placed .swiper-pagination div outside the .swiper-wrapper:
<div class="swiper-pagination"></div>
<div class="swiper-container">
<div class="swiper-wrapper">
<div class="swiper-slide" data-name="7 сентября">Slide 1</div>
<div class="swiper-slide" data-name="10 декабря">Slide 2</div>
<div class="swiper-slide" data-name="14-23 декабря">Slide 3</div>
<div class="swiper-slide" data-name="30 декабря">Slide 4</div>
<div class="swiper-slide" data-name="5-6 февраля">Slide 5</div>
<div class="swiper-slide" data-name="8 февраля">Slide 6</div>
<div class="swiper-slide" data-name="9 февраля">Slide 7</div>
</div>
<div class="swiper-button-prev"></div>
<div class="swiper-button-next"></div>
</div>
It's simple, try it:
window.mainSlider = new Swiper('.swiper-container', {
nextButton: '.swiper-button-next'
, prevButton: '.swiper-button-prev'
, pagination: '.swiper-pagination'
, paginationClickable: true
, paginationHide: false
paginationBulletRender : function (index, className) {
var slide = $('.' + this.wrapperClass).find('[data-name]')[index],
label = $(slide).attr('data-name');
return '<span class="' + className + '">' + (typeof label !== 'undefined' ? name : '') + '</span>';
}
});
I tried this, it works on swiper with loop set to true, if loop set to false, simply remove +1 from the index will do.
pagination: {
el: $('.your_class').find('.swiper-pagination'),// to find the swiper-pagination you put outside of the swiper-container
clickable: true,
renderBullet: function (index, className) {
var slider_array = [];
var el = $('.swiper-container')
el.find('[data-name]').each(function () {
slider_array.push($(this).data('name'));
});
console.log(slider_array);
return '<span class="' + className + '">' + slider_array[index + 1] + '</span>';
}
}

Resources