22 Jul 2008, 18:57
Sms_pragsmall

Smits (3 posts)

Dear Christophe, Thanks for your book. I really enjoyed reading it and, I think, it is an indispensable guide to write scripts based on Prototype and Script.aculo.us. The only thing I experienced difficulties with (hard to grasp) is the function binding (section 4.2). Your article at A List Apart gives a clear introduction. Thanks again.

My question is how to make a routine to check a range of check boxes in a form using a click (for the first) and a shift-click (for the last). My search lead to a JQuery plugin page of John Sutherland named “jquery.shiftclick”:http://plugins.jquery.com/files/jquery.shiftclick.js_0.txt but I didn’t want to include the jquery library of John Resig, so I used the code fragment to make it the prototype way.

We have a form on our page (id=frmStudentList) with a table containing a check box in front of each name in the table rows. The check boxes have a class checkbox. There are only check boxes, no other input fields. We don’t want to attach listeners to each checkbox (because the table will be updated by an Ajax call), so there is just one statement: $(‘frmStudentList’).observe(‘click’, handleClick);

Here is the code:

handleClick: function(e)
{
   var origin = e.element();
   if (!origin) return;
   if (!origin.nodeName) return;
   // Process click on an input of type checkbox 
   if (origin.nodeName.toLowerCase() === 'input') {
      if (e.shiftKey) {
         var checkBoxes = $A($('frmStudentList').getInputs('checkbox'));
         var last = checkBoxes.indexOf(lastSelected);
         var first = checkBoxes.indexOf(origin);
         if (-1 == last) {
            lastSelected = origin;
            return;
         }

         var start = Math.min(first, last);
         var end = Math.max(first, last);

         var chk = lastSelected.checked;
         for (var i = start; i < end; ++i) {
            checkBoxes[i].checked = chk;
         }
      } else {
         lastSelected = origin;
      }
   }
}  // end 'handleClick'

Can you comment on this code.Is there a way to do this faster or better with either Prototype or Script.aculo.us?

Thanks for your answer.

Kind regards from the Netherlands, Johan Smits.

23 Jul 2008, 20:48
Avatar_mw_large_pragsmall

Christophe Porteneuve (63 posts)

Hey Johan,

No time to reply just now, but the problem is interesting and I think I see a simple solution. Ping me by email tomorrow and I’ll try to come up with something here.

Cheers,

25 Jul 2008, 20:40
Sms_pragsmall

Smits (3 posts)

Hey Christophe,

I’m sorry but I don’t have your mail address so I can’t ping. Nice to see that this problem interests you. It is, I think, worthwhile to discuss because this will give users a better UI. In the meantime I will wait for your comment. Enjoy your weekend.

Warm regards, Johan.

27 Jul 2008, 11:29
Avatar_mw_large_pragsmall

Christophe Porteneuve (63 posts)

Hey Johan,

Well your implementation works alright. It makes little use of “comfort methods” so it’s pretty fast as it is. I have a few remarks, however:

I would explicitly declare @lastSelected@, at least as a global variable, ideally in a more scoped/namespaced way (perhaps just as a property of the @handleClick@ function, which you’d then access internally using, for instance, @arguments.callee.handleClick@).

# I fail to see why you’d want the two first checks; I don’t see a use case where they’d be useful. # Instead of nesting @if@’s, I tend to prefer inverting the conditions and short-circuting with @return@ calls to keep it leaner and avoid remote @else@ clauses. # As of Prototype 1.6, @observe@ calls the handler within the binding context of the element you observed on, so instead of going @$(‘frmStudentList’)@ (btw, are you coming from a Delphi background to use this naming scheme? :-)), you can just use @this@. # @getInputs@ already returns an actual array, so the wrapping @$A@ call is superfluous. # I would consider the last click always to define @lastSelected@, even when it’s a shift-click.

So I might rephrase your function like this:

var lastSelected = null;
handleClick: function(e)
{
    var origin = e.element();
    if (origin.nodeName.toLowerCase() != 'input')
        return;
    if (!e.shiftKey) {
        lastSelected = origin;
        return;
    }
    var checkBoxes = this.getInputs('checkbox');
    var first = checkBoxes.indexOf(origin);
    var last = checkBoxes.indexOf(lastSelected);
    if (-1 == last) {
        lastSelected = origin;
        return;
    }
    var start = Math.min(first, last), end = Math.max(first, last);
    for (var index = start; index < end; ++index)
        checkBoxes[i].checked = lastSelected.checked;
    lastSelected = origin;
} // handleClick

There are several tricks we could use to size it down a little, but they’d be somewhat detrimental to speed, so the trade-off isn’t so good.

‘HTH

27 Jul 2008, 18:38
Sms_pragsmall

Smits (3 posts)

Hi Christophe,

Thanks for your post and your remarks. Allow me to comment some of them.

  1. I forgot to write in my origin post, but I declared @lastSelected@ globally (in the window namespace). That’s why there was no var keyword to this variable. I already defined the function handleClick in a namespace. Can you give a reference how to use that “@arguments.callee.handleClick@” to have that variable in the same namespace as well.

  2. Those checks are from an other try at another topic. They must be left out.

  3. You are right that inverting conditions and using @return@ calls give less lines of code. However, they taught me to write procedures (functions) with one entry point and one exit point, using nested if’s and so on. Now you’ve got a function with four exit points, but I get your point.

  4. With respect to @this@ point. Again, it is (in my opinion) the most difficult part of Javascript and your book. I thought @this@ was referring to the original checkbox being clicked. In my original code I declared the listener on a @div@ within the form. So the code with the @$(‘frmStudentList’)@ replaced by @this@ will not work in my very situation, because @this@ will refer to that @div@. It is good to know that observe will give the element itself as being @this@ in the function. I think it is important to realize this, but I can’t find this in the book. Is it to be found at page 100-108? I think it is worthwhile to write an article about this feature, including the bubbling and delegation aspects of events.

(My background is not Delphi, although I learned programming in Pascal, from a book of Niklaus Wirth, Algorithms+DataStructures=Programs. Later I learned SPSS, Visual Basic, dBASE, SQL, mySQL, PHP4 and 5, CSS from the Eric Meyer books and unobtrusive Javascript from Stuart Langridge and PPK. Since it is important to know what kind of object or type you have in your variable I am using the Hungarian naming convention, so all my string variables are prepended with str, objects with obj, arrays with arr and so on. Although your variable names are a little longer it gives you a very good view what to expect in a certain variable. In the html context I always have forms with (names and) ids like frm…, tables with id tbl.. and so on)

The only exception I make is to use i for an integer index, instead of the full index as you did. In your code you have to adjust that @checkBox[i]@ also into @checkBox[index]@. Perhaps you can do that in your post to have a correct code snippet.

Thanks again for your comments and I hope you will enjoy Boston, at the end of September.

Warm regards from the Netherlands, JS.

  You must be logged in to comment