Bootstrap Multiselect filtering values based on another dropdown - bootstrap-multiselect

I'm working on cascading the multi-select based on the values of the data-attributes on another.
I have this method which is called inside the onChanged event of the parent. By parent I mean what to filter the child by i.e
Parent = Department Multiselect
Child = Staff Multiselect.
The child element has a data attribute of data-departmentid.
function cascadeMultiselect(control, targetControl, key) {
this.control = control;
this.targetControl = targetControl;
this.key = key;
this.toHide = [];
this.toShow =[];
//Get controls selectedIds
this.selectedIds = function() {
var selected = [];
_.each($(this.control + " option:selected"), function(c) {
selected.push($(c).val());
});
return selected;
};
//Now filter
this.filter = function() {
//Get target control attribute values
_.each($(targetControl + " option"), function(tc) {
//Use the val as an identifier
var val = $(tc).val();
var data = $(tc).attr("data-" + key);
data = data.split(",");
var isMatch = anyMatchInArray(data, this.selectedIds());
if(!isMatch){
$(tc).hide();
}
});
}
this.filter();
$(targetControl).multiselect('rebuild');
}
It's being called like so:
onChange: function() {
cascadeMultiselect('#departmentlist', '#stafflist', "DepartmentID");
}
The problem is I can't hide the elements. The filter works just fine, I've tried:
$(tc).hide(); // tc = targetControl option
I've also tried to refresh instead of rebuild.

Since it seems the only way to do this was to store the values of the multiselect, and then remove / add the relevant values.
I decided to use a rudimentary Cache class:
var Cache = (function () {
this.all = [];
function Cache(control) {
this.control = control;
}
Cache.prototype.getAll = function () {
return this.all;
};
Cache.prototype.setAll = function (all) {
this.all = all;
};
return Cache;
})();
Function now looks like:
function cascadeMultiselect(control, targetControl, key, cache) {
this.control = control;
this.targetControl = targetControl;
this.key = key;
this.toHide = [];
this.toShow =[];
//To reset the multiselect
this.reset = function(){
$(targetControl).html('');
$(targetControl).multiselect('dataprovider', cache.all)
}
//Get controls selectedIds
this.selectedIds = function() {
var selected = [];
_.each($(this.control + " option:selected"), function(c) {
selected.push($(c).val());
});
return selected;
};
//Now filter
this.filter = function() {
//Get target control attribute values
_.each($(targetControl + " option"), function(tc) {
//Use the val as an identifier
var val = $(tc).val();
var data = $(tc).attr("data-" + key);
data = data.split(",");
var isMatch = anyMatchInArray(data, this.selectedIds());
console.log($(tc));
if(!isMatch){
$(tc).remove();
}
});
}
//If nothing selected then reset the multiselect
if(this.selectedIds().length == 0){
this.reset();
return;
}else{
this.filter();
}
$(targetControl).multiselect('rebuild');
}
When I first populate the multiselect with the dataprovider I add the data to the cache:
if(cache != null){
cache.setAll(this.dataToAdd);
}

Related

Mapbox GL Menu Doesn't Respond to First Click

Similar to this question, the layer switcher in my Map does not react to the first click. I learned that this issue has something to do with the definition of the initial visibility on the layers.
Here is the code. Unfortunately I don't know how and where to insert default visibility - do you have an idea?
map.on('click', ({ point }) => {
const features = map.queryRenderedFeatures(point, {
layers: ['City'] // replace with your layer name
});
if (!features.length) {
return;
}
const feature = features[0]; const popup = new mapboxgl.Popup({offset: [0, -15], closeButton: false, closeOnMove: true})
.setMaxWidth("auto")
.setLngLat(feature.geometry.coordinates)
.setHTML(
`<table>\
<tr>\
<td>City</td>\
<td>${feature.properties.City}</td>
</tr>\ </table>`
)
.addTo(map); }); var toggleableLayerIds = ['City']
for (var i = 0; i < toggleableLayerIds.length; i++) {
var id = toggleableLayerIds[i]
var link = document.createElement('a')
link.href = '#'
link.className = 'active'
link.textContent = id
link.onclick = function (e) {
var clickedLayer = this.textContent
e.preventDefault()
e.stopPropagation()
var visibility = map.getLayoutProperty(clickedLayer, 'visibility')
if (visibility === 'visible') {
map.setLayoutProperty(clickedLayer, 'visibility', 'none')
this.className = ''
} else {
this.className = 'active'
map.setLayoutProperty(clickedLayer, 'visibility', 'visible')
}
}
var layers = document.getElementById('menu')
layers.appendChild(link)
}

