Exact filter when multiple values/html elements in a cell - yadcf

This might be more DataTables related than specifically YADCF but I'm asking it here as an obvious enhancement if the feature doesn't exist.
I have a column/s which can contain multiple value 'tags', similar to the YADCF examples. The filters to allow filtering by these tags generate correctly.
JSFiddle here https://jsfiddle.net/wallacio/622704ep/6/
See columns titled Area and Rig (excuse the horrific colours...!)
If I wish to see only the events in South, I need to use filter_match_mode: "exact" (to exclude those in South West, South East etc)
There is an event (8th down in unfiltered view) which contains tags "South" and "South East"
Event containing South and South East tags
When the filtering for "South", this event does not appear, presumably because the exact filter is matching against the contents of the cell as a whole and not against each discrete HTML element within it.
I could change filter_match_mode to "contains" but this would result in events which are tagged "South East" etc appearing, which is undesirable.
I've had a trawl through the source and can see that this is taken into account when generating the filter options, but apparently not when filtering, which is why I wonder if it's a limitation caused by how DataTables filters are implemented.

You can use the filter_type: 'custom_func', and write your own filtering logic for that specific column
column_number: 0,
filter_type: 'custom_func',
custom_func: myCustomFilterFunction,
data: ['one', 'two', 'three']
where myCustomFilterFunction looks something like
function myCustomFilterFunction(filterVal, columnVal) {
var found = false;
if (columnVal === '') {
return true;
}
switch (filterVal) {
case 'one':
found = //some logic goes here
break;
case 'two':
found = //some logic goes here
break;
case 'three':
found = //some logic goes here
break;
default:
found = false;
break;
}
if (found) {
return true;
}
return false;
}

Related

Vue: Setting Data by matching route query

I'm attempting to set data fields provided by an array based on the Vue Router query. For example, when someone lands on my website using example.com/?location=texas, I want to set the location data by an array.
An example the array:
locations {
{
slug: "texas",
tagline: "Welcome to Texas",
}, {
slug: "california",
tagline: "Welcome to California",
}
}
I know this should be done using a computed property, however I am unable to get anything functioning. I've tried simple tests like if (this.slug.location === "texas"), and I cannot get the location data to populate. I would also like to provide default data in case there are no route matches.
Any help is extremely appreciated!
Edit:
I can accomplish this in a very manual way. Right now, I'm setting the query in data by the following:
slug: this.$route.query.location
I can display specific text by doing something like:
h3(v-if="slug === 'texas'") This will show for texas
h3(v-else-if="slug === 'california'") This will show for California
h3(v-else) This is default
The issue with this approach is there are various elements I need to customize depending on the slug. Is there any way I can create an array, and move whichever array matches a key in an array to the data??
You should be able to access a query param using the following (link to Vue Router documentation):
this.$route.query.location
So based on what you listed I would do something like...
export default {
computed: {
displayBasedOnLocationQueryParam() {
switch(this.$route.query.location) {
case 'texas':
return 'Welcome to Texas'
default:
return 'hello there, generic person'
}
}
}
}
Note that I'm not using your array explicitly there. The switch statement can be the sole source of that logic, if need be.

Integrate Bootstrap typeahead js with Bootstrap Datatable

I am using bootstrap data-tables Datatables and bootstrap-taginput with typehead.js. I am new with bootstrap data-tables.
Here is the layout of my bootstrap data-tables Example and please consider Bootstrap tagging input box on top.
I want to search data-tables records with bootstrap tagging elements. but somehow i am unable to search with bootstrap tagging.
Thanks in advance.
If you start out with an empty array of the data you've got on your table you could do something clever by replacing the built in search box. In the example I'm linking to I don't care about one of the columns and the other columns need a little formatting:
var words = [];
var table = $('#example').DataTable({
"columns": [
null, {
"render": function(data, type, row) {
~words.indexOf(data) || words.push(data);
return data;
}
}, {
"render": function(data, type, row) {
var d = data.replace(/\, /g, " ");
~words.indexOf(d) || words.push(d);
return data.split(", ").join("<br/>");
}
}
],
"initComplete": function() {
var searchBox = $("#example_wrapper").find("input[type='search']");
var searchBoxHolder = searchBox.parent();
searchBox.empty().remove();
searchBoxHolder.append($("<input/>", {
"type": "text"
}).typeahead({
source: words,
afterSelect: function(word) {
table.search(word).draw();
}
}).on("keyup", function(x) {
if (words.indexOf($(x.target).val()) === -1) {
table.search($(x.target).val()).draw();
}
}));
}
});
Basically what we're doing here is creating a blank array of search terms then iterating over each second and third cell and adding the term to the array if it doesn't exist. In the case of the third cell I need to clear some formatting (extra comma). Then we get the original search box and it's parent. Remove the original and append the new one to the parent. We then set it up as a typeahead with the list of search terms. We need to make sure it still acts like the original so we add the keyup function. I hope that makes sense.
Working example is here, hope that helps.

