jQuery Isotope 2.1 Pagination - filter

I'm trying to paginate items using jQuery Isotope 2.1. I have a set of items which can be filtered. I'd like to be able to limit the displayed items using a range. If I want to display 10 items per page, I'd tell it show all items between 11 and 20 in the filtered set for page 2, for example.
To make matters more complex, items can be sorted as well.
Is there any way to do this?
I thought about toggling a "active" class on the filtered items, then using JS and CSS I would only show items with the "active" class that are within the current range. Not sure if that's the most efficient approach.
Thanks,
Howie

I was able to solve the problem by prototyping the _filter method.
Firstly, I call the _sort method before attempting to filter the items since sorting will affect which items are displayed on each page.
Then I added a few parameters to the options object, such as page and items_per_page. Using these two values, I can calculate the first and last item for the intended page. Even though it's not necessary for pagination, I also added a parameter called activeClass which defaults to active and is added to all matching items. In addition I added and odd-item and an even-item class to each item so that in a list view, for example, I can alternate the row colors.
Lastly, I set a property on the this object called all_matches to the entire set of filtered items. This way I can calculate the total number of pages for my pager. Then I return only the filtered items for the current page.
On the layoutComplete event, I use the page and items_per_page values from the options object in order to calculate the first and last item and update my pager.
I happen to be using Bootstrap along with a customized version of the Bootpag pager which accepts itemsPerPage and totalItems.
You can check out the codepen here. Hope it helps.
Isotope.prototype._filter = function (items)
{
//console.log('FILTER ' + arguments.callee.caller.toString());
var filter = this.options.filter;
filter = filter || '*';
var matches = [];
var all_matches = [];
var hiddenMatched = [];
var visibleUnmatched = [];
var start, end;
//console.log('page: ' + this.options.page + ' items per page: ' +
this.options.items_per_page);
start = (this.options.page - 1) * this.options.items_per_page;
end = this.options.page * this.options.items_per_page;
//console.log('start: ' + start + ' end: ' + end + ' page: ' +
this.options.page);
// we want to set the current value of filteredItems to all items
before we sort
// we must sort first becuase we need to make sure we are showing
// the correct results for each page in the correct order
this.filteredItems = items;
this._sort();
if (this.options.activeClass === undefined ||this.options.activeClass === '')
{
this.options.activeClass = 'active';
}
var test = this._getFilterTest(filter);
// test each item
for (var i = 0, len = items.length; i < len; i++)
{
//console.log('item: ' + i, $(items[i].element).find('.grid-item-title .asset-link').text(), items[i]);
var item = items[i];
if (item.isIgnored)
{
continue;
}
// add item to either matched or unmatched group
var isMatched = test(item);
// add to matches if its a match
if (isMatched)
{
all_matches.push(item);
// before we add it to the matched set, let's make sure if falls within range
// it needs to be part of the current page set otherwise we filter it out
if (all_matches.length > start && all_matches.length <= end)
{
matches.push(item);
var odd_even = (matches.length % 2 === 0) ? 'even-item' : 'odd-item';
$(item.element).addClass(this.options.activeClass + ' ' + odd_even);
} else
{
// we need to reset isMatched if we're not using it on the current page
isMatched = false;
$(item.element).removeClass(this.options.activeClass + ' odd-item even-item');
}
}
// add to additional group if item needs to be hidden or revealed
if (isMatched && item.isHidden)
{
// reveal these items
hiddenMatched.push(item);
} else if (!isMatched && !item.isHidden)
{
// hide these items
visibleUnmatched.push(item);
}
}
var _this = this;
function hideReveal()
{
_this.reveal(hiddenMatched);
_this.hide(visibleUnmatched);
}
if (this._isInstant)
{
this._noTransition(hideReveal);
} else {
hideReveal();
}
// set all matches as a property on 'this' since we'll need it later to build the pager
this.all_matches = all_matches;
return matches;
};
H

Related

Multiple dependent dynamic dropdowns with repeating column dropdowns in Google Sheets