Removing appended isotope items

I'm appending isotope items via Ajax in Wordpress:
My JS Code:
var $news_container = $('#news'); //The ID for the list with all the blog posts
$news_container.isotope({ //Isotope options, 'item' matches the class in the PHP
itemSelector: '.newsItem',
masonry: {
columnWidth: '.news-item-sizer',
gutter: '.gutter-sizer'
}
});
var has_run = false;
var init_offset = 0;
$('button.showall').click(function(e) {
e.preventDefault();
var button = $(this);
// Disable button
$(button).removeClass('showall');
$(button).addClass('showless');
// Record Nonce
var nonce = $(this).data("nonce");
if(has_run == false) {
button.data('offset', $(this).data("offset"));
init_offset = $(this).data("offset");
}
// Set AJAX parameters
data = {
action: 'mft_load_more_ajax',
init_offset: init_offset,
offset: button.data('offset'),
nonce: nonce
};
$.post(mft_load_more_ajax.ajaxurl, data, function(response) {
// Set Container Name
var response = JSON.parse(response);
console.log(response);
// Run through JSON
$.each( response, function( key, value ) {
// Set Value
var val = $(value);
// Set Container
var $container = $('#news').isotope();
// Append Val
$container.append(val).isotope( 'appended', val );
$(button).html('show less');
});
// Set Offset
var offset = button.data("offset");
button.data("offset", offset + 11 );
// If Has Run
has_run = true;
return false;
}
Until now, this works quite fine. Now I would like to switch the buttontext and it's class to .showless and on the next click all previously appended items should be removed. They all have the class .newsItem.appendedItem.
I tried this method:
$('button.showless').click(function(e) {
var button = $(this);
console.log('showless');
$out = $('.newsItem.appendedItem');
var isotopeInstance = $('#news').data('isotope');
isotopeInstance.$allAtoms = isotopeInstance.$allAtoms.not($out);
$out.remove();
// Disable button
$(button).removeClass('showless');
$(button).addClass('showall');
has_run = false;
return false;
});
Unfortunately this doesn't work, because the showless function is not even being entered, as I don't get a log in the console. What am I overlooking?
Thanks for your help!
Cara
Update 1:
I'm getting this error in Google Console.
Not sure, what was the problem now. But I cleaned the code a little bit with using an if statement, to check if the elements already got appended.
So my final code now looks like this:
var $news_container = $('#news'); //The ID for the list with all the blog posts
$news_container.isotope({ //Isotope options, 'item' matches the class in the PHP
itemSelector: '.newsItem',
masonry: {
columnWidth: '.news-item-sizer',
gutter: '.gutter-sizer'
}
});
var is_appended = false;
$('button.showall').click(function(e) {
e.preventDefault();
var button = $(this);
// Record Nonce
var nonce = $(this).data("nonce");
if(is_appended == false) {
button.data('offset', $(this).data("offset"));
// Disable button
button.prop( "disabled" , true );
// Set AJAX parameters
data = {
action: 'mft_load_more_ajax',
offset: button.data('offset'),
nonce: nonce
};
$.post(mft_load_more_ajax.ajaxurl, data, function(response) {
// Set Container Name
var response = JSON.parse(response);
console.log(response);
// Run through JSON
$.each( response, function( key, value ) {
// Set Value
var val = $(value);
// Set Container
var $container = $('#news').isotope();
// Append Val
$container.append(val).isotope( 'appended', val );
});
// Undo Button Disable
button.prop( "disabled" , false );
button.html('Weniger News anzeigen');
// Set Offset
// var offset = button.data("offset");
// button.data("offset", offset + 11 );
// If Was appended
is_appended = true;
return false;
});
} else if(is_appended == true) {
$out = $('.newsItem.appendedItem');
$news_container.isotope( 'remove', $out )
// layout remaining item elements
.isotope('layout');
button.html('Alle News anzeigen');
is_appended = false;
return false;
}
});
In Chrome everything works perfectly. But just figured out, that in Firefox my Ajax array is completely empty and I'm not able to fetch any data. Probably another problem, I'm going to post separately.

nightwatch assert ALL elements, not ANY

I'm trying to test that, when the Submit button is clicked on an empty form, all the "please fill in this field" labels are displayed.
I'm doing so with this:
page.click('#btn_submit');
page.expect.element('#validation_label_required').to.be.visible;
where #validation_label_required is represented by the CSS selector:
input[required] ~ p.error-message-required
However, this test passes if ANY of the validation labels are visible. The test should only pass if they ALL are.
How can I achieve this?
You will need to create a custom assertion for that where you locate all elements by selenium commands and then loop to verify condition. It should look something like this
var util = require('util');
exports.assertion = function (elementSelector, expectedValue, msg) {
this.message = msg || util.format('Testing if elements located by "%s" are visible', elementSelector);
this.expected = expectedValue;
this.pass = function (value) {
return value === this.expected;
};
this.value = function (result) {
return result;
};
this.command = function (callback) {
var that = this.api;
this.api.elements('css selector',elementSelector, function (elements) {
elements.value.forEach(function(element){
that.elementIdDisplayed(element.ELEMENT,function(result){
if(!result.value){
callback(false);
}
});
});
callback(true);
});
return this;
};
};
I've just ended up with another custom assertion that check how many elements are visible by given css selector.
/**
* Check how many elements are visible by given css selector.
*
*/
var util = require('util');
exports.assertion = function(elementSelector, expectedCount, msg) {
this.message = msg || util.format('Asserting %s elements located by css selector "%s" are visible', expectedCount, elementSelector);
this.expected = expectedCount;
this.count = 0;
this.pass = function(value) {
return value === this.expected;
};
this.value = function(result) {
return this.count;
};
this.command = function(callback) {
var me = this, elcount = 0;
this.count = 0;
this.api.elements('css selector', elementSelector, function(elements) {
if(elements.value && elements.value.length > 0){
elcount = elements.value.length;
}else{
return callback(false);
}
elements.value.forEach(function(element) {
me.api.elementIdDisplayed(element.ELEMENT, function(result) {
if (result.value) {
me.count++;
}
elcount--;
if (elcount === 0) {
callback(me.count);
}
});
});
});
};
};

Kendo Grid always focus on first cell of Top Row

I have checkbox in Kendo grid. Once i click on Checkbox it always focus the top cell in Kendo Grid. Below is code for Kendo grid that I am binding to checkbox value on checkbox click event in Kendo Grid
$("#contactgrid").on('click', '.chkbx', function () {
var checked = $(this).is(':checked');
var grid = $('#contactgrid').data().kendoGrid;
var rowIdx = $("tr", grid.tbody).index(row);
var colIdx = $("td", row).index(this);
// grid.tbody.find("tr").eq(rowIndex).foucs(); This doesn't work
var dataItem = grid.dataItem($(this).closest('tr'));
dataItem.set('IsSelected', checked);
});
I can get the row index and cell Index in click event but I was not able to figure out to focus the specific cell.
Thanks!
When you want to edit Grid with checkbox then I would suggest you to use the approach from this code library. No matter it uses the MVC extensions open Views/Home/Index.cshtml and see how the template is defined and the javascript used after initializing the Grid.
Here it is
Column template:
columns.Template(#<text></text>).ClientTemplate("<input type='checkbox' #= IsAdmin ? checked='checked':'' # class='chkbx' />")
.HeaderTemplate("<input type='checkbox' id='masterCheckBox' onclick='checkAll(this)'/>").Width(200);
<script type="text/javascript">
$(function () {
$('#persons').on('click', '.chkbx', function () {
var checked = $(this).is(':checked');
var grid = $('#persons').data().kendoGrid;
var dataItem = grid.dataItem($(this).closest('tr'));
dataItem.set('IsAdmin', checked);
})
})
function checkAll(ele) {
var state = $(ele).is(':checked');
var grid = $('#persons').data().kendoGrid;
$.each(grid.dataSource.view(), function () {
if (this['IsAdmin'] != state)
this.dirty=true;
this['IsAdmin'] = state;
});
grid.refresh();
}
</script>
I struggled with this. I essential refocused the cell as shown below. There's plenty of room for improvement in the Kendo grid client-side API. Hopefully my helper methods below will help people out.
var $row = getRowForDataItem(this);
var $current = getCurrentCell($row);
var currentCellIndex = $row.find(">td").index($current);
this.set('PriceFromDateTime', resultFromDate);
$row = getRowForDataItem(this);
var grid = getContainingGrid($row);
//select the previously selected cell by it's index(offset) within the td tags
if (currentCellIndex >= 0) {
grid.current($row.find(">td").eq(currentCellIndex));
}
//Kendo grid helpers
function getColumn(grid, columnName) {
return $.grep(grid.columns, function (item) {
return item.field === columnName;
})[0];
}
function getRowForDataItem(dataItem) {
return $("tr[data-uid='" + dataItem.uid + "']");
}
function getCurrentCell($elem) {
return getContainingGrid($elem).current();
}
function getContainingDataItem($elem) {
return getDataItemForRow(getContainingRow($elem));
}
function getContainingCell($elem) {
return $elem.closest("td[role='gridcell']");
}
function getContainingRow($elem) {
return $elem.closest("tr[role='row']");
}
function getContainingGrid($elem) {
return $elem.closest("div[data-role='grid']").data("kendoGrid");
}
function getGridForDataItem(dataItem) {
return getContainingGrid(getRowForDataItem(dataItem));
}
function getDataItemForRow($row, $grid) {
if (!$grid) $grid = getContainingGrid($row);
return $grid.dataItem($row);
}
function getMasterRow($element) {
return $element.closest("tr.k-detail-row").prev();
}
function getChildGridForDataItem(dataItem) {
return getRowForDataItem(dataItem).next().find("div.k-grid").data("kendoGrid");
}
function getMasterRowDataItem($element) {
var $row = getMasterRow($element);
return getDataItemForRow($row);
}

SlickGrid filter not working

I am fairly new to SlickGrid. I am trying to make SlickGrid filter work but no luck. I am following the example (http://mleibman.github.io/SlickGrid/examples/example4-model.html).
Below is my source code.
$(document).ready(function() {
var tName;
$('#submit').click(function(e) {
tName = $('#source option:selected').text();// name1
tName = tName.trim();
$.ajax({
url : 'someUrl',
type : 'GET',
cache : false,
success : function(d) {
var grid;
var searchString = "";
var data = [];
var columns = new Array();
var cols = d[0].columns;
var pkColNames = d[0].pkColNames;
for (var j=0; j< cols.length; j++) {
var key = {id: cols[j].colName, name: cols[j].colName, field: cols[j].colName, width: 200, sortable:true, editor: Slick.Editors.LongText};
columns[j] = key;
}
var options = {
editable: true,
enableAddRow: false,
enableCellNavigation: true,
asyncEditorLoading: false,
enableColumnReorder:true,
multiColumnSort: false,
autoEdit: false,
autoHeight: false
};
function myFilter(item, args) {
return true;// Let us return true all time ?
}
for (var i = 0; i < d.length; i++) {
var tempData = (data[i] = {});
var title = null;
var val = null;
for (var q = 0; q < d[i].columns.length; q++) {
title = d[i].columns[q].colName;
val = d[i].columns[q].colValue;
tempData[title] = val;
}
}
var dataView = new Slick.Data.DataView({ inlineFilters: true });
grid = new Slick.Grid("#myGrid", dataView, columns, options);
dataView.setPagingOptions({
pageSize: 25
});
var pager = new Slick.Controls.Pager(dataView, grid, $("#myPager"));
var columnpicker = new Slick.Controls.ColumnPicker(columns, grid, options);
grid.setSelectionModel(new Slick.CellSelectionModel());
grid.onAddNewRow.subscribe(function(e, args) {
// Adding a new record is not yet decided.
});
grid.onCellChange.subscribe(function (e) {
var editedCellNo = arguments[1].cell;
var editedColName = grid.getColumns()[editedCellNo].field;
var newUpdatedValue= arguments[1].item[grid.getColumns()[editedCellNo].field];
var editedColType = "";
for (var cnt = 0; cnt < cols.length; cnt++) {
if (editedColName == cols[cnt].colName) {
editedColType = cols[cnt].colType;
break;
}
}
var pkKeyValue="";
for (var v=0; v <pkColNames.length;v++) {
for (var p=0; p<grid.getColumns().length; p++) {
if (pkColNames[v] == grid.getColumns()[p].field) {
var value = arguments[1].item[grid.getColumns()[p].field];
pkKeyValue += "{"+pkColNames[v] + '~' +getColDbType(grid.getColumns()[p].field) + ":"+value+"},";
break;
}
}
}
function getColDbType(colName) {
for (var c = 0; c < cols.length; c++) {
if (colName == cols[c].colName) {
return cols[c].colType;
}
}
}
pkKeyValue = pkKeyValue.substring(0, pkKeyValue.length-1);
$.ajax({
url: 'anotherUrl',
type:'GET',
dataType:'text',
success: function(f) {
bootbox.alert("Data updated successfully");
},
error: function() {
bootbox.alert("Error - updating data. Please ensure you are adding the data in right format.");
grid.invalidateAllRows();
grid.render();
}
});
});
grid.onClick.subscribe(function (e) {
//do-nothing
});
dataView.onRowsChanged.subscribe(function(e, args) {
grid.updateRowCount();
grid.invalidateRows(args.rows);
grid.render();
});
grid.onSort.subscribe(function(e, args) {
// args.multiColumnSort indicates whether or not this is a multi-column sort.
// If it is, args.sortCols will have an array of {sortCol:..., sortAsc:...} objects.
// If not, the sort column and direction will be in args.sortCol & args.sortAsc.
// We'll use a simple comparer function here.
var comparer = function(a, b) {
return a[args.sortCol.field] > b[args.sortCol.field];
}
// Delegate the sorting to DataView.
// This will fire the change events and update the grid.
dataView.sort(comparer, args.sortAsc);
});
// wire up the search textbox to apply the filter to the model
$("#txtSearch").keyup(function (e) {
console.log('Called...txtSearch');
Slick.GlobalEditorLock.cancelCurrentEdit();
// clear on Esc
if (e.which == 27) {
this.value = "";
}
searchString = this.value;
updateFilter();
});
function updateFilter() {
console.log("updateFilter");
dataView.setFilterArgs({
searchString: searchString
});
dataView.refresh();
}
dataView.beginUpdate();
dataView.setItems(data, pkColNames);
dataView.setFilterArgs({
searchString: searchString
});
dataView.setFilter(myFilter);
dataView.endUpdate();
},
error : function() {
bootbox.alert("Invalid user");
}
});
});
});
Your function myFilter() is always returning true so of course it will never work. The example that you looked at, was to filter something specific. I would recommend that you look at the following example to have a generic filter. From the example, simply type the text you are looking on a chosen column and look at the result... see example below (from SlickGrid examples).Using fixed header row for quick filters
In case you want a more in depth conditional filters ( > 10, <> 10, etc...), please take a look at my previous answer on how to make this kind of filtering possible, see my previous answer below:SlickGrid column type
Hope that helps you out

Resources