Hiding a series by default in a spider plot

I have a spider plot in using the graphing library of Dojo defined like this:
require([
"dojox/charting/Chart",
"dojox/charting/themes/Claro",
"dojox/charting/plot2d/Spider",
"dojox/charting/action2d/Tooltip",
"dojox/charting/widget/SelectableLegend",
"dojox/charting/axis2d/Default"
], function (Chart, theme, Spider, Tooltip, Legend, Default) {
var chart = new Chart(element).setTheme(theme).addPlot("default", {
type: Spider,
radius: 200,
fontColor: "black",
labelOffset: "-20"
});
var colors = ["blue", "red", "green", "yellow", "purple", "orange", "teal",
"maroon", "olive", "lime", "aqua", "fuchsia"];
$.each(factors, function (index, factor) {
chart.addAxis(factor.name, {
type: Default,
min: factor.min,
max: factor.max
});
});
$.each(presets, function (pIndex, preset) {
var data = [];
$.each(factors, function (fIndex, factor) {
data[factor.name] = preset.values[fIndex];
});
chart.addSeries(preset.short, data, {
fill: colors[pIndex % colors.length]
});
});
new Tooltip(chart, "default");
chart.render();
new Legend({
chart: chart,
horizontal: false
}, $(element).next(".legend")[0]);
});
I add a series for every member of an array called presets and I use a selectable legend that lets the user turn them on or off as they want. However, what I can't seem to find in the docs is how to start a series in the unselected, not visible state? What I ideally want to do is cap the number of series visible when the page loads because in some cases I have up to 14 presets and it just looks a mess until the user deselects a bunch. So I'd like to have, say, every preset above the first 5 be hidden at the start.
Here's a crude fiddle I've knocked to demonstrate. What I want is to have some of the series unselected when the plot is first displayed.
Update: I tried adding this after adding my series:
var checkboxes = $(".dijitCheckBoxInput").each((index, elem) => {
if (index > 4) {
elem.click();
}
});
Which works, but seems very fragile. If they change the class assigned to checkboxes, it'll break. Also, it prohibits me using more than one set of dojo checkboxes because I don't have a good way to tell the difference. (Note, the IDs of the checkboxes added by the SelectableLegend are dijit_form_CheckBox_0, dijit_form_CheckBox_1, etc, which also gives no useful information as to what they are related to). I thought I might be able to use the legend placeholder div as a way to select the descendant checkboxes, but it appears that Dojo replaces the placeholder entirely with a table.
i looked into the dojo code and found the area in which the shapes are toggled on & off whitin the SelectableLegend.js :
var legendCheckBox = query(".dijitCheckBox", legend)[0];
hub.connect(legendCheckBox, "onclick", this, function(e){
this._toggle(shapes, i, legend.vanished, originalDyn, seriesName, plotName);
legend.vanished = !legend.vanished;
e.stopPropagation();
});
The toggling process is very complex and is based on many local attributes:
_toggle: function(shapes, index, isOff, dyn, seriesName, plotName){
arrayUtil.forEach(shapes, function(shape, i){
var startFill = dyn.fills[i],
endFill = this._getTransitionFill(plotName),
startStroke = dyn.strokes[i],
endStroke = this.transitionStroke;
if(startFill){
if(endFill && (typeof startFill == "string" || startFill instanceof Color)){
fx.animateFill({
shape: shape,
color: {
start: isOff ? endFill : startFill,
end: isOff ? startFill : endFill
}
}).play();
}else{
shape.setFill(isOff ? startFill : endFill);
}
}
if(startStroke && !this.outline){
shape.setStroke(isOff ? startStroke : endStroke);
}
}, this);
}
I tried also checking & unchecking the dijit/form/Checkbox in a legend manually, but that does not trigger the _toggle function in any case, even if you do a render() / fullrender() on the chart.
With that in mind it seems that there is no other possibilty to toggle the series on and off than by firing the onclick events manually.
To make your code less fragile, you could access the Checkbox widgets within the legend manually using:
query(".dijitCheckBox", legend); // Should deliver an array containing
the widgets.
and triggering the onclick event on them. Their keynumber in the array should correspond to the order the series where added...
Dojo is a fine piece of work, please dont stop working with it !
dojox/charting/Series has an attribute called dirty which according to the API docs is a "flag indicating whether or not this element needs to be rendered".
Alternately, if you are limiting the display of some series you can write a separate interface for adding them. For example, loop over the first 5. Then create a select box or list of check boxes with all entries and an onchange event that calls chart.addSeries.
Keeping a reference to each series you create will allow you to later call destroy() or destroyRecursive() on it if the user no longer wishes it displayed.
So while ideally you could toggle the display of these series, the worst case senerio is that you just add, destroy, and read based on some user input.
Using a templated widget will allow you to keep this interface and the chart tightly linked and support reuse.
BTW, consider using "dojo/_base/array" and "dojo/query" in place of the jquery
I think i've got it !
I found another way to access the checkboxes ! It's the same way dojo uses internally to connect the "toggle code" to the onclick event. First take a look at this from SelectableLegend.js (Lines 150 - 156):
// toggle action
var legendCheckBox = query(".dijitCheckBox", legend)[0];
hub.connect(legendCheckBox, "onclick", this, function(e){
this._toggle(shapes, i, legend.vanished, originalDyn, seriesName, plotName);
legend.vanished = !legend.vanished;
e.stopPropagation();
});
It looks like they use the ".dijitCheckBox" class to find the checkbox dom element and connect to it using dojo/connect. Now based on that, i made this function:
function toggleSeries (legend,num) {
dojo.query("*",legend.legends[num])[0].click();
dijit.findWidgets(legend.legends[num])[0]._onClick(); }
It doesn't use any class definition (because of the *) and it accesses the areas where the checkboxes are from within the SelectableLegend. It needs the SelectableLegend and the number of the series you want to deactivate as parameters. Here the jsfiddle example with this function & hiding all 4 of your series with it:
http://jsfiddle.net/luciancd/92Dzv/17/
Also please notice the "onDomReady" Option in jsfiddle, without it: doesnt work in IE.
And the ready function within the code !
Lucian
I have updated your code http://jsfiddle.net/92Dzv/18/
Here is the key to toogle.
dom.byId(le._cbs[0].id).click();
dom.byId(le._cbs[2].id).click();
Choose the index of your legend and set to _cbs.
By this way le._cbs[0].id you will get the real id of checkbox (that inside in the widget) and then just use click()
Note : le is came from here.
var le = new Legend({
chart: chart,
horizontal: false
}, legend);