The Google Sheet I have uses code made by user Max Makhrov, code here, to make multiple dependent dynamic dropdowns in columns D-F (for location) and columns H-L (for objectives & activities) in my sample sheet here.
I would like help to modify the script to do two things:
Whatever activity is selected from the dropdown menu in Column I, I would like the same dropdown menu options to be available (to repeat) for columns J-L. As you can see I found a way to do it, but to me it seems clunky and not ideal, and leaves too much room for errors. Users should not select the activity twice, but I've put conditional formatting in to flag that if they do. However:
Ideally, but less importantly, if the dropdown menu items could still repeat for columns J-L but once an activity is selected in previous cells, that option is removed from each of the following repeated dropdown menus in additional columns, up to and including column L. This would help avoid accidentally repeating an activity.
NB: Reference question "How do you do dynamic / dependent drop downs in Google Sheets?"
Thank You!
When one of the drop-down cells is edited you can use an onEdit trigger [1] to iterate through the 4 columns (I-L) and update the drop-downs in each cell removing the option selected in the edited cell. You also need to add the old selected value (previously deleted from other options) to the other drop-downs. For this, you can use getDataValidation [2] and getCriteriaValues [3] functions chained to a Range object to retrieve the current drop-down values array on that range and delete the option matching with the selected option.
Use newDataValidation() [4] function to create a new rule using your updated drop-down values array and setDataValidation [5] function to set the rule to the range.
function onEdit(event) {
var range = event.range;
var sheetName = range.getSheet().getSheetName();
var col = range.getColumn();
var newValue = event.value;
var oldValue = event.oldValue;
//If the edited range is in sheet '3W' and beetween columns I-L
if(sheetName == '3W') {
if(col>=9 && col<=12) {
for(var i=9; i<13; i++) {
//Don't change anything for edited cell
if(col == i) { continue; }
else {
//Get range to update and current dropdown values for that range
var rangeToUpdate = range.getSheet().getRange(range.getRow(), i, 1, 1);
var dropdownValues = rangeToUpdate.getDataValidation().getCriteriaValues()[0];
//Find new edited value and delete it from options array
var index = dropdownValues.indexOf(newValue);
if (index > -1) {
dropdownValues.splice(index, 1);
}
//If previous selected value is not beetween the options, add it
if(oldValue && dropdownValues.indexOf(oldValue) == -1) {
Logger.log(oldValue)
dropdownValues.push(oldValue);
}
//Set new dropdown values to range
var updatedRule = SpreadsheetApp.newDataValidation().requireValueInList(dropdownValues, true).setAllowInvalid(false);
rangeToUpdate.setDataValidation(updatedRule);
}
}
}
}
}
Run just the first time to set all the drop-downs in columns I-L, which are get it from range E1:E10:
function setDropdownsInitially() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
//Range with the dropdown values
var sheet = ss.getSheetByName("indicators");
var dropdownValues = sheet.getRange("E1:E10").getValues();
//Data validation rule
var rule = SpreadsheetApp.newDataValidation().requireValueInList(dropdownValues, true).setAllowInvalid(false);
//Range where the dropdowns will be created
var targetSheet = ss.getSheetByName("3W");
var cells = targetSheet.getRange("I2:L");
//Set data validation rule
cells.setDataValidation(rule);
}
[1] https://developers.google.com/apps-script/guides/triggers/events#google_sheets_events
[2] https://developers.google.com/apps-script/reference/spreadsheet/range#getdatavalidation
[3] https://developers.google.com/apps-script/reference/spreadsheet/data-validation-builder.html#getcriteriavalues
[4] https://developers.google.com/apps-script/reference/spreadsheet/spreadsheet-app#newdatavalidation
[5] https://developers.google.com/apps-script/reference/spreadsheet/range#setdatavalidationrule

Google Sheets - Multiple dependent drop-down lists

