24ways

Custom SQL query returning 2 rows (hide)

Query parameters

rowidtitlecontentsyearauthorauthor_slugpublishedurltopic
124 Writing Responsible JavaScript Without a doubt, JavaScript has been making something of a comeback in the last year. If you’re involved in client-side development in any way at all, chances are that you’re finding yourself writing more JavaScript now than you have in a long time. If you learned most of your JavaScript back when DHTML was all the rage and before DOM Scripting was in vogue, there have been some big shifts in the way scripts are written. Most of these are in the way event handlers are assigned and functions declared. Both of these changes are driven by the desire to write scripts that are responsible page citizens, both in not tying behaviour to content and in taking care not to conflict with other scripts. I thought it may be useful to look at some of these more responsible approaches to learn how to best write scripts that are independent of the page content and are safely portable between different applications. Event Handling Back in the heady days of Web 1.0, if you wanted to have an object on the page react to something like a click, you would simply go ahead and attach an onclick attribute. This was easy and understandable, but much like the font tag or the style attribute, it has the downside of mixing behaviour or presentation in with our content. As we’re learned with CSS, there are big benefits in keeping those layers separate. Hey, if it works for CSS, it should work for JavaScript too. Just like with CSS, instead of adding an attribute to our element within the document, the more responsible way to do that is to look for the item from your script (like CSS does with a selector) and then assign the behaviour to it. To give an example, take this oldskool onclick use case: <a id="anim-link" href="#" onclick="playAnimation()">Play the animation</a> This could be rewritten by removing the onclick attribute, and instead doing the following from within your JavaScript. document.getElementById('anim-link').onclick = playAnimation; It’s all in the timing Of course, it’s never quite that easy. To be able to attach that onclick, the element you’re targeting has to exist in the page, and the page has to have finished loading for the DOM to be available. This is where the onload event is handy, as it fires once everything has finished loading. Common practise is to have a function called something like init() (short for initialise) that sets up all these event handlers as soon as the page is ready. Back in the day we would have used the onload attibute on the <body> element to do this, but of course what we really want is: window.onload = init; As an interesting side note, we’re using init here rather than init() so that the function is assigned to the event. If we used the parentheses, the init function would have been run at that moment, and the result of running the function (rather than the function itself) would be assigned to the event. Subtle, but important. As is becoming apparent, nothing is ever simple, and we can’t just go around assigning our initialisation function to window.onload. What if we’re using other scripts in the page that might also want to listen out for that event? Whichever script got there last would overwrite everything that came before it. To manage this, we need a script that checks for any existing event handlers, and adds the new handler to it. Most of the JavaScript libraries have their own systems for doing this. If you’re not using a library, Simon Willison has a good stand-alone example function addLoadEvent(func) { var oldonload = window.onload; if (typeof window.onload != 'function') { window.onload = func; } else { window.onload = function() { if (oldonload) { oldonload(); } func(); } } } Obviously this is just a toe in the events model’s complex waters. Some good further reading is PPK’s Introduction to Events. Carving out your own space Another problem that rears its ugly head when combining multiple scripts on a single page is that of making sure that the scripts don’t conflict. One big part of that is ensuring that no two scripts are trying to create functions or variables with the same names. Reusing a name in JavaScript just over-writes whatever was there before it. When you create a function in JavaScript, you’ll be familiar with doing something like this. function foo() { ... goodness ... } This is actually just creating a variable called foo and assigning a function to it. It’s essentially the same as the following. var foo = function() { ... goodness ... } This name foo is by default created in what’s known as the ‘global namespace’ – the general pool of variables within the page. You can quickly see that if two scripts use foo as a name, they will conflict because they’re both creating those variables in the global namespace. A good solution to this problem is to add just one name into the global namespace, make that one item either a function or an object, and then add everything else you need inside that. This takes advantage of JavaScript’s variable scoping to contain you mess and stop it interfering with anyone else. Creating An Object Say I was wanting to write a bunch of functions specifically for using on a site called ‘Foo Online’. I’d want to create my own object with a name I think is likely to be unique to me. var FOOONLINE = {}; We can then start assigning functions are variables to it like so: FOOONLINE.message = 'Merry Christmas!'; FOOONLINE.showMessage = function() { alert(this.message); }; Calling FOOONLINE.showMessage() in this example would alert out our seasonal greeting. The exact same thing could also be expressed in the following way, using the object literal syntax. var FOOONLINE = { message: 'Merry Christmas!', showMessage: function() { alert(this.message); } }; Creating A Function to Create An Object We can extend this idea bit further by using a function that we run in place to return an object. The end result is the same, but this time we can use closures to give us something like private methods and properties of our object. var FOOONLINE = function(){ var message = 'Merry Christmas!'; return { showMessage: function(){ alert(message); } } }(); There are two important things to note here. The first is the parentheses at the end of line 10. Just as we saw earlier, this runs the function in place and causes its result to be assigned. In this case the result of our function is the object that is returned at line 4. The second important thing to note is the use of the var keyword on line 2. This ensures that the message variable is created inside the scope of the function and not in the global namespace. Because of the way closure works (which if you’re not familiar with, just suspend your disbelief for a moment) that message variable is visible to everything inside the function but not outside. Trying to read FOOONLINE.message from the page would return undefined. This is useful for simulating the concept of private class methods and properties that exist in other programming languages. I like to take the approach of making everything private unless I know it’s going to be needed from outside, as it makes the interface into your code a lot clearer for someone else to read. All Change, Please So that was just a whistle-stop tour of a couple of the bigger changes that can help to make your scripts better page citizens. I hope it makes useful Sunday reading, but obviously this is only the tip of the iceberg when it comes to designing modular, reusable code. For some, this is all familiar ground already. If that’s the case, I encourage you to perhaps submit a comment with any useful resources you’ve found that might help others get up to speed. Ultimately it’s in all of our interests to make sure that all our JavaScript interoperates well – share your tips. 2006 Drew McLellan drewmclellan 2006-12-10T00:00:00+00:00 https://24ways.org/2006/writing-responsible-javascript/ code
132 Tasty Text Trimmer In most cases, when designing a user interface it’s best to make a decision about how data is best displayed and stick with it. Failing to make a decision ultimately leads to too many user options, which in turn can be taxing on the poor old user. Under some circumstances, however, it’s good to give the user freedom in customising their workspace. One good example of this is the ‘Article Length’ tool in Apple’s Safari RSS reader. Sliding a slider left of right dynamically changes the length of each article shown. It’s that kind of awesomey magic stuff that’s enough to keep you from sleeping. Let’s build one. The Setup Let’s take a page that has lots of long text items, a bit like a news page or like Safari’s RSS items view. If we were to attach a class name to each element we wanted to resize, that would give us something to hook onto from the JavaScript. Example 1: The basic page. As you can see, I’ve wrapped my items in a DIV and added a class name of chunk to them. It’s these chunks that we’ll be finding with the JavaScript. Speaking of which … Our Core Functions There are two main tasks that need performing in our script. The first is to find the chunks we’re going to be resizing and store their original contents away somewhere safe. We’ll need this so that if we trim the text down we’ll know what it was if the user decides they want it back again. We’ll call this loadChunks. var loadChunks = function(){ var everything = document.getElementsByTagName('*'); var i, l; chunks = []; for (i=0, l=everything.length; i<l; i++){ if (everything[i].className.indexOf(chunkClass) > -1){ chunks.push({ ref: everything[i], original: everything[i].innerHTML }); } } }; The variable chunks is stored outside of this function so that we can access it from our next core function, which is doTrim. var doTrim = function(interval) { if (!chunks) loadChunks(); var i, l; for (i=0, l=chunks.length; i<l; i++){ var a = chunks[i].original.split(' '); a = a.slice(0, interval); chunks[i].ref.innerHTML = a.join(' '); } }; The first thing that needs to be done is to call loadChunks if the chunks variable isn’t set. This should only happen the first time doTrim is called, as from that point the chunks will be loaded. Then all we do is loop through the chunks and trim them. The trimming itself (lines 6-8) is very simple. We split the text into an array of words (line 6), then select only a portion from the beginning of the array up until the number we want (line 7). Finally the words are glued back together (line 8). In essense, that’s it, but it leaves us needing to know how to get the number into this function in the first place, and how that number is generated by the user. Let’s look at the latter case first. The YUI Slider Widget There are lots of JavaScript libraries available at the moment. A fair few of those are really good. I use the Yahoo! User Interface Library professionally, but have only recently played with their pre-build slider widget. Turns out, it’s pretty good and perfect for this task. Once you have the library files linked in (check the docs linked above) it’s fairly straightforward to create yourself a slider. slider = YAHOO.widget.Slider.getHorizSlider("sliderbg", "sliderthumb", 0, 100, 5); slider.setValue(50); slider.subscribe("change", doTrim); All that’s needed then is some CSS to make the slider look like a slider, and of course a few bits of HTML. We’ll see those later. See It Working! Rather than spell out all the nuts and bolts of implementing this fairly simple script, let’s just look at in it action and then pick on some interesting bits I’ve added. Example 2: Try the Tasty Text Trimmer. At the top of the JavaScript file I’ve added a small number of settings. var chunkClass = 'chunk'; var minValue = 10; var maxValue = 100; var multiplier = 5; Obvious candidates for configuration are the class name used to find the chunks, and also the some minimum and maximum values. The minValue is the fewest number of words we wish to display when the slider is all the way down. The maxValue is the length of the slider, in this case 100. Moving the slider makes a call to our doTrim function with the current value of the slider. For a slider 100 pixels long, this is going to be in the range of 0-100. That might be okay for some things, but for longer items of text you’ll want to allow for displaying more than 100 words. I’ve accounted for this by adding in a multiplier – in my code I’m multiplying the value by 5, so a slider value of 50 shows 250 words. You’ll probably want to tailor the multiplier to the type of content you’re using. Keeping it Accessible This effect isn’t something we can really achieve without JavaScript, but even so we must make sure that this functionality has no adverse impact on the page when JavaScript isn’t available. This is achieved by adding the slider markup to the page from within the insertSliderHTML function. var insertSliderHTML = function(){ var s = '<a id="slider-less" href="#less"><img src="icon_min.gif" width="10" height="10" alt="Less text" class="first" /></a>'; s +=' <div id="sliderbg"><div id="sliderthumb"><img src="sliderthumbimg.gif" /></div></div>'; s +=' <a id="slider-more" href="#more"><img src="icon_max.gif" width="10" height="10" alt="More text" /></a>'; document.getElementById('slider').innerHTML = s; } The other important factor to consider is that a slider can be tricky to use unless you have good eyesight and pretty well controlled motor skills. Therefore we should provide a method of changing the value by the keyboard. I’ve done this by making the icons at either end of the slider links so they can be tabbed to. Clicking on either icon fires the appropriate JavaScript function to show more or less of the text. In Conclusion The upshot of all this is that without JavaScript the page just shows all the text as it normally would. With JavaScript we have a slider for trimming the text excepts that can be controlled with the mouse or with a keyboard. If you’re like me and have just scrolled to the bottom to find the working demo, here it is again: Try the Tasty Text Trimmer Trimmer for Christmas? Don’t say I never give you anything! 2006 Drew McLellan drewmclellan 2006-12-01T00:00:00+00:00 https://24ways.org/2006/tasty-text-trimmer/ code