Turbotable : p-tableHeaderCheckbox selects disabled lines - primeng-turbotable

I am facing a problem on PrimeNG TurboTable.
I started from the following example: https://www.primefaces.org/primeng/#/table/selection and more particularly from the Checkbox Selection example.
The only difference is that on some p-tableCheckbox I added a [disabled]="true"
This works very well if I select a disabled line it does not activate and can not be selected, but when I click on p-tableHeaderCheckbox all the lines are selected even the lines in disabled.
In addition, the selection also counts the lines in status disabled or it should only take lines with no status disabled
I made an example on stackblitz : https://stackblitz.com/edit/angular-gnbsml?file=src%2Fapp%2Fapp.component.html
How to prevent tableHeaderCheckbox from also selecting disable lines?
Thank you in advance for your answers

You can prevent selection in (selectionChange) callback on table. Split [(selection)] on two part:
[selection]="selectedRowData" (selectionChange)="onSelectionChange($event)"
Add onSelectionChange method to component:
onSelectionChange(selection: any[]) {
for (let i = selection.length - 1; i >= 0; i--) {
let data = selection[i];
if (this.isRowDisabled(data)) {
selection.splice(i, 1);
}
}
this.selectedRowData = selection;
}
Also add isRowDisabled method:
isRowDisabled(data: any): boolean {
return data.color === 'orange'
}
and change template for tableCheckbox to use isRowDisabled (it's only for check in one place)
<p-tableCheckbox [value]="rowData" [disabled]="isRowDisabled(rowData)"></p-tableCheckbox>
See example on https://stackblitz.com/edit/angular-hnzxs2 (I am also add logic to exclude disabled rows from process of calculating state of headerCheckBox)

It's failing when we have only disabled rows after filter. I have fixed it by checking active rows.
ngAfterViewInit(): void {
const orig_updateCheckedState = this._headerCheckBox.updateCheckedState;
const me = this;
this._headerCheckBox.updateCheckedState = function() {
const cars: any[] = me._table.filteredValue || me._table.value;
const selection: any[] = me._table.selection;
let actRows: boolean = false;
for (const car of cars) {
if (!me.isRowDisabled(car)) {
actRows = true;
const selected = selection && selection.indexOf(car) >= 0;
if (!selected) return false;
}
}
if (actRows) {
return true
} else {
return false;
}
};
}

Related

Lightbox2 Swipe gesture for touchscreens

To my great surprise Lightbox2(http://lokeshdhakar.com/projects/lightbox2/) does not support swipe gestures out of the box...
I was not able to find any add on in order to support this behavior. Anyone has any suggestions a side from changing the entire plugin? :)
To summary, you must hide the navigation buttons and implement swiping, moving and sliding effect on the image.
You will need :
jquery.event.move
jquery.event.swipe
jquery ui slide effect, you can package it in the jquery ui download builder
maybe there's a simplest way to get/implement all of these 3 small dependencies... but that way works for me.
in the lightbox script, add a new LightboxOptions enableSwipeOnTouchDevices and set it to true :
this.enableSwipeOnTouchDevices = true;
add the following blocks after the this.$lightbox.find('.lb-next').on('click'... block to create the swiping events:
this.$lightbox.find('.lb-image').on("swiperight",function() {
$('.lb-image').effect("slide", { "direction" : "right", "mode" : "hide"} ,function(){
if (self.currentImageIndex === 0) {
self.changeImage(self.album.length - 1);
} else {
self.changeImage(self.currentImageIndex - 1);
}
})
});
this.$lightbox.find('.lb-image').on("swipeleft",function() {
$('.lb-image').effect("slide", { "direction" : "left", "mode" : "hide"} ,function(){
if (self.currentImageIndex === self.album.length - 1) {
self.changeImage(0);
} else {
self.changeImage(self.currentImageIndex + 1);
}
})
});
and rewrite the updateNav function like this to hide the navigation buttons:
Lightbox.prototype.updateNav = function() {
// Check to see if the browser supports touch events. If so, we take the conservative approach
// and assume that mouse hover events are not supported and always show prev/next navigation
// arrows in image sets.
var alwaysShowNav = false;
var enableSwipe = false;
try {
document.createEvent("TouchEvent");
alwaysShowNav = (this.options.alwaysShowNavOnTouchDevices)? true: false;
enableSwipe = (this.options.enableSwipeOnTouchDevices)? true: false;
} catch (e) {}
//if swiping is enable, hide the two navigation buttons
if (! enableSwipe) {
this.$lightbox.find('.lb-nav').show();
if (this.album.length > 1) {
if (this.options.wrapAround) {
if (alwaysShowNav) {
this.$lightbox.find('.lb-prev, .lb-next').css('opacity', '1');
}
this.$lightbox.find('.lb-prev, .lb-next').show();
} else {
if (this.currentImageIndex > 0) {
this.$lightbox.find('.lb-prev').show();
if (alwaysShowNav) {
this.$lightbox.find('.lb-prev').css('opacity', '1');
}
}
if (this.currentImageIndex < this.album.length - 1) {
this.$lightbox.find('.lb-next').show();
if (alwaysShowNav) {
this.$lightbox.find('.lb-next').css('opacity', '1');
}
}
}
}
}
};
I've used jquery mobile to detect swipeleft and swiperight. Then bind them to click .lb-next and .lb-prev. It's working now.
Here is my codepen.
PEC's solution worked for me with one modification on a Jekyll site.
Instead of:
this.enableSwipeOnTouchDevices = true;
We added this to /_includes/scripts.html after the dependencies and lightbox.js:
<script>
lightbox.option({
'enableSwipeOnTouchDevices': true,
})
</script>
The PEC solution is good, but it doesn't work anymore with the current version of lightbox (2.11.2). The effect() method doesn't exists anymore.
So the swiping methods should be updated:
this.$lightbox.find('.lb-image').on("swiperight",function() {
if (self.currentImageIndex === 0) {
self.changeImage(self.album.length - 1);
} else {
self.changeImage(self.currentImageIndex - 1);
}
return false;
});
this.$lightbox.find('.lb-image').on("swipeleft",function() {
if (self.currentImageIndex === self.album.length - 1) {
self.changeImage(0);
} else {
self.changeImage(self.currentImageIndex + 1);
}
return false;
});
Less fancy, but shorter and works.
In short: 'catch' swipe gesture and then trigger 'click' on next/prev button based on swipe direction.
let touchstartX = 0;
let touchendX = 0;
function handleGesture() {
if (touchendX < touchstartX) $(".lb-prev").trigger("click");
if (touchendX > touchstartX) $(".lb-next").trigger("click");
}
$(document).on("touchstart", ".lb-nav", e => {
touchstartX = e.changedTouches[0].screenX;
});
$(document).on("touchend", ".lb-nav", e => {
touchendX = e.changedTouches[0].screenX;
handleGesture();
});

In Firebase, how do I handle new children added after I statically loaded the latest N?

Here's my pagination/infinite scrolling scenario:
Load the initial N with startAt().limit(N).once('value'). Populate a list items.
On scroll, load the next N. (I pass a priority to startAt() but that's tangential.)
When a new item is added, I'd like to pop it to the top of items.
If I use a .onChildAdded listener for step 3, it finds all the items including those I've already pulled in thus creating duplicates. Is there a better way?
Another method would be to use the .onChildAdded listener for the initial N in step 1 instead of .once, but when the initial N items come in I do items.add(item) to sort one after the other as they are already in order, but with the new one that comes in after the fact I need to somehow know it's unique so I can do items.insert(0, item) to force it to the top of the list. I'm not sure how to set this up, or if I'm off the mark here.
EDIT: Still in flux, see: https://groups.google.com/forum/#!topic/firebase-talk/GyYF7hfmlEM
Here's a working solution I came up with:
class FeedViewModel extends Observable {
int pageSize = 20;
#observable bool reloadingContent = false;
#observable bool reachedEnd = false;
var snapshotPriority = null;
bool isFirstRun = true;
FeedViewModel(this.app) {
loadItemsByPage();
}
/**
* Load more items pageSize at a time.
*/
loadItemsByPage() {
reloadingContent = true;
var itemsRef = f.child('/items_by_community/' + app.community.alias)
.startAt(priority: (snapshotPriority == null) ? null : snapshotPriority).limit(pageSize+1);
int count = 0;
// Get the list of items, and listen for new ones.
itemsRef.once('value').then((snapshot) {
snapshot.forEach((itemSnapshot) {
count++;
// Don't process the extra item we tacked onto pageSize in the limit() above.
print("count: $count, pageSize: $pageSize");
// Track the snapshot's priority so we can paginate from the last one.
snapshotPriority = itemSnapshot.getPriority();
if (count > pageSize) return;
// Insert each new item into the list.
// TODO: This seems weird. I do it so I can separate out the method for adding to the list.
items.add(toObservable(processItem(itemSnapshot)));
// If this is the first item loaded, start listening for new items.
// By using the item's priority, we can listen only to newer items.
if (isFirstRun == true) {
listenForNewItems(snapshotPriority);
isFirstRun = false;
}
});
// If we received less than we tried to load, we've reached the end.
if (count <= pageSize) reachedEnd = true;
reloadingContent = false;
});
// When an item changes, let's update it.
// TODO: Does pagination mean we have multiple listeners for each page? Revisit.
itemsRef.onChildChanged.listen((e) {
Map currentData = items.firstWhere((i) => i['id'] == e.snapshot.name);
Map newData = e.snapshot.val();
newData.forEach((k, v) {
if (k == "createdDate" || k == "updatedDate") v = DateTime.parse(v);
if (k == "star_count") v = (v != null) ? v : 0;
if (k == "like_count") v = (v != null) ? v : 0;
currentData[k] = v;
});
});
}
listenForNewItems(endAtPriority) {
// If this is the first item loaded, start listening for new items.
var itemsRef = f.child('/items').endAt(priority: endAtPriority);
itemsRef.onChildAdded.listen((e) {
print(e.snapshot.getPriority());
print(endAtPriority);
if (e.snapshot.getPriority() != endAtPriority) {
print(e.snapshot.val());
// Insert new items at the top of the list.
items.insert(0, toObservable(processItem(e.snapshot)));
}
});
}
void paginate() {
if (reloadingContent == false && reachedEnd == false) loadItemsByPage();
}
}
Load the initial N with startAt().limit(N).once('value'). Populate a list items.
On the first run, note the first item's priority, then start an onChildAdded listener that has an endAt() with that priority. This means it'll only listen to stuff from there and above.
In that listener, ignore the first event which is the topmost item we already have, and for everything else, add that to the top of the list.
Of course, on scroll, load the next N.
EDIT: Updated w/ some fixes, and including the listener for changes.

UIACollectionView cells vs visibleCells

I'm trying to write a test script using automation in xcode 4.5.
I have a UICollectionView and I want to click on some cell not currently visible.
Per documentation, I should expect cells to return all cells in the collection view, and visibleCells to return only the currently visible ones.
Instead what I'm seeing is that cells returns only the currently visible cells, and calling visibleCells stops the script on 'undefined' is not a function (evaluating 'collection.visibleCells()')
var target = UIATarget.localTarget();
var collection = target.frontMostApp().mainWindow().collectionViews()[0];
UIALogger.logMessage("Looking in collection: " + collection);
UIALogger.logMessage("Cells: " + collection.cells() + " length " + collection.cells().length);
UIALogger.logMessage("Visible cells: " + collection.visibleCells());
The code above returns the right UICollectionView, second log line prints:
Cells: [object UIAElementArray] length 12
although I have 100 items in the collection view, and third log line crashes script.
Is this a documentation/UIACollectionView bug?
Any ideas how can I tell the automation to scroll until it sees a cell with the name "My cell"?
I've tried using someCell.scrollToVisible, but I need to have the cell to do that, and I don't since I can't get it from cells.
EDIT:
As suggested by Jonathan I've implemented a scroll-till-found function.
it's a bit implementation specific, so you'll probably need to tweak isCellWithName.
I'm also looking to add a break in case we didn't find the needed cell in the while loop, if anyone has ideas, feel free to edit this.
function isCellWithName(cell, name) {
return (cell.staticTexts()[0].name() == name);
}
function getCellWithName(array, name) {
for (var i = 0; i < array.length; i++) {
if (isCellWithName(array[i], name)) {
return array[i];
}
}
return false;
}
function scrollToName(collection, name) {
var found = getCellWithName(collection.cells(), name);
while (found === false) {
collection.dragInsideWithOptions({startOffset:{x:0.2, y:0.99}, endOffset:{x:0.2, y:0},duration:1.0});
found = getCellWithName(collection.cells(), name);
}
return found;
}
The documentation is apparently incorrect. There is no visibleCells() method on UIACollectionView. I figured this out by looping over all the collection view elements properties and printing out their names:
var target = UIATarget.localTarget();
var window = target.frontMostApp().mainWindow();
var collectionView = window.collectionViews()[0];
for (var i in collectionView) {
UIALogger.logMessage(i);
}
Table view elements, on the other hand, do list all the cells with the cells() method. I'm wondering if they choose not to do this because of the much more complicated nature of collection views. It could be very expensive to actually fetch all the collection view cells, build their representations and frames, and return the elements if you had a lot of them. That's what UI Automation does when it asks table views for all the cells. They have to all be instantiated and calculated in order to get the element representations.
But, to answer your larger question, how to scroll to a specific cell. Can you consistently scroll it into view with a swipe gesture? It's not the most convenient way to do it and we're "spoiled" by the ability to scroll to non-visible elements with table views. But from a user behavior testing standpoint, swiping a certain amount is what the user would have to do anyway. Could the test be structured to reflect this and would it address your need?
I couldn't get the the #marmor dragInsideWithOptions() bit to work in a generic fashion. Instead, I'm using the collectionView's value() function to get an index of the current page vs. last page, as in "page 3 of 11". Then I use collectionView's scrollUp() and scrollDown() methods to walk through the pages until we find what we're after. I wrote an extension for TuneUp's uiautomation-ext.js that seem to do the trick, and more:
function animationDelay() {
UIATarget.localTarget().delay(.2);
}
extend(UIACollectionView.prototype, {
/**
* Apple's bug in UIACollectionView.cells() -- only returns *visible* cells
*/
pageCount: function() {
var pageStatus = this.value();
var words = pageStatus.split(" ");
var lastPage = words[3];
return lastPage;
},
currentPage: function() {
var pageStatus = this.value();
var words = pageStatus.split(" ");
var currentPage = words[1];
//var lastPage = words[3];
return currentPage;
},
scrollToTop: function() {
var current = this.currentPage();
while (current != 1) {
this.scrollUp();
animationDelay();
current = this.currentPage();
}
},
scrollToBottom: function() {
var current = this.currentPage();
var lastPage = this.pageCount();
while (current != lastPage) {
this.scrollDown();
animationDelay();
current = this.currentPage();
}
},
cellCount: function() {
this.scrollToTop();
var current = this.currentPage();
var lastPage = this.pageCount();
var cellCount = this.cells().length;
while (current != lastPage) {
this.scrollDown();
animationDelay();
current = this.currentPage();
cellCount += this.cells().length;
}
return cellCount;
},
currentPageCellNamed: function(name) {
var array = this.cells();
for (var i = 0; i < array.length; i++) {
var cell = array[i];
if (cell.name() == name) {
return cell;
}
}
return false;
},
cellNamed: function(name) {
// for performance, look on the current page first
var foundCell = this.currentPageCellNamed(name);
if (foundCell != false) {
return foundCell;
}
if (this.currentPage() != 1) {
// scroll up and check out the first page before we iterate
this.scrollToTop();
foundCell = this.currentPageCellNamed(name);
if (foundCell != false) {
return foundCell;
}
}
var current = this.currentPage();
var lastPage = this.pageCount();
while (current != lastPage) {
this.scrollDown();
animationDelay();
current = this.currentPage();
foundCell = this.currentPageCellNamed(name);
if (foundCell != false) {
return foundCell;
}
}
return false;
},
/**
* Asserts that this collection view has a cell with the name (accessibility identifier)
* matching the given +name+ argument.
*/
assertCellNamed: function(name) {
assertNotNull(this.cellNamed(name), "No collection cell found named '" + name + "'");
}
});

JQuery dialog box issue in Internet Explorer

I am using a UI dialog box to display a message.
It works well in Firefox and Google Chrome. However, when I test in IE versions the dialog box doesn't open.
Can any one tell what real problem is?
I had pasted my code below:
function check_selected(c) {
var count = c - 1;
var radios = document.getElementsByName('plan');
for ( var i = 0; i < radios.length; i++) {
if (radios[i].disabled) {
if (radios[i].checked) { // checked
$('#planalert').dialog({
modal : true,
autoOpen : true,
title : "Plan",
width : 650,
height : 150,
show : "blind",
hide : "scale",
});
var c = 0;
} else {
var c = 1;
}
}
}
;
if (c == 0) {
return false;
} else {
return true;
}
}
Try removing the trailing comma from the options object you're passing dialog:
$('#planalert').dialog({
modal:true,
autoOpen: true,
title:"Plan",
width:650,
height:150,
show: "blind",
hide: "scale" // <-----
});
Internet Explorer will choke on the extra comma, while other browsers may not.
Also, remove the semicolon (;) at the end of the for loop ending brace:
for (var i = 0; i < radios.length; i++) {
/* snip */
} // <--- semicolon not necessary
Additionally, you are attempting to define c multiple times inside of your function. You are passing it in to the function so your var c = ... statements actually aren't having the effect you intend. You should either use another variable (which I would recommend rather than mutating the parameter you are passed), or remove the var statements from inside the if block.
Additionally, your loop is really only setting c for the last, disabled radio button. Is this what you intended?
Anyway, here is how I would re-write it (without attempting to fix the logical error above). Be sure to use tools like JsHint to check your JavaScript for probems:
function check_selected(c){
var count=c-1;
var radios = document.getElementsByName('plan');
var isChecked = 0;
for (var i = 0; i < radios.length; i++) {
if (radios[i].disabled) {
if (radios[i].checked){ // checked
$('#planalert').dialog({
modal:true,
autoOpen: true,
title:"Plan",
width:650,
height:150,
show: "blind",
hide: "scale"
});
isChecked = 0;
} else{
isChecked = 1;
}
}
}
return isChecked;
}

jQuery Datepicker, select multiple dates and mark them via css at selection time, permanently

so I have spent a day developing and reading (teh other way around) to enable my jQuery datepicker to select and highlight mutliple dates.
For this I have managed to write the selected dates to an array which is visualized in a simple textfield.
What I could not manage is to permanently modify the look of the selected date in the datepicker.
I have implemented the "beforeShowDay:" option, which is working properly uppon loading the datepicker, but of course is not called directly on selecting a date.
For this I guessed, I would need to refresh the view "onSelect", but the refresh is not working properly.
Right now, I have some small methods for handling the datearray.
Uppon adding or removing a date, I am trying to refresh the datepicker.
Uppon selecting a date, I am either adding it to, or remove it from the dates-array.
Also, onSelect, I am matching the selected day and try to addClass().
But once again, I think I missed something all the way round, as it is not shown in my datepicker.
Here's my code for now:
var dates = new Array();
function addDate(date) {
if (jQuery.inArray(date, dates) < 0 && date) {
dates.push(date);
}
}
function removeDate(index) {
dates.splice(index, 1);
}
// Adds a date if we don't have it yet, else remove it and update the dates
// textfield
function addOrRemoveDate(date) {
var index = jQuery.inArray(date, dates);
if (index >= 0) {
removeDate(index);
updateDatesField(dates);
} else {
addDate(date);
updateDatesField(dates);
}
jQuery(calContainer).datepicker("refresh");
}
var calContainer = document.getElementById("calendar-container");
jQuery(calContainer).datepicker({
minDate : new Date(),
dateFormat : "#",
onSelect : function(dateText, inst) {
addOrRemoveDate(dateText);
jQuery(".ui-datepicker-calendar tbody tr td a").each(function() {
if (jQuery(this).text() == inst.selectedDay) {
jQuery(this).addClass("ui-state-highlight");
jQuery(this).parent().addClass("selected");
}
});
// addTimeSelectorColumns(dates);
},
beforeShowDay : function(_date) {
var gotDate = -1;
for ( var index = 0; index < dates.length; index++) {
if (_date.getTime() == dates[index]) {
gotDate = 1;
} else {
gotDate = -1;
}
}
if (gotDate >= 0) {
return [ true, "ui-datepicker-today", "Event Name" ];
}
return [ true, "" ];
}
});
function updateDatesField(dates) {
if (dates.length > 0) {
var tmpDatesStrArr = new Array();
var dateString = "";
var datesField = document.getElementById("dates");
for ( var index = 0; index < dates.length; index++) {
var dateNum = dates[index];
var tmpDate = new Date();
tmpDate.setTime(dateNum);
dateString = tmpDate.getDate() + "." + tmpDate.getMonth() + "."
+ tmpDate.getFullYear();
tmpDatesStrArr.push(dateString);
}
jQuery(datesField).val(tmpDatesStrArr);
}
}
(I am still a beginner in Javascript/jQuery, so any help or hints towards my coding are welcome, btw...)
It depends whether you want to stick with jQuery UI or not. If you are open to other library, there is a jQuery Datepicker that supports multiple selection: jquery.datePicker with multiple select enabled. It may save you sometmie fiddling with jQuery UI's one, as it does not natively support multiple selection.
EDIT:
If your main library is not jQuery, I think you should look for a standalone or Prototype-dependent library instead. The JS Calendar looks promising. It natively supports multiple selection by detecting the Ctrl key.

Resources