I am trying to create a dependent list as described and answered (with a script) here.
I would like to achieve that if selecting a certain value (e.g. "First") from a cell in column 1, then the drop-down options from the next cell in the same row should offer a range of values from the column in a different sheet with the same heading as the value in the first - left - cell (i.e. the first sheet is called "Selector" - in which there are dropdowns, in the second sheet called "KAT" I have the options for these dropdowns). This should then be possible for every row depending on the value of each first cell of the row.
I have tried to use and adapt the suggested script and have reviewed the sample files in the article but I apparently lack some basic understanding of the script to be able to adapt and implement it properly.
Could anybody kindly help me with making this dynamic dropdown work properly?
Just to clarify my final intention: I would like to have this script working first to be able to use it on multiple files. My final goal, though, is to make self-filling dropdown lists and selectors, so that I could simply fill in the data in the "Selector" sheet and would then be able to select these same values later in the cells below (depending on the name (value) of the first cell in the row = first cell of the column holding validation range). I hope to be able to achieve this by using either Pivot table or any other formula in the "KAT" sheet that would aggregate my data from "Selector" sheet and feed them back as drop-down options ...).
Thank you for your help.
See the example sheet here
Code I used (as above):
function onEdit()
{
var ss = SpreadsheetApp.getActiveSpreadsheet(),
sheet = ss.getActiveSheet(),
name = sheet.getName();
if (name != 'Selector') return;
var range = sheet.getActiveRange(),
col = range.getColumn();
if (col != 1) return;
var val = range.getValue(),
dv = ss.getSheetByName('KAT'),
data = dv.getDataRange().getValues(),
catCol = data[0].indexOf(val),
list = [];
Logger.log(catCol)
for (var i = 1, len = 100; i < len; i++) // Problem is here, you have too many items in list! Cannot have more 500 items for validation
list.push(data[i][catCol]);
var listRange = dv.getRange(2,catCol +1,dv.getLastRow() - 1, 1)
Logger.log(list)
var cell = sheet.getRange(range.getRow(), col-1)
var rule = SpreadsheetApp.newDataValidation()
.requireValueInRange(listRange) // Use requireValueIn Range instead to fix the problem
.build();
cell.setDataValidation(rule);
Logger.log(cell.getRow())
}
This question deals with dynamic dropdown lists. A previous question and answer on StackOverflow (Google Sheets - Dependent drop-down lists) were referenced, and code from that answer was being unsuccessfully re-purposed.
The code in the question was not working for one reason: Line 20
var cell = sheet.getRange(range.getRow(), col-1)
In the referenced code, the dropdown list begins in Column F (col=6). The dependant dropdowns ranged to the left so the definition of the dependant column was "col-1". In the questioner's scenario, the dropdown list begins in Column A (col=1) and the dependant dropdowns range from left to right. However, this line of code was not changed to take into account the different layout. Rather than "col-1", it should be "col+1".
Other matters
In addition to this, lines 16 and 17 perform a loop to create an array that might be used for the dependant dropdown. However the loop is redundant because the dropdown is actual defined by creating and assigning a range on the "KAT" sheet.
Cell A2 of KAT includes a formula:
=sort(unique(Selector!$A$2:$A),1,true)
This may appear to be useful because it automatically adds any new dropdown value entered in "Selector" to a list of values in KAT. In reality it is unproductive, because the dependant dropdown build by the code works vertically rather than horizontally. So an additional row added to KAT does not, of itself, contribute to building the dependant dropdown.
The following code works to build the dependant drop down list. I have deliberately left a number of "Logger" entries in the code to assist the questioner in understanding how the code works.
function onEdit() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var name = sheet.getName();
if (name != 'Selector') return;
var range = sheet.getActiveRange();
var col = range.getColumn();
var dropdownrow = range.getRow(); // added for debugging and informationm
if (col != 1) return;
var val = range.getValue();
Logger.log("the cursor is in 'Selector' in cell = " + range.getA1Notation()); //DEBUG
Logger.log("That's row " + dropdownrow + ", and column " + col + ". The value selected = " + val); // DEBUG
var dv = ss.getSheetByName('KAT');
var data = dv.getDataRange().getValues();
var catCol = data[0].indexOf(val);
var list = [];
var KAT_data = dv.getDataRange();
var KAT_data_len = KAT_data.getLastRow(); // added to give 'for' loop a sensible range
Logger.log("The data range on KAT is " + KAT_data.getA1Notation() + ", and the last row of data = " + KAT_data_len); //DEBUG
Logger.log("KAT data = '" + data + "'"); // DEBUG
Logger.log("Found the dropdown cell value of '" + val + "' in KAT as item #" + catCol); //DEBUG
for (var i = 1, len = KAT_data_len; i < len; i++) { // Irrelevant because the data validation range is obtained by defining a range on KAT
// Problem is here, the unique command in A2 creates a blank row
// Logger.log("i="+i+", data = "+data[i][catCol]); // DEBUG
list.push(data[i][catCol]);
}
var listRange = dv.getRange(2, catCol + 1, dv.getLastRow() - 1, 1);
Logger.log("FWIW, this is the list after the loop= " + list); // DEBUG
Logger.log("The contents for the new data validation range (taken from KAT) is " + listRange.getA1Notation()); // DEBUG
Logger.log("The new validation range gets added to col = " + (col + 1)); // DEBUG
//var cell = sheet.getRange(range.getRow(), col-1); // governs the next validation range. Example validation worked right to left, but this sheet works left to right. So must ADD 1, not subtract 1.
var cell = sheet.getRange(range.getRow(), col + 1);
Logger.log("The cell to be assigned the new validation range will be " + cell.getA1Notation()); // DEBUG
var rule = SpreadsheetApp.newDataValidation().requireValueInRange(listRange).build(); // Build validation rule
cell.setDataValidation(rule); // assign validation range to new cell
}
Is this code worthwhile?
The code, as written and referenced, is limited to creating only one level of dependant dropdowns. To this extent it has very limited value. A different approach to creating dependant dropdowns is justified.
"How do you do dynamic / dependent drop downs in Google Sheets?" on StackOverflow has been a meeting place for discussing and updating techniques for dynamic dependant dropdowns since 2014. The latest update was in February 2018 by Max Makhrov. Thye code described here may be useful for the questioner.

