selectively using different range sliders using Algolia instantsearch.js - javascript

With Algolia Instantsearch.js I'm looking to selectively use different rangeSliders depending on the value of a passed country_code parameter in the query string.
If e.g: the country_code is 'FR' I want to use a total_area_meters range slider, and use another range slider that uses euros. For the 'UK' I want to use total_area_ft and another range slider that uses pounds.
Though I'm waiting (and hoping) for https://github.com/algolia/instantsearch.js/issues/753 to be implemented, I've been told by Algolia support personnel that it's potentially possible to use the helper library: https://github.com/algolia/algoliasearch-helper-js to get this working.
I have e.g: experimented with selectively enabling disjunctive facets using:
search.addWidget({
init: function (opts) {
// opts.helper contains the underlying algoliasearchHelper
if(detected_locale === 'fr'){
opts.helper.setQueryParameter('disjunctiveFacets', [
'total_area_meters','price_eur'
]);
//...
Though doing this is not enough: among other issues, there are still slider widgets on the page e.g: requiring 'total_area_feet, and 'price_gbp' which raise javascript errors - I need to disable them somehow, and also there are quite obtrusive urls that need to be selectively removed.
e.g: If my disjunctive facet is: total_area_meters: I get a url like this:
&nR[total_area_meters][>=][0]=135&nR[total_area_meters][<=][0]=770 - which needs to be removed when I have a country_code where feet are used, instead.
So my questions about this are:
Is there a way to selectively enable/disable instantsearch.js rangeslider widgets programmatically via js? (or can I somehow programmatically set their values/ reset them so they have no query parameters?)
If not covered in the solution above, are there any pre-built functions for me to clean up the rangeslider url parameters? Thank you!

With help from Tim Carry at Algolia, I was able to get a work-around working. It's not perfect, but in a nutshell, I first added all my sliders to the page. I then selectively hid them using:
.hideClass {
display: none;
}
$sliders[i].addClass('hideClass');
// or
$sliders[i].removeClass('hideClass');
This performs much better than:
$sliders[i].show()
//and
$sliders[i].hide()
Depending on the country_code in the url.
I found that it's (unfortunately) necessary to re-issue hiding/showing commands on each render call (not just on init()) e.g:
search.addWidget( {
render: function(opts) {
show_hide_sliders();
}
});
It's possible to remove all url parameters for a slider using:
helper.removeNumericRefinement(slider_attribute[i]);
which is only issued if there is a country_code change.
And it pretty-much seems to function (though caveat: only limited testing so far). Hope this is helpful to someone.
EDIT:
To respond to the comments, a note: in this case I got a handle on the helper by using opts.helper (but I probably might have also succeeded using search.helper, too.). e.g:
search.addWidget( {
render: function(opts) {
show_hide_sliders();
opts.helper. ... // add your helper methods...
}
});

Related

how to handle javascript that only runs on certain pages when bundled together

When working on small client sites, I often end up working with a main.js file that includes a bunch of jQuery plugins and small toggle functionality. Some of these code snippets are only relevant on certain pages, but ends up bundled together in one main.min.js file.
My question is, how do people write the individual code snippets in order to only execute that code when the correct page is being rendered?
Here's an example: Let's say I have a page with a search input field. This input is hooked up with jQuery autocomplete in order to show search suggestions as the user types. the code in main.js could look something like this:
var data = [
{
value: 'some value',
data: 'some data'
},
{...}
]
$('#autocomplete').autocomplete({
lookup: data,
lookupLimit: 10,
minChars: 3,
});
This code is only useful on the template that has that input field, but as main.js contains a bunch of other smaller bits like this that are useful globally and on other pages, the whole file is loaded on every pageview. What strategy should I use to only execute that piece of code when the page needs it?
I though of a few ways my self:
Check if the DOM-element (in this case #autocomplete) exists.
Check if the URL is == '/page-with-autocomplete'.
Use a class on , and check for that class i n order to run the script.
Other ideas? Any standard way to do this sort of thing? Anything considered a "best practice"?
Stick your JS in an if block and check for the unique DOM element on the page you want the script to run.
Although you can't just do:
if ( $('#my-el') ) {}
You have to check if the element has a length, like:
if ( $('#my-el').length ) {}

Extjs 4.1 pagingtoolbar default page

I want to change default page of pagination toolbar to 1 of 1 instead 0 of 0 in case of no record.Plus I am not using store proxy to request any records, so is there any way to accomplish it without using store proxy. According to my requirement user can add rows manually to the grid with the pagination toolbar showing page 1 and when rows exceeds 10 it moves to 2nd page.
In Ext it is possible to overload a component like Ext.toolbar.Paging with your own custom version. Simply specify an alias in your definition and you can us it just like the "native control."
In order to be sure that the approach would work, I set up a test project with a simple datasource and implemented enough of a replacement definition that I could see the "Ext.toolbar.Paging".getPagingItems method being fired in my custom definition.
From that point you can replace the code inside the definition of the original method to allow for a custom minimum in addition to the opportunity to overload the "updateInfo" method to make sure that during data reloads you're not plowing through your customizations.
In addition to these two things, you should (with a relatively small amount of effort) be able to implement on top of the control to support dynamically changing it's values based on the contents of your grid.
If you look at the documentation for ux.data.PagingStore you should be able to suss out the differences in using a remotely supplied store from something that is served with data locally.
Hope this helps you.
Code Sample:
Ext.define(
"Test.view.testview.TvPageBar",
{
extend: "Ext.toolbar.Paging",
alias: "widget.tvpagebar",
title: "Bob",
strictInit: function () {
"use strict";
console.log("TvPageBar init");
},
getPagingItems: function () {
console.log("getPagingItems", this);
this.callParent(arguments);
},
initComponent: function () {
this.strictInit();
this.callParent(arguments);
}
}
);

Using multiple jQuery plugins on multiple pages?

I have site which is using a few JavaScript/jQuery plugins, I then have a default.js file which uses the plugin functionality for each of the different pages.
E.g. some plugins I have include:
a custom scrollbar plugin
a slideshow plugin
a cookie plugin
etc.
Then in the default.js file, I'll do something as follows (pseudocode):
var scrolling = findScrollbarDiv;
scrolling.ScrollFunction({ options });
(and then the same for the slideshow + other plugins I have)
However, if there is a plage where findScrollbarDiv returns null, I get an error something like the following:
ScrollFunction is not a function
This is not because the elements are returning null, but because I haven't included the plugin file for this page. My reasoning behind this is that I don't want to include every file on every page (even if it's not needed) as this could cause unnecessary HTTP requests (especially on the homepage, which only needs one plugin)
This error in turn messes up the rest of the JavaScript.
What is the best way to overcome this? Should I just include every plugin file on every page regardless of whether it is needed or not? Or is there some JavaScript like the following that I can use:
var scrolling = findScrollbarDiv;
if(scrolling != null) {
scrolling.ScrollFunction({ options });
}
(this feels a bit clunky to me, but if this is the best solution let me know)
Or is using one default js file to launch all plugins a bad idea?
If you're using the same header file and you don't mind having the file included, you can always check if the element exists before attaching an event to it.
Heres an example:
if(jQuery('#someElement').length > 0){
jQuery('#someElement').ScrollFunction({ options });
}
Either that, or have a JavaScript file for each function.
So you'd have one for the gallery and one for the cookies etc.. And include only the ones necessary for each page.
You can just do it as a conditional statement and avoid the clunky if:
scrolling && scrolling.ScrollFunction({ options });
This works great for any method you want to call and want to check if the parent object exists before you do.
You can go further (if you need to) and check if the method itself exists before execution:
scrolling && scrolling.ScrollFunction && scrolling.ScrollFunction({ options });
if scrolling is a collection, just check it's length (if length == 0 the statement will fail):
scrolling.length && scrolling.ScrollFunction({ options });

jQuery, What's Best, Have All the Binds in One Single File For an Entire Site or on a per Page Basis?

I'm in the middle of building a web app with heavy use of jQuery plugins and lots of bindings.
The backend was developed with a template system which only allows (as of now) to place all scripts in that one HTML file. We will use YUI compressor to merge all these into one.
Now, for bindings, how bad is it to have binds in an HTML file (which now is a template for the whole site) for elements that may not be present on a particular page?
Any advice is greatly appreciated
I've been using Paul Irish's markup-based solution pretty extensively on larger sites.
One of the biggest problems with doing this is one of performance - the selector will be evaluated and the DOM searched for each binding not intended for a specific page. At the very least, perhaps set up an object literal to run appropriate ready binding code based on a page identifier, which could be the window.location.href or a substring of. Something like
// avoid global pollution!
(function() {
var pages = {
pageX : {
ready: function() { /* code to run on ready */ },
teardown: function() { /* code to run on teardown */ }
},
pageY : {
ready: function() { /* code to run on ready */ },
teardown: function() { /* code to run on teardown */ }
},
}
// set up ready event handler
$(ready);
// handler function to execute when ready event raised
// Note: Access to pages through closure
function ready() {
var location = window.location.href;
pages[location].ready();
}
})();
Be careful with your selectors if you've got some large pages. For example, if you've got some pages with big, but inert (no bindings) tables, but other pages where tables are small but have controls in them, you probably don't want to do this:
$('td.bindMe').bind('whatever', function() { ... });
(Set aside the live() issue here; sometimes you need to do element-by-element work and that's what I'm talking about.) The problem is that Sizzle will have to look through all the td elements on the page, potentially. Instead, you can put some sort of "marker" container around things like the "active" table with controls, and work it that way:
$('table#withControls').find('td.bindMe').bind(/* ... */);
That way Sizzle only needs to figure out that there's no table called "withControls", and then it's done.
Biggest problem for using all bindings on all pages is that you can get bindings that you did not intended to have, causing troubles...
And of course you will have some performance issues in the page load, but if that is a problem is of course depending on how many bindings you have and how the code looks like.
You might lose some performance on the client side (parsing the file, executing the document-ready handler), but it improves caching on the client (i.e. the file doesn't need to be transferred more than once). That saves server lookups as well. I think this is rather an advantage than a disadvantage as long as you can ensure you're not accidentally modifying objects.
I think the selector engine is fast enough that you, or anyone else, shouldn't notice a difference.
Obviously this is not a "best practice," but if you're binding to ID's and classnames and you won't have any conflicts or unintended bindings then I don't see the harm.

Event handling in Dojo

Taking Jeff Atwood's advice, I decided to use a JavaScript library for the very basic to-do list application I'm writing. I picked the Dojo toolkit, version 1.1.1. At first, all was fine: the drag-and-drop code I wrote worked first time, you can drag tasks on-screen to change their order of precedence, and each drag-and-drop operation calls an event handler that sends an AJAX call to the server to let it know that order has been changed.
Then I went to add in the email tracking functionality. Standard stuff: new incoming emails have a unique ID number attached to their subject line, all subsequent emails about that problem can be tracked by simply leaving that ID number in the subject when you reply. So, we have a list of open tasks, each with their own ID number, and each of those tasks has a time-ordered list of associated emails. I wanted the text of those emails to be available to the user as they were looking at their list of tasks, so I made each task box a Dijit "Tree" control - top level contains the task description, branches contain email dates, and a single "leaf" off of each of those branches contains the email text.
First problem: I wanted the tree view to be fully-collapsed by default. After searching Google quite extensively, I found a number of solutions, all of which seemed to be valid for previous versions of Dojo but not the one I was using. I eventually figured out that the best solution would seem to be to have a event handler called when the Tree control had loaded that simply collapsed each branch/leaf. Unfortunately, even though the Tree control had been instantiated and its "startup" event handler called, the branches and leaves still hadn't loaded (the data was still being loaded via an AJAX call). So, I modified the system so that all email text and Tree structure is added server-side. This means the whole fully-populated Tree control is available when its startup event handler is called.
So, the startup event handler fully collapses the tree. Next, I couldn't find a "proper" way to have nice formatted text for the email leaves. I can put the email text in the leaf just fine, but any HTML gets escaped out and shows up in the web page. Cue more rummaging around Dojo's documentation (tends to be out of date, with code and examples for pre-1.0 versions) and Google. I eventually came up with the solution of getting JavaScript to go and read the SPAN element that's inside each leaf node and un-escape the escaped HTML code in it's innerHTML. I figured I'd put code to do this in with the fully-collapse-the-tree code, in the Tree control's startup event handler.
However... it turns out that the SPAN element isn't actually created until the user clicks on the expando (the little "+" symbol in a tree view you click to expand a node). Okay, fair enough - I'll add the re-formatting code to the onExpand() event handler, or whatever it's called. Which doesn't seem to exist. I've searched to documentation, I've searched Google... I'm quite possibly mis-understanding Dojo's "publish/subscribe" event handling system, but I think that mainly because there doesn't seem to be any comprehensive documentation for it anywhere (like, where do I find out what events I can subscribe to?).
So, in the end, the best solution I can come up with is to add an onClick event handler (not a "Dojo" event, but a plain JavaScript event that Dojo knows nothing about) to the expando node of each Tree branch that re-formats the HTML inside the SPAN element of each leaf. Except... when that is called, the SPAN element still doesn't exist (sometimes - other times it's been cached, just to further confuse you). Therefore, I have the event handler set up a timer that periodically calls a function that checks to see if the relevant SPAN element has turned up yet before then re-formatting it.
// An event handler called whenever a "email title" tree node is expanded.
function formatTreeNode(nodeID) {
if (dijit.byId(nodeID).getChildren().length != 0) {
clearInterval(nodeUpdateIntervalID);
messageBody = dijit.byId(nodeID).getChildren()[0].labelNode.innerHTML
if (messageBody.indexOf("<b>Message text:</b>") == -1) {
messageBody = messageBody.replace(/>/g, ">");
messageBody = messageBody.replace(/</g, "<");
messageBody = messageBody.replace(/&/g, "&");
dijit.byId(nodeID).getChildren()[0].labelNode.innerHTML = "<b>Message text:</b><div style=\"font-family:courier\">"+messageBody+"</div>";
}
}
}
// An event handler called when a tree node has been set up - we changed the default fully-expanded to fully-collapsed.
function setupTree(theTree) {
dijit.byId("tree-"+theTree).rootNode.collapse();
messageNode = dijit.byId("tree-"+theTree).rootNode.getChildren();
for (pl = 0; pl < messageNode.length; pl++) {
messageNode[pl].collapse();
messageNode[pl].expandoNode.onclick = eval("nodeUpdateIntervalID = setInterval(\"formatTreeNode('"+messageNode[pl].id+"')\",200); formatTreeNode('"+messageNode[pl].id+"');");
}
}
The above has the feel of a truly horrible hack, and I feel sure I must have taken a wrong turn somewhere early on in my thought process. Can someone please tell me:
The correct way to go about putting nicely-formatted text inside a Dojo/Dijit Tree control.
The correct way to handle Dojo events, like where I can figure out what events are available for me to subscribe to.
A better JavaScript library to use (can I do what I want to with JQuery and avoid the all-around-the-houses approach seen above?).
PS: If you're naming a software project, give thought to its name's uniqueness in Google - I'm sure searching for "Dojo" documentation in Google would be easier without all the martial arts results getting in the way.
PPS: Firefox spellchecker knows how to spell "Atwood", correcting me when I put two 'T's instead of one. Is Jeff just that famous now?
I assume that you followed the dijit.Tree and dojo.data in Dojo 1.1 tutorial which directed you to pass the data to the tree control using a data store. That had me banging my head of a brick wall for a while.
Its not really a great approach and the alternative is not really well documented. You need to create a use model instead. I have included an example below of a tree model that I created for displaying the structure of an LDAP directory.
You will find the default implementation of the model in your dojo distribution at ./dijit/_tree/model.js. The comments should help you understand the functions supported by the model.
The IDirectoryService class the code below are stubs for server-side Java POJOs generated by Direct Web Remoting (DWR). I highly recommend DWR if you going to be doing a lot of client-server interaction.
dojo.declare("LDAPDirectoryTreeModel", [ dijit.tree.model ], {
getRoot : function(onItem) {
IDirectoryService.getRoots( function(roots) {
onItem(roots[0])
});
},
mayHaveChildren : function(item) {
return true;
},
getChildren : function(parentItem, onComplete) {
IDirectoryService.getChildrenImpl(parentItem, onComplete);
},
getIdentity : function(item) {
return item.dn;
},
getLabel : function(item) {
return item.rdn;
}
});
And here is an extract from the my JSP page where I created the model and used it to populate the tree control.
<div
dojoType="LDAPDirectoryTreeModel"
jsid="treeModel"
id="treeModel">
</div>
<div
jsid="tree"
id="tree"
dojoType="dijit.Tree" model="treeModel"
labelAttr="name"
label="${directory.host}:${directory.port}">
</div>

Categories

Resources