Archive
Time span sliders in jQuery
I’ve been working on prototyping a new application that needed a way for users to schedule things in spans of time on multiple days. It should be quick and easy for people to pick arbitrary time spans on each day of the week. The main use case is to set a day time block, during business hours for example, or a block that excludes business hours. Someone might schedule the time span from 8am to 5pm, or they might schedule it to end at 8am and then start again at 5pm. I looked around but didn’t find any examples of doing anything like this that I liked.
I settled on a slider that you can invert. So you can set the range from 8am to 5pm, then click on a check box that flips the slider to mean everything except for 8am to 5pm. It needs to be visually obvious what this is doing, so the slider and text needs to reflect what is happening. I think the result is fairly easy to use and very workable. The layout looks something like this:
The other thing I wanted was to be able to drop the whole thing in a page easily based on code in one place. I anticipate it will be used in more than one location within the application. So I wanted to construct it as a jQueryUI widget that basically wraps the standard slider widget. I didn’t try to make it portable beyond this app. There are a few things I’ve done here that you wouldn’t want to do if you were making a normal jQueryUI plugin. For example, it carries the HTML for laying out the text and sliders within the widget. I think its illustrative for a prototype, but not elegant.
Posts on doing multiple sliders or time based sliders are not easy to find. The best one I found was from Marc Neuwirth. That post saved me some time in figuring out how to deal with the times on the slider. There is also an improved slider on the Filament Group’s site, but it doesn’t really answer the basic needs I had. The fact that I had a hard time finding examples from other people who’d already done the same thing is what prompted this post.
For a time slider, we need to convert the times to integers so we can use them for slider values. To convert the time to an integer, we split the time into hours and minutes, multiply the hours times 60, and add back the minutes. We do the reverse to turn the integer into a formatted time for display. In the code below these operations are done in two functions, named formatTime and getTime. These are single use functions I don’t have a need for elsewhere, so I’m carrying them inside the narrow scope. If I ended up having a similar need somewhere else I’d refactor that out to make it usable elsewhere.
Since I’m making a jQueryUI widget (sort of), I start with the basic self executing function and the widget factory method.
(function($){ $.widget("ui.timeslider", { }); })(jQuery);
To that, I need to add the options object, which will allow us to set our options for the widget and also double as the options we’ll pass to the individual sliders. That last bit gets a little sticky, because we’ll have a changing scope as we deal with events within seven different slider (sub)widgets. Usually I wouldn’t put the whole callback function in the options definition, since its harder to read and maintain, but its easier to make it portable to each of the sliders this way.
options: { animate: false, days: ["monday","tuesday","wednesday","thursday","friday","saturday","sunday"], distance: 0, max: 1440, min: 0, orientation: "horizontal", range: true, slide: function(event,ui){ function formatTime(time){ var hours = parseInt(time / 60 % 24); var minutes = parseInt(time % 60); minutes = minutes + ""; if (minutes.length == 1) { minutes = "0" + minutes; } return hours + ":" + minutes; } var startTime = formatTime(ui.values[0]); var endTime = formatTime(ui.values[1]); var selector = "#"+this.id; $(selector + "-time1").val(formatTime(ui.values[0])); $(selector + "-time2").val(formatTime(ui.values[1])); }, step: 5, value: 0, values: [360, 1080] },
Most of these options are the standard slider options. The only addition is the days array, which we’ll use to add the individual days to our results. Putting it in the options this way lets us pass in a different set of days if we wanted to (maybe Monday through Friday are more appropriate in some places). The slide callback is basically a typical callback for the slider widget. The id based selector will make more sense in a bit.
The rest of the widget is all in two methods: _create and _sliderhtml. _sliderhtml just holds our html to build the containers for the individual sliders. The underscore at the front of the method names are how we tell the jQueryUI factory that they are private.
_sliderhtml: function(day){ var displayday = day.charAt(0).toUpperCase() + day.substr(1); var html = ""+ '<div class="fieldrow spacer">' + '<div>' + '<label for="'+day+'-time" class="inline">'+displayday+':</label> ' + '<span id="'+day+'-midnight1" style="display:none">midnight to</span> '+ '<input type="text" name="'+day+'-time1" id="'+day+'-time1" value="6:00" class="blended"> - '+ '<input type="text" name="'+day+'-time2" id="'+day+'-time2" value="18:00" class="blended"> '+ '<span id="'+day+'-midnight2" style="display:none">to midnight</span> '+ '<span>Except <input type="checkbox" name="'+day+'-except" id="'+day+'-except"></span>' + '</div>' + '<div id="'+day+'"></div>' + '</div>'; return html; }
That’s not pretty, and the wrapping displayed here makes it look even worse. This is special use widget, intended for one application. I wouldn’t build more general use widgets with the HTML like this, because you have to edit the widget code to change the layout, but its fine for my limited purpose here. This is the HTML for one slider. We’ll pass it the day from the days array in the options and use the day name for our field names and element IDs. We change the first letter of the day’s name to upper case to make the label.
_create is the standard method that will be used by the jQueryUI to construct our widget. It looks like this:
_create: function(){ var self = this, o = this.options, dayslength = o.days.length; o.timeslider = this; function getTime(time){ var times = time.split(':'); time = (times[0] * 60) + parseInt(times[1]); return time; } for(var i=0; i < dayslength; i++){ self.element.append(self._sliderhtml(o.days[i])); $( "#" + o.days[i] ).slider(o); $( "#" + o.days[i] + "-time1" ).bind("change",function(){ var slider = "#" + this.id.substr(0,this.id.length - 6); var newval = getTime($("#"+this.id).val()); if(!isNaN(newval)) $(slider).slider("values" , 0 , newval); }); $( "#" + o.days[i] + "-time2" ).bind("change",function(){ var slider = "#" + this.id.substr(0,this.id.length - 6); var newval = getTime($("#"+this.id).val()); if(!isNaN(newval)) $(slider).slider("values" , 1 , newval); }); $( "#" + o.days[i] + "-except" ).bind("change",function(){ var excl = "#"+this.id; var sliderlength = excl.length - 7; var slider = excl.substr(0,sliderlength); var exclude = $(excl).is(":checked"); if(exclude){ $(slider).addClass("ui-widget-header"); $(slider).removeClass("ui-widget-content"); $(slider + " div").addClass("ui-widget-content"); $(slider + " div").removeClass("ui-widget-header"); $(slider + "-midnight1").css("display","inline"); $(slider + "-midnight2").css("display","inline"); } else { $(slider).addClass("ui-widget-content"); $(slider).removeClass("ui-widget-header"); $(slider + " div").addClass("ui-widget-header"); $(slider + " div").removeClass("ui-widget-content"); $(slider + "-midnight1").css("display","none"); $(slider + "-midnight2").css("display","none"); } }); } },
The interesting part, and basically the whole reason for the widget to exist, is in the for loop in this method. self.element is the element from the selector in our page. So we might create the widget like this:
$("#scheduler").timeslider();
In that case the DOM element with “scheduler” as the ID will be self.element within the _create method. We iterate through our options.days array and append the html from _sliderhtml for each day in the array. Then we put a slider widget in each of those blocks of HTML (line 16 above), passing it the same options array we’re using. It has the extra days array, but the slider widget will ignore that. The rest of the loop is adding event bindings to the three input elements embedded in our HTML.
The first two input elements hold the times. This makes grabbing the values extremely easy, because we have an input element with an ID. We put the values in these fields in the slide callback in the options (line 23 and 24 in the second code block in this post). Here we’re binding an onchange to these elements. That allows a user to click on the times and type in a new value, which will move the slider to what they typed. It might be that nobody will ever do that, but I think it’s a nice feature.
The third event we’re binding is a change event to our checkbox. The string manipulation on lines 29 and 30 is identifying the various elements that make up our layout. On line 31 we see if the checkbox is checked or not, and then depending on that we set styling on the slider.
As a last step, I added another checkbox at the top to disable the whole thing, but that’s not actually in the widget. When you click the checkbox it does this:
function(){ if($("#turnitoff").is(":checked")){ $("#scheduler").timeslider("disable"); } else { $("#scheduler").timeslider("enable"); } }
This highlights the benefits of using the jQueryUI widget factory for this exercise, if that wasn’t clear already. By using the widget like this, we get the disable and enable methods, and all the other standard widget methods built in.
This is prototype, and I’m not sure yet if it will make it into the production application. If it does (and I remember) I’ll update this to show it used in context. If it gets into production it will likely need some refinement. For example, the checkbox might be better as a button and “Except” is not the right label, and there are a few other things I’d tweak. In the meantime, I hope this is useful for someone.
Async doesn’t have race conditions
I’ve been noticing comments about race conditions in async code, most specifically javascript used server side as in node.js. As someone who has written more lines of blocking code than I care to think about, I can relate to the angst of people trying to make the mind bending transition to thinking asynchronously. I think that just the name “race condition” is an artifact of the age of blocking code. There are no race conditions in the async world. The term is about bugs in blocking code. In async code there are no races, because it isn’t about getting done in order, its about getting done when you’re done. Thinking in terms of avoiding race conditions isn’t about bugs, its thinking in the wrong paradigm. There is no spoon.
Testing variable types in Javascript
Anyone who has written more than a handful of lines of Javascript has found a need to find out the type of a variable. A quick search on their favorite search engine or in their reference book turns up typeof, which indeed tells you the type of a variable. It works great, except if you’re looking for arrays, null, or NaN.
The typical way that typeof is used is in tests:
var my_string = "llamas"; var my_number = 42; if( typeof my_string === "string" ){ // do something with my string } if( typeof my_number === "string" ){ // do something with my number }
This type of test works well for strings, numbers, functions, and undefined. You most often see it used for the first three of these. Most people check for undefined more directly:
if( something === undefined){ // treat it like it isn't defined }
This is particularly useful if you are wanting to know whether an argument for your function was included when the function was called.
var myfunction = function(foo){ if(foo === undefined) foo = "bar"; // now we can do something with it, knowing that it is defined. }
So, what do you do if you have a variable that might be an array or an object, and you need to know which it is? Using typeof doesn’t help, because it tells you that it’s an object either way. It is an object either way, actually, but that’s not very helpful.
The easiest test between a non-array object and an array is by checking for length. Arrays have a length, non-array objects do not. So if typeof says its an object, and its length !== undefined, then it is an array.
NaN is a special confusing case. NaN is a value representing “Not a Number,” except that of course typeof says that NaN is a number. It is useful for working with messed up math and bad dates, both of which are best detected as quickly as possible. NaN is returned by several Javascript methods such as parseFloat and parseINt. NaN is never equal to any number, including itself. So to test for NaN you use the aptly named isNaN(testValue), which returns true if testValue is indeed not a number.
Putting all this in a simple function that you can use to find the type of something is fairly easy:
function(thing){ return (thing === null) ? "null" : (typeof thing == "object" && thing.length !== undefined) ? "array" : (typeof thing == "number" && isNaN(thing)) ? "NaN" : typeof thing; }
If there’s a law against abusing the ternary operator anywhere, that is sure to break it.
One other thing worth noting about testing variable types is testing booleans. Using typeof we get “boolean” as the response if the operand is actually (===) true or false. So the following variables are all boolean, according to typeof:
var yes = true; var no = false; var another = ( 1 == 2 ); var tistrue = ( yes !== undefined );
However NaN, null and undefined, even though they test as false, aren’t boolean according to typeof. All of these test as false:
( null ) ( undefined ) ( false ) ( NaN ) ( 1 == 2 )
But only one of them (false) has a typeof Boolean.
Setting styles in Google Apps Scripts
I recently wrote a post reviewing Google Apps Scripts. Buried in that post was a code snippet that showed my workaround for the lack of CSS or global styling in Google Apps Scripts. The problem is that it is in the middle of my venting about some of this service’s limitations, and most people will likely overlook it. So I’m editing that post and putting that piece of it here on its own.
Google Apps Scripts doesn’t allow you to work with CSS at all. Instead, they provide a mechanism for setting in-line styles as style attributes to your HTML elements. This is done by using the setStyleAttribute method, which is available on several of the classes. This method takes two string arguments, a style name (“line-height”, “font-size”, and so on) and the value for that style setting (10px, 1.1em, 90%, ‘bold’, etc). These aren’t CSS styles, they are inline style attributes. Its been a while since I’ve worked with this attribute, and I may have forgotten some of what I once knew about using these. I found that it didn’t work well to set a “font” attribute to “110% bold serif,” you needed to set that with three separate setStyleAttribute calls. You can chain these, which is still verbose but does make it somewhat easier.
var label = app.createLabel("Text for the label").setStyleAttribute("font-size","110%").setStyleAttribute("font-weight","bold");
That works fine, except that you end up with LOTS of setStyleAttribute calls throughout your script, and if you want to change something uniformly you’ll be copy and pasting all over the place. That gets old fast. So I wrote a little function as a workaround for this issue, and it seems to work reasonably well.
First, I created an object to hold my global styles
var appStyles = { "tableheaders": { "font-weight": "bold", "font-size": "1.1em", "padding": "0px 5px 0px 5px", "text-decoration": "underline" } , "listtable": { "border-top": "1px solid #CCC" , "margin-top": "6px", "width": "99%" }, "helptext": { "font-size": ".9em", "color": "#999" } };
Actually, pasting that in I realized that you don’t need the quotes around the property names. My fingers have been typing too much JSON. That does work as it appears there though, and its what I’ve tested, so I’ll leave it for now.
The next part is the function to apply the styles, uncreatively called applyStyles:
function applyStyles(element, style){ if(!element.setStyleAttribute || !(style in appStyles)) return element; for(var attribute in appStyles[style]){ element.setStyleAttribute(attribute, appStyles[style][attribute]); } return element; }
Many of the classes in Google Apps Scripts support setStyleAttribute, but not all. So we’re checking before we try to use it. Then, we iterate over the attributes in the appStyles object and apply the styles that match the passed in style name to the element. You can pass any element that supports setStyleAttribute to this function and it will be styled, and instead of copied styles all across your script you have one place to tweak the style headings.
This isn’t an ideal solution, and if I was going to use it much I’d probably do more with it. However, it is a start at a way to turn unmanageable styles in Google Apps Scripts into something you can work with and maintain.
Google Apps Scripts
I was recently asked to review Google Apps Scripts as a potential platform for some simple business apps, tying spreadsheet data to Google sites. The organization already uses Google Apps, and it seems like a short leap to using Google Apps scripting. My conclusion from the review was to recommend looking for another platform for building apps, and integrate with other information in Google Apps in other ways. It might be that Google Apps Scripts are just too young, and the feature set will grow to fulfill its promise. At the moment, its just not there yet.
The first hurdle is that building apps in Google Apps Scripts is clunky, and doesn’t really feel like working with javascript. Javascript is flexible and easy to work with, if at times hard to wrap your head around its quirks. Working with Google App Scripts felt like going back a decade. A large part of that is because the Script platform is built around GWT. You don’t really work with HTML, CSS or the DOM. You can’t generate client side javascript or any kind of markup directly. Everything has to pass through a series of overlaid panels with grids and widget objects that you put field objects, labels, and text into. This works, and in the end you can make decent looking web pages out of it, but its very cumbersome.
There is no CSS that you can work with in Google Apps Scripts. In my original version of this post I had my workaround here, but in the interest of letting people find that without having to read through the rest of this, I pulled that out into its own post. The approach let me apply the whole set of styles to all “tableheaders” across the whole app, and if I wanted to change the styles I could change it one place and have it applied everywhere. That’s not particularly wonderful, but it sure saves a lot of setStyleAttribute calls. Without this approach, applying the four styles in my appStyles example above to the table headers would have taken four uses of setStyleAttribute for each column in each table that appeared in the app. This approach could be improved on significantly, but it solved my immediate headache.
What killed my ability to use Google Apps Scripts, though, was the lack of searches or queries for data in spreadsheets. When I started digging into this I assumed that Google would provide search capabilities, sort of like they do in the Spreadsheet data API. As far as I can tell this capability is completely missing, so far, from Google Apps Scripts. There’s a feature request for it, but it isn’t there yet.
This means that if you want to find something in a spreadsheet, you have to load the whole sheet (or range of cells) and iterate through the whole thing looking for the bit you’re actually needing. This works, but it is slow. It would be fine if you had a few dozen rows, but it doesn’t take much data to be getting into a thousand or few thousand records, and this isn’t an acceptable way to look up data. I wasn’t expecting this to be an approach suited for hundreds of thousands of records or anything, but from my quick tests it’s too slow for 1000 rows to be practical.
On top of that, it isn’t really asynchronous as we expect things to be in a modern platform. If I was building the same type of thing with jQuery or something, and parts of it took more time than I really wanted to keep users waiting (which these days isn’t long), I’d slap out the basic HTML and fill in all the pieces as they became available. That would hide the slower data on a panel or tab or whatever that isn’t displayed on start up, allowing a delayed load. You can’t really do that with Google Apps Scripts, since you can’t write custom client side code beyond the relatively few event driven callbacks they provide. From what I can tell, you pretty much have to load all of your data at the start, including data you aren’t going to use until people start clicking. That loses you those seconds to get your data in order that might otherwise be available before the user looks behind the curtain. There isn’t any way to swap out content, other than simple things like text values. You can’t manipulate the DOM. That’s no way to build a web application.
Most of the problems I had using Google Apps Scripts were related to how young it is. The documentation is incomplete at best. If you want to know what most of the classes do, you have to dig into the GWT documentation. Most of Google’s code documentation is decent (I recently had occasion to work through their OpenId documentation as well, but that’s a different post for a different day), and I expect that before too long they’ll fix this documentation as well.
But if this platform is really going to be useful Google is going to have to take significant steps to embrace javascript much more than they are here. Obviously they can. They have other things based on javascript that are awesome. Unfortunately, this isn’t one of them. Yet.