Applying Google Apps Script for Dynamic Data Validation to Existing Sheet

So I'm using this script (credit to Chicago Computer Classes) for populating dynamic data validation of a Google Sheets cell based on what the user entered in a different cell.
For example, if they enter the sport "Football" in one cell, the next cell has data validation for "CFB, CFL, or NFL" but if they enter "Basketball" in the first cell then the second cell's data validation changes to "ABL, CBB, NBA, or WNBA" for examples.
The script is working fantastic and you are welcome to play with the sheet here
However ... here's my problem:
I have an existing spreadsheet with 9000 rows of data. I would like to apply this new data validation scheme to this spreadsheet. The script is triggered with the onEdit() function which works great when you are entering things one row at a time. But if I try to copy and paste a whole bunch of rows in the first column, only the first row of the second column triggers the onEdit and gets the new data validation while all the other rows of the second column are unchanged. I've also tried to "Fill Down" or "Fill Range" on the first column and they have the same result where the first row in the selected range gets the new data validation but the rest of the selection is unchanged.
And while it would work just fine if I was manually entering rows, I really don't feel like doing that 9000 times :)
How do I modify the script to trigger the function with data that's copy/pasted or filled down?
Thanks!
Script here:
function onEdit(){
var tabLists = "Leagues";
var tabValidation = "2018";
var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var datass = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(tabLists);
var activeCell = ss.getActiveCell();
if(activeCell.getColumn() == 6 && activeCell.getRow() > 1 && ss.getSheetName() == tabValidation){
activeCell.offset(0, 1).clearContent().clearDataValidations();
var makes = datass.getRange(1, 1, 1, datass.getLastColumn()).getValues();
var makeIndex = makes[0].indexOf(activeCell.getValue()) + 1;
if(makeIndex != 0){
var validationRange = datass.getRange(3, makeIndex, datass.getLastRow());
var validationRule = SpreadsheetApp.newDataValidation().requireValueInRange(validationRange).build();
activeCell.offset(0, 1).setDataValidation(validationRule);
}
}
}
You should use the event object, which will provide you with the range that was edited. What you're doing now is looking only at the "active cell", which doesn't leverage the benefits of the event object, and can also lead to bugginess when you make rapid changes.
Using the event object, when you make an edit to multiple cells at once (from copy/paste), you can then loop through the range and set your validations.
function onEdit(e) {
var editedRange = e.range;
var ss = editedRange.getSheet();
var tabValidation = "2018";
if(editedRange.getColumn() == 6 && editedRange.getRow() > 1 && ss.getSheetName() == tabValidation) {
var tabLists = "Leagues";
var tabListsSheet = e.source.getSheetByName(tabLists);
var makes = tabListsSheet.getRange(1, 1, 1, tabListsSheet.getLastColumn()).getValues(); // This won't change during execution, so call only once
var activeCell = editedRange.getCell(1,1); // Start with the first cell
var remainingRows = editedRange.getHeight();
while(remainingRows > 0) {
var cellValue = activeCell.getValue();
activeCell.offset(0, 1).clearContent().clearDataValidations(); // Always clear content & validations
if (cellValue != "") { // Add validations if cell isn't blank
var makeIndex = makes[0].indexOf(cellValue) + 1;
if(makeIndex != 0) {
var validationRange = tabListsSheet.getRange(3, makeIndex, tabListsSheet.getLastRow()-2);
var validationRule = SpreadsheetApp.newDataValidation().requireValueInRange(validationRange).build();
activeCell.offset(0, 1).setDataValidation(validationRule);
}
}
activeCell = activeCell.offset(1, 0); // Get the next cell down
remainingRows--; // Decrement the counter
}
}
}