Partial Address Search / Typeahead

We have an address field we want to provide typeahead for. It sits behind a login, although if we needed to we could get crafty and make that one page public for licensing compliance.
The Google Maps API is getting locked down. We used to use the "reverse geocode" portion of it to perform partial address search / typeahead for addresses - so for example if the user typed:
1600 Penn
I could hit the service and get back several suggestions, like:
1600 Pennsylvania Avenue, Washington, DC
There are several other partial address searches out there I've come across but they each have problems.
Google: $10000/yr minimum for just 7500 requests/day - ridiculous
Yahoo: Shutdown this year
Bing: Requires the page to be public / not behind login. This isn't a hard stop for us, but it would be a challenging redesign of how the page works.
Mapquest OpenStreetMap API: Searches for the exact string rather than a leading string - so it returns Penn Station instead of 1600 Pennsylvania Ave.
Mapquest OpenStreetMap data: We could download all of this and implement our own, but the CPU and data requirements would likely be too much to bite off for the time being.
We're fine with paying for usage, we'd just seek a solution closer to Amazon's $0.01/10000 requests than Google's.
UPDATE: My original answer, intact below, was given before SmartyStreets had an address autocomplete offering, which is free with a LiveAddress API subscription.
It's simple, really. The USPS has approved certain vendors for address verification/standardization services. I work for one such company, SmartyStreets, and in my experience, what you are trying to do is probably easier than you think with a good API that has a REST endpoint. Look into the LiveAddress API.
Submit a street address and at least a city/state, or zip, or both, and you'll get a list of suggested results.
NOTICE, however, that non-CASS providers such as Google Maps do address approximation, not address validation. Google - and others - make a "best guess" which is what Google and them are expert at. If you want actual existing addresses, make sure you find a service that does just that. I'll add that LiveAddress is free now and offers better performance than, for example, the USPS API.
How comes that doing it yourself isn't an option? IMO a partial search or a typeahead isn't so hard to do with a ternary trie on the address, street, city etc.pp field.
Google has released Google Places Autocomplete which resolves exactly this problem.
At the bottom of a page throw this in there:
<script defer async src="//maps.googleapis.com/maps/api/js?libraries=places&key=(key)&sensor=false&callback=googPlacesInit"></script>
Where (key) is your API key.
We've set our code up so you mark some fields to handle typeahead and get filled in by that typeahead, like:
<input name=Address placeholder=Address />
<input name=Zip placeholder=Zip />
etc
Then you initialize it (before the Google Places API has loaded typically, since that's going to land async) with:
GoogleAddress.init('#billing input', '#shipping input');
Or whatever. In this case it's tying the address typeahead to whatever input has name=Address in the #billing tag and #shipping tag, and it will fill in the related fields inside those tags for City, State, Zip etc when an address is chosen.
Setup the class:
var GoogleAddress = {
AddressFields: [],
//ZipFields: [], // Not in use and the support code is commented out, for now
OnSelect: [],
/**
* #param {String} field Pass as many arguments as you like, each a selector to a set of inputs that should use Google
* Address Typeahead via the Google Places API.
*
* Mark the inputs with name=Address, name=City, name=State, name=Zip, name=Country
* All fields are optional; you can for example leave Country out and everything else will still work.
*
* The Address field will be used as the typeahead field. When an address is picked, the 5 fields will be filled in.
*/
init: function (field) {
var args = $.makeArray(arguments);
GoogleAddress.AddressFields = $.map(args, function (selector) {
return $(selector);
});
}
};
The script snippet above is going to async call into a function named googPlacesInit, so everything else is wrapped in a function by that name:
function googPlacesInit() {
var fields = GoogleAddress.AddressFields;
if (
// If Google Places fails to load, we need to skip running these or the whole script file will fail
typeof (google) == 'undefined' ||
// If there's no input there's no typeahead so don't bother initializing
fields.length == 0 || fields[0].length == 0
)
return;
Setup the autocomplete event, and deal with the fact that we always use multiple address fields, but Google wants to dump the entire address into a single input. There's surely a way to prevent this properly, but I'm yet to find it.
$.each(fields, function (i, inputs) {
var jqInput = inputs.filter('[name=Address]');
var addressLookup = new google.maps.places.Autocomplete(jqInput[0], {
types: ['address']
});
google.maps.event.addListener(addressLookup, 'place_changed', function () {
var place = addressLookup.getPlace();
// Sometimes getPlace() freaks out and fails - if so do nothing but blank out everything after comma here.
if (!place || !place.address_components) {
setTimeout(function () {
jqInput.val(/^([^,]+),/.exec(jqInput.val())[1]);
}, 1);
return;
}
var a = parsePlacesResult(place);
// HACK! Not sure how to tell Google Places not to set the typeahead field's value, so, we just wait it out
// then overwrite it
setTimeout(function () {
jqInput.val(a.address);
}, 1);
// For the rest, assign by lookup
inputs.each(function (i, input) {
var val = getAddressPart(input, a);
if (val)
input.value = val;
});
onGoogPlacesSelected();
});
// Deal with Places API blur replacing value we set with theirs
var removeGoogBlur = function () {
var googBlur = jqInput.data('googBlur');
if (googBlur) {
jqInput.off('blur', googBlur).removeData('googBlur');
}
};
removeGoogBlur();
var googBlur = jqInput.blur(function () {
removeGoogBlur();
var val = this.value;
var _this = this;
setTimeout(function () {
_this.value = val;
}, 1);
});
jqInput.data('googBlur', googBlur);
});
// Global goog address selected event handling
function onGoogPlacesSelected() {
$.each(GoogleAddress.OnSelect, function (i, fn) {
fn();
});
}
Parsing a result into the canonical street1, street2, city, state/province, zip/postal code is not trivial. Google differentiates these localities with varying tags depending on where in the world you are, and as a warning, if you're used to US addresses, there are places for example in Africa that meet none of your expectations of what an address looks like. You can break addresses in the world into 3 categories:
US-identical - the entire US and several countries that use a similar addressing system
Formal addresses - UK, Australia, China, basically developed countries - but the way their address parts are broken up/locally written has a fair amount of variance
No formal addresses - in undeveloped areas there are no street names let alone street numbers, sometimes not even a town/city name, and certainly no zip. In these locations what you really want is a GPS location, which is not handled by this code.
This code only attempts to deal with the first 2 cases.
function parsePlacesResult(place) {
var a = place.address_components;
var p = {};
var d = {};
for (var i = 0; i < a.length; i++) {
var ai = a[i];
switch (ai.types[0]) {
case 'street_number':
p.num = ai.long_name;
break;
case 'route':
p.rd = ai.long_name;
break;
case 'locality':
case 'sublocality_level_1':
case 'sublocality':
d.city = ai.long_name;
break;
case 'administrative_area_level_1':
d.state = ai.short_name;
break;
case 'country':
d.country = ai.short_name;
break;
case 'postal_code':
d.zip = ai.long_name;
}
}
var addr = [];
if (p.num)
addr.push(p.num);
if (p.rd)
addr.push(p.rd);
d.address = addr.join(' ');
return d;
}
/**
* #param input An Input tag, the DOM element not a jQuery object
* #paran a A Google Places Address object, with props like .city, .state, .country...
*/
var getAddressPart = function(input, a) {
switch(input.name) {
case 'City': return a.city;
case 'State': return a.state;
case 'Zip': return a.zip;
case 'Country': return a.country;
}
return null;
}
Old Answer
ArcGis/ESRI has a limited typeahead solution that's functional but returns limited results only after quite a bit of input. There's a demo here:
http://www.esri.com/services/disaster-response/wildlandfire/latest-news-map.html
For example you might type 1600 Pennsylvania Ave hoping to get the white house by the time you type "1600 Penn", but have to get as far as "1600 pennsylvania ave, washington dc" before it responds with that address. Still, it could have a small benefit to users in time savings.

ExtJs 4 : Tree grid panel filter

I am using ExtJs 4 with a Tree panel on west region and TreeGrid panel on center region. Is there any way to filter the TreeGrid panel(center region) on selection of the treepanel(west) ??
I tried the following but no luck :
Ext.define('MyApp.view.MyViewport', {
extend: 'MyApp.view.ui.MyViewport',
initComponent: function() {
var me = this;
me.callParent(arguments);
me.down('#westTreePanel').getSelectionModel().on('selectionchange',me.CenterTreeFilter,me);
}, //end of initComponent
CenterTreeFilter: function(){
var selection = this.down('#westTreePanel').getView().getSelectionModel().getSelection()[0];
var centerTreeGrid = this.down('#centerTreeGrid');
console.log(selection.data.text);
centerTreeGrid.store.filterBy(function(rec, id){
console.log(rec);
return (rec.store("text") == selection.data.text);
});
console.log("sub store : " + this.down('#centerTreeGrid').getStore().storeId);
}
});
After days of fighting with this issue, I was finally able to get the functionality, albeit in a not so satisfying way. Also, only leaf nodes are currently hidden.
filtering all nodes that don't mention "text":
t.getRootNode().cascadeBy(function(n){
if (!n.hasChildNodes() &&
n.raw && n.raw.text.toLowerCase().indexOf(text.toLowerCase()) < 0) {
toRemove.push({ node: n, parent: n.parentNode });
}
});
To restore later, run:
function restoreTrees() {
for (var n in toRemove) {
toRemove[n].parent.appendChild(toRemove[n].node);
}
toRemove = [];
}
There are many flaws with this solution. Including that the restored tree will probably have a different ordering for their nodes. But hey, at least this is some progress.
Would love to see a better one! (Had it working beautifully in Ext JS 3, but now these darn nodes don't have a .ui.hide() function any more).
i've checked their example http://dev.sencha.com/deploy/ext-4.0.2a/examples/tree/treegrid.html, in fact the issue here is that the store for the treeGrid is a tree store which doesn;t have the method filterBy , the method filterBy is defined in ext.data.store and treeStore extends ext.data.abstractStore.. as i see it you have to apply your filters diferently, using the the filters config for the treeStore. You can define your filter and set the filterOnLoad on true and instead of calling the filterBy method do centerTreeGrid.store.fireEvent('load',selection). I hope this helps you
Edit
I haven't used filters for tree stores but i think you can do something like this
var treeFilter = new Ext.util.Filter({
filterFn: function(rec) {
console.log(rec);
return (rec.store("text") == selection.data.text);
});
And assign the filter to the treeStore in the initComponent
centerGrid.store.filters.add(treeFilter);
centerGrid.store.filterOnLoad = true;
And in the CenterTreeFilter function
centerGrid.store.fireEvent('load',selection);
P.s the code is untested and probably it will need some modifications, but i think this is the way to do it.