Dependent dynamically populated dropdown list Google Sheet

I would simply like to autopopulate the Point Segment with all the segments linked to a specific Street, when the Street name is inputted.
When Street name is inputted into column C, Column D should have a dropdown containing only the point segments of that street.
While I realize this can simply be achieved by creating a filter in the Data tab, I am trying to create a form which does not allow this and therefore need to script it.
This is the Google sheet:
https://docs.google.com/spreadsheets/d/1QbTqPegE_GLj9V6x5uCNNXAoi0v12Pmaelhc7uaMknE/edit?usp=sharing
I have written this code, however I am having trouble filtering by Street.
function setDataValid_(range, sourceRange) {
var rule = SpreadsheetApp.newDataValidation().requireValueInRange(sourceRange,
true).build();
range.setDataValidation(rule);
}
function onEdit() {
var aSheet = SpreadsheetApp.getActiveSheet();
var aCell = aSheet.getActiveCell();
var aColumn = aCell.getColumn();
if (aColumn == 3 && aSheet.getName() == 'Sheet1') {
var range = aSheet.getRange(aCell.getRow(), aColumn + 1);
var sourceRange = aSheet.getRange('Sheet1!B2:B5131');
setDataValid_(range, sourceRange)
}
}
Any help would be much appreciated.
You're close, but you should use requireValueInList instead of requireValueInRange. You were passing sourceRange, which is equal to all of the point segments.
To accomplish the filtering, you need to look at all of the street values. If the street value matches the selection, then save the adjacent point segment to a separate list. Once you've saved all those point segments, then pass it to requireValueInList. To do this, you need to take advantage of getValues() to get the range values as an array and loop through it.
I've made a few other modifications:
In onEdit, you should use the an event object
Changed your variable names so they would be easier to understand
Added a check to make sure that the Street cell isn't blank (no need to trigger the action when you delete a value from the cell)
Added a function getPointSegments that does the filtering
Removed the setDataValid_ function as it made your code less readable, and in my opinion, wasn't worthy of being its own function
function onEdit(event) {
var eventSheet = event.range.getSheet();
var eventCell = event.range;
var eventColumn = eventCell.getColumn();
var eventValue = eventCell.getValue();
if (eventColumn == 3 && eventSheet.getName() == "Sheet1" && eventValue != "") {
var pointRange = eventSheet.getRange(eventCell.getRow(), eventColumn + 1);
var pointSegments = getPointSegments_(eventSheet, eventValue);
var rule = SpreadsheetApp.newDataValidation().requireValueInList(pointSegments, true).build();
pointRange.setDataValidation(rule);
}
}
function getPointSegments_(sheet, selectedStreet) {
var streetsAndPoints = sheet.getRange("A2:B").getValues();
var pointSegments = [];
for (var i=0; i<streetsAndPoints.length; i++) {
var street = streetsAndPoints[i][0];
var pointSegment = streetsAndPoints[i][1];
if (street === selectedStreet)
pointSegments.push(pointSegment);
}
return pointSegments;
}
Lastly, be sure that your data validations in the Street field look like this (and I would actually suggest "Reject input" on invalid data).

How to set cursor position to end of text in CKEditor?

Is there a way to set the cursor to be at the end of the contents of a CKEditor?
This developer asked too, but received no answers:
http://cksource.com/forums/viewtopic.php?f=11&t=19877&hilit=cursor+end
I would like to set the focus at the end of the text inside a CKEditor. When I use:
ckEditor.focus();
It takes me to the beginning of the text already inside the CKEditor.
Dan's answer got strange results for me, but minor change (in addition to typo fix) made it work:
var range = me.editor.createRange();
range.moveToElementEditEnd( range.root );
me.editor.getSelection().selectRanges( [ range ] );
According to the documentation for CKEditor 4, you can do the following once you have the editor object.
var range = editor.createRange();
range.moveToPosition( range.root, CKEDITOR.POSITION_BEFORE_END );
editor.getSelection().selectRanges( [ range ] );
Link: http://docs.ckeditor.com/#!/api/CKEDITOR.dom.selection (under selectRanges function).
After a bit of fiddling, I've got it to work with the following code:
$(document).ready(function() {
CKEDITOR.on('instanceReady', function(ev) {
ev.editor.focus();
var s = ev.editor.getSelection(); // getting selection
var selected_ranges = s.getRanges(); // getting ranges
var node = selected_ranges[0].startContainer; // selecting the starting node
var parents = node.getParents(true);
node = parents[parents.length - 2].getFirst();
while (true) {
var x = node.getNext();
if (x == null) {
break;
}
node = x;
}
s.selectElement(node);
selected_ranges = s.getRanges();
selected_ranges[0].collapse(false); // false collapses the range to the end of the selected node, true before the node.
s.selectRanges(selected_ranges); // putting the current selection there
}
});
The idea is:
Get the root node (not body)
Advance to next node, until there are no more nodes to advance to.
Select last node.
Collapse it
Set range
Here's a similar answer to #peter-tracey. In my case my plugin is inserting a citation. If the user has made a selection, I needed to disable the selection and place the cursor at the end of the sentence.
// Obtain the current selection & range
var selection = editor.getSelection();
var ranges = selection.getRanges();
var range = ranges[0];
// Create a new range from the editor object
var newRange = editor.createRange();
// assign the newRange to move to the end of the current selection
// using the range.endContainer element.
var moveToEnd = true;
newRange.moveToElementEditablePosition(range.endContainer, moveToEnd);
// change selection
var newRanges = [newRange];
selection.selectRanges(newRanges);
// now I can insert html without erasing the previously selected text.
editor.insertHtml("<span>Hello World!</span>");
CKEditor 3.x:
on : {
'instanceReady': function(ev) {
ev.editor.focus();
var range = new CKEDITOR.dom.range( ev.editor.document );
range.collapse(false);
range.selectNodeContents( ev.editor.document.getBody() );
range.collapse(false);
ev.editor.getSelection().selectRanges( [ range ] );
}
}
based on pseudo-code provided by the developers here:
https://dev.ckeditor.com/ticket/9546#comment:3
You have to focus editor, get document object, put it in range,
collapse range (with false parameter), select body (with
selectNodeContents), collapse it (with false parameter) and finally
select range. It is best to do it all in instanceReady event.
This is the easiest solution provided by the ckeditor API. I have tested it on IE10+, ff, safari and Chrome:
range = editor.createRange();
// the first parameter is the last line text element of the ckeditor instance
range.moveToPosition(new CKEDITOR.dom.node(editor.element.$.children[pos - 1]), CKEDITOR.POSITION_BEFORE_END)
range.collapse()
editor.getSelection().selectRanges([ range ])
This will work for sure.
CKEDITOR.config.startupFocus = 'end';
have you tried ckEditor.Selection.Collapse(false);
According to CKEditor 4 documentation, another option is:
const range = this.ckeditor.createRange()
range.moveToElementEditablePosition(range.root, true)
this.ckeditor.getSelection().selectRanges([range])
Link: https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_dom_range.html#method-moveToElementEditablePosition

Resources