Archive for the 'Technology' Category

Consolidated Interfaces for efficient Education web apps

When I started teaching, way back in 2001, we didn’t have the new-fangled, high-tech Learning Management System (LMS) web applications that we teachers have today, and my approach to grading my students’ programming assignments reflected that.  Nonetheless I made my students submit electronic copies of their homework even back then, which I then graded using an IDE (to examine the source code) and Microsoft Word (to create the document that would provide feedback to each student).  Grading all the students’ submissions for one assignment, for one class, would produce about 30 Word documents – one per student.  I’d use the Master Document feature in Word to ‘chain together’ all these separate documents, and print them all in one go.  It took 20-30 minutes for the printer to print them all, but since I could do other work during the printing, I didn’t mind.

I still grade homework assignments the same way, except that nowadays I upload the documents into an LMS (which then emails the docs to the students) instead of clear-cutting trees.  Which means that after spending many, many hours laboriously grading my students’ assignments (creating Word documents as I go) I then upload all the documents into the LMS at once.  The first time I did this I was surprised at the amount of time I spent following links and uploading files.  The next couple of times I timed myself, and was found myself spending more than 15 minutes minutes doing nothing but uploading finished documents into my LMS for a small class.  And that one term that I sent back feedback via Outlook Web Access?  With all the clicking on links, cutting and pasting boilerplate messages, and attaching files I spent more than 30 minutes just sending out feedback, for a small class of 20 or so students.  It might not sound like much, but it’s 30 minutes of pure overhead.

Which brings me to this post’s topic: the excessive overhead many LMS’s impose on teachers when uploading large numbers of documents, and how this overhead can be eliminated.

The fundamental problem is the number page-loads (and mouse clicks) per document (per student) the LMS forces on the instructor.  Typically, the web app presents the instructor with a list of all the students, with each student’s name rendered as a hyperlink.  In order to upload the feedback the instructor has to click on a student’s name.  And then wait for the "Upload Feedback" page to load.  Next, the instructor clicks several more times to upload the feedback file and/or fill in a TEXTAREA with a message for the student, and then finally clicks on the ‘Ok’ button.  And then waits for the next page to load.  If the instructor is lucky, the app will send the instructor directly back to the list of students and clearly display a confirmation/failure message at the top of that page.  If the instructor is unlucky, then clicking the ‘Ok’ button will be followed by a brief wait, then a page that displays the confirmation/failure message.  This confirmation page comes complete with ANOTHER link to follow in order to get back to the ‘List of all Students’ page (after another wait, of course).  This second approach was used by BlackBoard 5 pretty much everywhere – after every action the instructor did, Bb 5 sent the teacher to a page that basically said ‘Hey, that worked!’, and contained a link to go back and do the next useful action.

What’s bad about this approach is that it forces the teacher to send feedback to a class of 20, 30, 40, or more students one student at a time.  Let’s say that there’s 30 students in the class, and that each page-load requires 5 seconds of time.  Consider the following workflow:

Download the ‘List All Students’ page, then enter a loop:

  1. Download a specific student’s page
  2. Upload the feedback for that student
  3. Re-download the ‘List All Students’ page

Using the above workflow, it’s clear that the instructor be spending ( 5 + 30 x ( 5 + 5 ) ~= 300  seconds / 60 = ) 5 minutes just waiting for things to load, not counting the time to upload the files from the teacher’s computer.

BlackBoard V5 adds a step 2.5 into the loop: "Download a page telling you everything went fine".  In that case, the teacher will spend ( 5 + 30 x (5 + 5 + 5 ) ~= 450 seconds / 60 = ) 7.5 minutes just waiting for stuff to load. 

As the overhead-per-page number increases the number of minutes of pure overhead increases.  A short list of possible slowdowns include a slow server, a slow connection to the server, deciding to include the time to upload the feedback file (which can easily take 5-10 seconds, depending on the file’s size), deciding to include the time required to scroll to back the right place in the list, deciding to include the time required to re-orient yourself after the page loads, etc.  With 10 seconds/load using the first workflow, we’re looking at about 10 minutes of overhead.  Increase the number of students, and these numbers all go up, too.  Of course, if these numbers go down (fast server, fast connection to the server, small class size, or an instructor that uploads homework feedback with the frantic speed of a kid playing video games, etc), then you suffer through less overhead.  But the underlying point remains – this style of user interface doesn’t scale to medium or large class sizes.

When dealing with the large volume of individual students that teachers normally do, some sort of ‘consolidated interface’ is critical to maintaining productivity.  Instructors should be presented with a single page that lets contains a list of file upload widgets, so that the instructor can upload each and every student’s feedback on a single page.  Instead of forcing the instructor to repeatedly visit one page per student, all relevant work is consolidated onto a single page, thereby eliminating unneeded overhead.  What’s left is the necessary work of actually uploading the feedback files.  Unfortunately there aren’t too many examples of this that I’m aware of.  The Moodle gradebook is one of the few exceptions, and is pictured here:

(This image was taken from http://www2.oakland.edu/elis/traindocs/Moodle/WeightedGrades/editgrades.jpg). 

The above image has a lot stuff going on in it, so let’s focus on the important parts using the image below:

image

Notice that each student goes into a separate row; in the above picture each of the two students’ rows are highlighted with the yellow boxes.  For each student, ALL of the graded items are listed, one item per column; in the above picture there’s a blue box around the first three items for "Test Student13".  While the above gradebook doesn’t allow for file upload specifically, it does neatly demonstrate what I mean by a ‘consolidated interface’ – the teacher can now enter the grades for "Homework 1" for all the students in the class on a single page. 

I hope that you, gentle reader, can see that consolidating all the relevant work onto a single page enables greater productivity by NOT forcing teachers to spend substantial chunks of their grading time fighting to upload feedback and grades to students.

 

What about grading student-by-student online?

You might be wondering why I still grade on my computer by saving feedback into Word documents when I could just type my feedback into an LMS directly.  Here’s a couple of reasons:

  1. MS Word is more reliable than a website.  Browser sessions time-out (losing the work that’s been done)(especially if you take a long time to grade an individual item), my ISP not-infrequently drops my connection, and so on.  By saving my work to a local file I know that I won’t lose the work I’ve done.
  2. It’s easy to make backup copies.  By uploading the documents to the LMS I’ve effectively made a backup copy, since the documents now exist in the LMS AND on my local hard drive.  But if I want more copies I just need to copy the directory (manually, or in a script using something like XCOPY)
  3. MS Word has more features than LMS’s.  Several LMS’s allow the teacher to write feedback directly to the student, using a Rich Text edit box.  But these rarely have features like tables, or autocorrect.
  4. Writing feedback directly into the LMS still incurs the overhead, even if it’s less visible.  One might not notice the overhead as much, since one spends a bunch of time grading between loading pages, but it’s still there.
  5. Once the students have seen their feedback, it can’t be (easily) changed.  What if you grade Aaron Aardvarkson directly in the LMS at 1:10pm, which he can then immediately see.  Suppose that when you get to Mike Mason at 3:30pm you realize both Aaron and Mike made the same mistake.  Let’s say that you furthermore realize that the point penalty you leveled on Aaron was too high.  Clearly you must grade both Mike and Aaron consistently, so you can’t just give Mike the smaller penalty without changing Aaron’s penalty.   But if Aaron can see his grade at 1:10pm, then by 3:30pm it’s too late to go back and change it.  This assumes that the system even lets the teacher change the original grade, and doesn’t just force the instructor to upload a fresh copy.
  6. The instructor might not want to grade all the students in one sitting.  What if you grade Aaron Aardvarkson at 8:10pm, Mike Mason at 11:30pm, then go to sleep so that you can get up in time for your early class the next day?  You can bet that you’ll receive emails from ungraded students asking when they’ll be graded.

September 28 2009 | Teaching and Technology | 2 Comments »

Using jQuery-based Date/Time Picker controls in CakePHP

An overview of your jQuery options, by Mike Panitz

As the summer winds down here in beautiful, increasingly overcast Washington state, I have to face certain facts.  Fact: if I don’t get that stationary trainer set up, I’m not going to ride my bicycle this winter.  Fact: I really, really need a haircut.  Fact: I’m not going to implement the ‘due date’ functionality in my homework hand-in web application by the time school starts again in two weeks.  This last fact is sad, but not tragic, especially since I got other Really Important Stuff done this summer.  And I got figured out how to use jQuery with a calendar sort of control/widget, starting with choosing a date (or time) picker for my web app.

To be clear, my motivation is that I want a a DHTML date/time picker that will make my otherwise plain old input form that much better.  There’s no AJAX involved here, just DHTML interactive goodness.  I’m going to use the jQuery / jQuery UI stuff because it’s easy to use, elegant, and because I’m planning on using it for other things, in the future.  I’ll be continuing to use CakePHP for the backend, since it’s ‘convention over configuration’ approach is refreshingly XML-free, their MVC abstraction is clean, and their ORM implementation is quickly and easily used.

After much fiddling, I’ve decided on one date/time picker control to use.  I’ve also found some strategies for trying this stuff out.  So I’ll start by talking through the strategies, then I’ll talk about the contenders, then finally I’ll call out the winner.  If I have time, I’ll walk you through using the winner in my next post (which will include nitty-gritty details like the exact CakePHP code you’ll need to use :)   )

Suggested strategy for deciding on a UI widget/control:

In general, this seemed to work well:

  1. Find a widget
    This is the ‘Google around for it’ stage.  You’re just looking for options at this point.
  2. Look through the control’s home page, paying special attention to the demo page
    The home page will help make it clear how polished/full-featured the control is.  It should contain a link to a page that contains clear documentation for all the options, and the documentation should contain code samples.  Especially for projects run by individuals, sample code is crucial for figuring out how the author expects you to use it.

    You really, really want to find a ‘demo page’, which shows off the UI widget.  Not only will this let you quickly get a feel for how the widget works, but will serve as a complete, working example for the ‘copy locally’ step (#4, below).

  3. Repeat the above steps until you find something that you like
    ‘Nuff said :)
  4. Copy the demo page into a local directory & make sure it works
    The local directory can either be something in the file system, or something that’s served up by a web server that you’ve got control over.  XAMPP is perfect for this step.

    The real goal here is to make sure that you’ve got everything working, so that when you go to integrate it into CakePHP, you’ve got a working copy to compare your Cake-ified version to.  This is especially useful if you’re making local copies of the various libraries & files that the control depends on.

    This step can also be useful to make sure that you can make the control appear (and behave) the way you want, before investing time to Cake-ify it.

  5. Cake-ify it
    First get the UI part to work, then verify that you can get the data you need back to the server (back to the Cake controller).  Then, and only then, should you fiddle with the control’s options extensively.

Having outlined my suggested strategy for figuring out which control you want, let’s look at the options that I found.  These aren’t the only options, but these are the ones that I found, that seemed to work well.

The Contenders:

Cake’s default date/time picker

Cake_DATETIME_Default
Figure 1: Cake’s default control(s) for a DATETIME field

While not actually jQuery based, I figured that I’d point this out just because of how quick-and-easy this is to program.  It’s built into Cake (obviously), and so you don’t have to do much for this to work.  Just use the FormHelper’s input method on a database field that’s a DATETIME, and Cake takes care of the rest. 

Cake auto-magically provides the user with a collection of drop-down menus that let the user safely pick out the date and time, as shown in Figure #1.

If you’ve got something that doesn’t need to be AJAX-y, this is an option worth considering, simply because of how fast, easy, and reliable it is.

jQuery’s Datepicker

Home Page: http://docs.jquery.com/UI/Datepicker

Demo Page: http://jqueryui.com/demos/datepicker/ 
(there’s another demo in the home page, too)

DatePicker  
Figure 2: The jQuery UI’s Datepicker control

This is the only date/time picker that’s a standard part of the jQuery UI, so it’s easy to get your hands on, easily documented, and you can get CDN-hosting benefits.  The upside of this is that it’s really nicely done – solid documentation, full-featured, and easy to use.  As you can see in Figure 2, above, Datepicker supports the jQuery UI ThemeRoller custom themes, slide-out visual effects, and such nice perks as having the widget ‘output’ a differently formatted string into another form field (in this case it’s outputting "Thursday, 17 December, 2009" into the text input field on the right).  

The major downside is that this control only allows the user to pick the date.  If you want to pick the time as well, you’re on your own – there aren’t any time-picking widgets in the standard jQuery UI.  So the Datepicker widget is a great choice if you’ve got a DATE field in your (MySQL) database, and want the user to have a slick, calendar-based way of picking a date.

A word of warning: You’ll need to watch the format – the default format for the Datepicker isn’t the exact same as the MySQL DATE format, which will cause problems for CakePHP.  You’ll need to use one of the format parameters to make sure that the Datepicker widget puts a properly formatted string into the text field, so that Cake can auto-convert it).

An aside: what if you want to use the Datepicker as part of a solution?

Let’s say that we want the user to be able to pick both the date AND the time.  One option would be to use a separate DATE and TIME field in the database, and have each one nicely tied to a single jQuery widget.  This will make the UI fairly straightforward, but will then complicate later queries.  If we’re interested in determining if the student’s homework submission is late, we’d have to do convoluted queries like "An assignment is be late if it was handed in after the due DATE, or if it was handed in on the due DATE and after the TIME it is due").  Questions like ‘How many days late is the submitted homework’ should be even more fun :)

Luckily , MySQL provides both DATETIME and TIMESTAMP data types – they appear to be pretty much identical (which clearly indicates that I just haven’t researched the differences enough :)   ).  They can be compared directly, and there are PHP routines for dealing with DateTimes (http://us.php.net/manual/en/class.datetime.php), so using a single DATETIME in the database layer, and multiple input fields in the UI layer, seems like a reasonable approach.

The jQuery Datepicker doesn’t do anything with time, but we’ve got a couple of options.  The worst option is to set up the Datepicker’s format string to include an arbitrary, unchanging time (e.g., "12:00:00"), but if we’re going to do that, then why waste the space & effort on recording the time at all? 

If we’re not going to hard-code a time into the field, then we’ll have to ask the user for for the time using a separate widget.  We can do this using two separate widgets in the UI layer.  The question then becomes "Where should I merge those two separate fields into the single DATETIME field?  In JavaScript (on the web page), or in PHP (in the Cake controller method)?"  A friend who’s been doing web programming for a while says that doing such merging work in PHP (in the controller method) is the way to go.  Thinking about it, I agree with him, mostly because using JavaScript to manipulate the automatically generated Cake input form fields seems brittle.  The various arrays (mostly $this->data) in the controller seem much more stable.  For example, the documentation clearly defines the format used, and this definition is so fundamental to how Cake works that it would be a major, breaking change for them to alter it.  An added bonus for putting this ‘merging’ logic into the controller is that the UI can display anything you want, so long as your controller code can parse the fields, then merge and reformat the fields into a single, properly formatted DATETIME string that Cake’s model code is expecting.

So if you’re going to use the jQuery UI’s Datepicker widget to let your users pick the date/time, then you’ll probably want to have a second field that will allow them to pick the time, and then merge these two fields into one DATETIME in the Cake controller method.

timepickr

Home Page: http://haineault.com/media/jquery/ui-timepickr/page/

Demo Page: (at the top of the home page)

timepickr
Figure 3:
The timepickr widget

This widget only picks out the time – there’s no support for picking out the date.  I’m only  mentioning this control at all in case you want to use the approach of providing a Datepicker to pick the date, and something like this timepickr to pick the time.

As you can see in Figure 3, above, the widget is a tad odd.  Essentially, you click on the field ( "{suffix}", in this case), and then the first row of numbers pops out beneath it.  You then choose the hour ("06", in Figure 3) by moving your mouse over it, and the next row pops out.  You then select the minutes ( "30" ), and finally AM/PM (unless you’re using a 24 hour timepickr). 

It’s a really nice interface, actually – not one I would have thought of, but once you use it a couple times, it works really well.

dtpicker

Home Page: http://code.google.com/p/jquery-dtpicker/

Demo Page: http://jquery-dtpicker.vndv.com/

dtpicker

 Figure 4: The dtpicker widget

This is the first combined date and time picker widget that I found.  It’s very similar to the timepickr control, in the sense that you click on the input form (in Figure 4, above, I clicked on the text field under the "event" label), and it displays the first column of choices – the years, in the above figure.  Once you move the mouse over a year, it lets you pick the month ( "March" ), then a range of days ("20-29"), then a specific day ( "22" ), then an hour ( "09:" ), then a minute ( "20 ).  It’s a slick interface, and I liked it.  I could quickly and easily pick out the date and time that I wanted, especially after the timepickr widget familiarized me with how this style of widget worked.

There are two reasons that I didn’t go with with widget.  First, I wanted something that looks like a calendar, since this will be used to pick out due dates for homework assignments.  It’s invaluable to see how far apart dues are, visually, so that it’s clear how much time (a week?  two weeks?) the students will have to work on any given project.  Secondly, this widget doesn’t appear to play nicely with Cake’s CSS ‘out of the box’.

A word of warning: Watch out for the type of the input field that you attach the dtpicker to.  The dtpicker doesn’t appear to work when attached to a text input (e.g., <input type="text">), but instead you must attach it to one of the custom "date" input types that it requires (e.g., <input type="date">)

dyndatetime

Home Page: http://code.google.com/p/dyndatetime/

Demo Page: http://www.mechanicalmarksy.com/hosted/toolman/dyndatetime/example.html

dynDateTime Figure 5: The dyndatetime widget

As you can see in Figure 5, above, the dyndatetime widget looks like a calendar with a space for time at the bottom.  You activate it by clicking on something (in this case, the button labeled ‘PICKER", but you can attach it to the text field instead).  The widget then appears on the screen, and you can pick the time, then pick the date.  The time picker isn’t entirely intuitive (you can SHIFT+Click to increase the hour/minute individually, or you can click-and-hold on the hour (or minute), and then drag the mouse left or right to adjust the time), but once you figure it out, it works pretty well.

The Winner: dynDateTime

dyndatetime, based on the based on Dynarch date time calendar, is what I’m going to use.  It looks pretty slick, seems to work well, and will produce a single field that contains both date and time. 

It seems to be reasonably full-featured, including things like formatting the input field one way, and then formatting another field in a different way.  This will make it easy to have a field that Cake’s server-side code will use to automatically populate the DATETIME database field. 

There’s also a bunch of pre-fab .CSS and language options, too, so you can customize how it looks, or which language you’ll be using it with.  Speaking of which, dyndatetime’s CSS plays pretty well (but not perfectly) with Cake’s default CSS, which is nice.  Generally, there are lots of nice options (http://code.google.com/p/dyndatetime/wiki/Home), and the date/time formatting that can both support MySQL’s DATETIME, and more human-readable formats.

September 12 2009 | CakePHP and jQuery | 2 Comments »

Generating jQuery code from your CakePHP Controller/View

An adventure in PHP’s include, eval, and JavaScript!

I am looking at using jQuery and AJAX as a means of making some of my forms in my StudentTracker© web app more streamlined.  Currently, I’m dynamically creating forms so that all the students’ assignments are listed on a single page, no matter how many assignments there are.  By putting each item into a separate form element (i.e., one text field for the student-visible name, another for the number of points it’s worth, etc, etc), I can allow the user (me! :)   ) to edit each one directly and in-place, without having to visit a separate page for each assignment.  This approach works well, even in spite of the ‘reload the entire page after each edit’ workflow that StudentTracker’s non-AJAX pages use.  The problem is that if I wanted to use, say, jQuery to create a rich user experience, it seems like I’d need a way to generate some JavaScript code per element, in order to do things like send AJAX messages back to the server when a form element gets changed. 

After thinking about it for a while, I’m not so sure that’s true.  I think that for my situation, not only will it be possible to use jQuery to handle a bunch of different elements individually, but it will actually be better to go with that approach.  I say this with the confidence of one who hasn’t actually tried it yet :)

That said, I was playing around with both CakePHP and some simple jQuery, and thinking that if I could use (Cake)PHP to write jQuery code directly into my JavaScript file then I could dynamically generate some JavaScript to go with my dynamically generated form.  Specifically, I wanted the PHP page that generated the JavaScript to have access to all the PHP variables that I have defined in my Cake view.  I also wanted to do so using an approach that could be reused within the Cake controller, if I chose.

After thinking about it, I’ve come up with a fun, clever, very very hack-y way to do it.  It’s so much fun, in fact, that I’m posting it here, even though I think I’ve got a better way to accomplish my original goal :) .

Side-note: If all you want to do is execute PHP code in your JavaScript file, then you can rename the webroot/js/…/foo.js file to be foo.js.php, and CakePHP will correctly interpret the code first as a normal PHP file.  You’ll need to link to foo.js.php (instead of foo.js), but except for that change, the browser will interpret that file as a JavaScript file.

The problem I have with this solution is that you can’t get to anything from the original context – any local variables in your view (or helpers that your controller defined, etc) will be UNAVAILABLE in the JavaScript-generating PHP file. 

This is because the .js.php file is separate from your controller/view files, which means that the browser needs to make a separate request to the web server to retrieve the JavaScript file – by the time the JS is being served up, the original controller/view methods have long since finished!

I started by asking myself "What about using PHP’s include function?"

include will open a file, pull it’s contents into the current .PHP page, and then evaluate the file’s contents, which is exactly what we want.  What’s also very handy is that the include code will be evaluated as if it really had been pasted into that line, including having access to all the local variables available at the time that include is called.  Incidentally, include with also drop back out of ‘PHP mode’, and into ‘HTML mode’, which we also want.  This mode switch will mean that include will basically ignore anything that’s not wrapped in <?php ?> tags, which is perfect.

At first blush, this seems like it should solve the problem easily and elegantly – we can pull the .js file into the view/controller, and evaluate all the (Cake)PHP elements in the file with full access to the view’s local variables.

But include this doesn’t quite work, because it will (effectively) paste the included (JavaScript) code directly into the controller/view method.  Instead, what we want is to have include do it’s work, and then have it hand us a string that contains results of that work (i.e., the evaluated contents of the included file).

Luckily for us, the PHP manual page spells out how to do exactly this.  We can use output buffering to include the file, then put that into a string.  My code below is basically a simple variation on http://us3.php.net/manual/en/function.include.php – specifically the "Example #6 Using output buffering to include a PHP file into a string"

I said to myself "Let’s put this logic into a function and call it!"

The code that I got from the PHP manual wasn’t the most tricky thing in the world, but it wasn’t really something I wanted to have littered throughout my CakePHP code.  So putting this into a function is a natural and normal way to place it into a well-understood and reusable package.

The only problem is that if we call include from a function, then all of that function’s local variables will be in scope, and so we won’t be able to access the controller/view’s variables.

I told myself that I’d finally found a fun reason to use PHP’s eval function!

The basic approach is to define a string that is valid PHP code for including the JavaScript file.  The output buffering goes into this block, too, but I’m going to leave most of the error-checking/input massaging out of this example to keep things simple.  When PHP’s eval is called on the block, then it will execute the ‘include’ statement, in the context of the eval line.

Because the code runs in the context of the eval line, we should should be clear any variables that we create (or stomp on) will still exist after the call to eval is done.  (By ’stomp on’, I mean ‘overwrite’.)  Because of this, we shouldn’t create local variables (or stomp on existing variables) in the script that we’ll be evaluating.   We can achieve this by creating a CakePHP helper, having the controller import that helper, and then using instance variables on that helper object instead of local variables.  In order to make that work, we’ll need to have a variable that contains the name of the helper. 

Thus, the final product: the JavascriptEval helper, with instance variables that’ll substitute for parameters and return values (and locals, if I had any).  The code for the helper (and example files to demonstrate the use of the helper) are listed below; if you want a .ZIP file that contains all of these files, you can find it at http://panitzco.com/Files/JavascriptEval_Demo.zip.  All the files in the .ZIP should be set up so that you can extract it into your /app/ directory, and everything will end up where it needs to go. 

javascript_eval.php
(this is the new, reusable helper – to be placed into /app/views/helpers)

<?php
/**
* JavaScript Helper class file.
*
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
/**
* JavascriptEx Helper class for easy use of (Cake)PHP to generate
* JavaScript, from within a view/controller file.
*
*/
class JavascriptEvalHelper extends AppHelper {

/**
* File to evaluate in the current context
* This needs to be a path (in the file system) to the JavaScript file -
*     it will be passed to the call to include
*
* You should set this before eval'ing evalScript
*
* @var string
* @access public
*/
    var $scriptToEvaluate = false;

/**
* Code that needs to be passed to PHP's eval function. This
*     is set in the helper's constructor.
*
* You shouldn't need to change this, normally.
*
* @var string
* @access public
*/
    var $evalScript = false;

/**
* The resulting, evaluated script
*
* This is roughly equivalent to a return value, if
*     this had been done using functions, instead of this
*     hacky eval thing :)
*
* @var string
* @access public
*/
    var $scriptResults = null;

    function __construct() {
        parent::__construct();
        $this->scriptToEvaluate = "";

$this->evalScript = <<<THESCRIPT
    if( !isset( \$name_of_javascript_eval_object ) ) {
        \$name_of_javascript_eval_object = "javascriptEval";
        // probably should do better error handling, here
    }

    if ( \$\$name_of_javascript_eval_object->scriptToEvaluate == "" ) {
        \$\$name_of_javascript_eval_object->scriptResults = "NO SCRIPT TO EVALUATE";
    }
    else
    {
        // pre-pend the /.../app/webroot/js/ onto the given filename
        \$\$name_of_javascript_eval_object->scriptToEvaluate = APP.'webroot'.DS.'js'.DS. \$\$name_of_javascript_eval_object->scriptToEvaluate ;
        if    (!is_file(\$\$name_of_javascript_eval_object->scriptToEvaluate )) {
            \$\$name_of_javascript_eval_object->scriptResults = 'FILENAME ('.
                    \$\$name_of_javascript_eval_object->scriptToEvaluate . ') IS NOT A VALID FILE!';
        }
        else {
            ob_start();
            include \$\$name_of_javascript_eval_object->scriptToEvaluate ;
            \$\$name_of_javascript_eval_object->scriptResults= ob_get_contents();
            ob_end_clean();
        }
    }
THESCRIPT;
    }
}
?>

You’ll notice that all the action is in the script that the constructor assigns to evalScript, using a heredoc.  Within the script, we check and see if $name_of_javascript_eval_object is defined, and if not, then we assign it a name that should typically work for view code (i.e,. in .CTP files).  Next we check if scriptToEvaluate has been set (it must be set to a file underneath the /app/webroot/js/ directory).  Following that, we assume the file’s name refers to something in the /app/webroot/js/ directory, and so we pre-pend /app/webroot/js/ to the provided script name.  If that file doesn’t exist, then we stop, otherwise we go ahead and include it.

Note the clever use of variable variables to allow someone to re-use this script in, say, a controller, by setting name_of_javascript_eval_object to be something other than the default javascriptEval.  This does mean that we’re creating a local variable in the scope that wants to do the include, but name_of_javascript_eval_object seems like a sufficiently unique name that I just go ahead & create it without checking.  if you wanted to, you could check that it doesn’t already exist and/or unset it before the script ends.

Here’s an example that uses this:

Controller file:

jse_controller.php
(this is the controller – to be placed into /app/controllers/jse_controller.php)

<?php
class JseController extends AppController {

    var $name = 'Jse';

    // For this simple demo, make sure that
    // the controller does not use any model(s)
    var $uses = array();

    function demoEvaluator() {
        $this->helpers[] = 'JavascriptEval';
        $this->helpers[] = 'JavaScript';
    }

}
?>

Other than adding the new JavascriptEval class and the core JavaScript class to the helpers array, this really doesn’t do much.

demo_evaluator.php
(this is the view – to be placed into /app/views/jse/demo_evaluator.php)

<?php
App::import('Sanitize');

    echo $javascript->link('jquery/jquery.js', false); // pull in jQuery

    // Test #1: Forgot to set the evalScript field
    eval( $javascriptEval->evalScript );
    echo 'This should read \'NO SCRIPT TO EVALUATE\'<br/><pre>'.Sanitize::html( $javascriptEval->scriptResults ).'</pre><br/><hr/>';

    // Test #2: Didn't provide a valid filename (into the local filesystem)
    $javascriptEval->scriptToEvaluate = 'NonexistentFile.js';
    eval( $javascriptEval->evalScript );
    echo 'This should read \'FILENAME (...) IS NOT A VALID FILE\'<br/><pre>'.Sanitize::html( $javascriptEval->scriptResults ).'</pre><br/><hr/>';

    // Test #3: This should include the JavaScript file, and evaluate it
    //     as if it was copy-and-pasted in, right here
    $javascriptEval->scriptToEvaluate = 'JavaScriptToEval.js';
    eval( $javascriptEval->evalScript );
    echo Sanitize::html( 'This should be the JavaScript file, with <?php ?> element evaluated').
        '<br/><pre>'. Sanitize::html( $javascriptEval->scriptResults ).'</pre>';

    // Normally, you'll want to include the resulting script in your page, like so:   
    $javascript->codeBlock( $javascriptEval->scriptResults, array('inline'=>false)  );
        // Because we're using the 'inline' => false setting, Cake will put the code into a
        // SCRIPT block in the head of the document.
        // Therefore we don't need to echo it to the document here....
?>
<br/>
<br/>
<hr/>
<br/>
<br/>
<h1>This is the jQuery/CakePHP demo for the 'JavascriptEval' helper</h1>
<p>These lines were generated using normal CakePHP routines</p>

<?php
    // Simple, jQuery link that we'll use in the JavaScript
    echo $html->link("Click here!", "#", array("id"=>'theLink'));
?>

This does a couple of quick unit tests on the provided helper.  It makes sure that the script will detect a missing filename, an incorrect filename, and that it will function correctly when given a correct filename.  Note that the core of this (the ‘typical usage’) boils down to three key steps:

  1. Set the scriptToEvaluate field to the relative path of the file within /app/webroot/js/.
  2. Use PHP’s eval function on the scriptToEvaluate field
  3. Retrieve the scriptResults field, and then use the core JavaScript helper to put the evaluated script into the head of the document.
Typical usage:

<?php 
    $javascriptEval->scriptToEvaluate = 'JavaScriptToEval.js';

    eval( $javascriptEval->evalScript );

    $javascript->codeBlock( $javascriptEval->scriptResults, array('inline'=>false)  );
?>

Note that because the helper assumes that we’ll be using it in a view, we do not need to set the name_of_javascript_eval_object variable. 

JavascriptToEval.php
(this is the JavaScript  – to be placed into /app/webroot/js/JavascriptToEval.js)

/**
* @author Panitz
*/

alert("Hello, from evaluated JavaScript!");

var outputString = <?php

echo '"hello, world!\n'; // note the " inside to start the string

for( $i = 0; $i < 4; $i++) {
    echo 'Iteration '. $i .'\n';
}
echo '"
'; // don't forget to close the string!

// The real goal: Note the use of the CakePHP things that we're
// using, just as if this code was executing inside the .CTP file
//    (which it is :)   )
$testHTML="<h1>BigTitle</h1>";
echo 'alert("sanitized html:\n'. Sanitize::html($testHTML) .'");';

echo $javascript->codeBlock("//Using the core JavaScript Helper!", array('inline'=>false))

?>

$(document).ready(function(){
    $("#theLink").click( function() {
        alert(outputString);
    } );

});

This basically contains a bunch of small tests, in order to demonstrate what you can do with this helper.  When you actually use this helper yourself, you’d want to replace the contents of this file with the JavaScript that you want to dynamically generate using (Cake)PHP. 

The first alert confirms that the JavaScript is loading correctly.  We then use PHP to generate a JavaScript string that we’ll use later on.

We then get into the really useful/interesting stuff – since the view imported the Sanitize class, we can use it here.  Similarly, we can use the helpers that the view has access to, such as the $javascript helper.

Following all that, we have an ultra-simple use of jQuery (which I talked about in my prior post)

A better way?

My original motivation for doing this was being able to create JavaScript code that could deal with dynamically created forms.  For example, I might want to create a single page that contains a list of all the assignments the students will do in a term, with a bit of JavaScript/jQuery attached to each item, so that each and every item can have it’s own rich interface (for example, having each item include a datepicker control).

In retrospect, I think that it’s possible (and better) to write the jQuery code so that it will attach event handlers to all of the UI elements (possibly by ‘tagging’ each one with a CSS class), and then just use that one, single, cache-able, normal JavaScript file instead of dynamically generating code for each client.

Still, the above hack is pretty darn cool, and was fun to try out :)

I’d love to hear if this post helped you out, if you’ve got further questions, or if you’ve got feedback on my blog.

July 06 2009 | CakePHP and Technology and jQuery | No Comments »

Using jQuery with CakePHP: The Basics

First steps towards a drag-and-drop, calendar-based interface for assigning due dates in my StudentTracker web app.

It’s summer time, which means I finally have large(-ish) blocks of free time again.  Of course, with great time comes a great desire to accomplish something satisfying, so naturally I’m thinking about sprucing up my StudentTracker© web app.  I wrote the CakePHP-based version last summer, so that my students could submit their homework online this past year.  It’s worked well, but there’s always that extra something that I want to add to it :)

One feature that I didn’t quite get to last summer was having due dates for assignments.  This was left out partly because of time constraints, and partly because I realized that I’d hit that stage where “I can always add more features/fiddle with it needlessly!!”, so I stopped working on it.  On top of that, I had tried to use CakePHP’s nascent support for Prototype-based AJAX.  I could never quite get AJAX to work well, owing mostly to the lack of documentation for that (apparently) new feature.  That, plus the odd implementation that I found daunting (it appeared to detect changes in the selection of a drop-down menu using polling).

Of course, having taught for years I am comfortable managing due dates manually.  I tell the students when stuff is due in class, send out email notifying them of changes via the class’s Google Group, etc, etc.

But during this past year, I realized that I really did want to have that due date feature.  Not because it could auto-ding my students, but because it could provide each student with a personalized ‘TODO’ list.  Since StudentTracker knows which assignments each student has handed in, it could use the due-date information to produce a list of assignments that each student still needed to do.  I’d like to continue managing due dates myself so that consistently hard-working students who occasionally get a bit behind don’t get penalized.  StudentTracker can then tell each student what that student still needs to do.

The first step in adding this functionality is to create a way for the instructor to enter due dates for assignments, obviously.  The problem with doing this using normal HTML forms is that entering dates is a bit tricky.  Forcing the user to enter the month/day/year is awkward, and it’s easy to make mistakes.  Plus, when you’re looking to enter the due dates for 20 or more items for each of three classes, this gets real old, real fast.  Think about it – if you want to assign a due date to each of four homework assignments, and their allowed revisions, and the 10 weekly warm-up exercises, and the mid-term and final exams, and possibly the daily, 2-point ‘Are you paying attention?’ quizzes,  you can easily pile up 20-30 due dates per course that you’re teaching.  Multiply that by the number of classes you need to teach to fill out your load (three, in my case), and you can see how entering month/day/year gets tedious.

Thus, my immediate goal is to incorporate a ‘date picker’ UI widget into my web app.

After looking around for a while, I decided to use the jQuery UI’s Datepicker, because it looks nice, seems functional, and because I’ve heard a lot of good things about jQuery.  Plus, jQuery has a FullCalendar plugin that will allow me to (eventually) provide a calendar-based view of all the assignments, along with a drag-and-drop mechanism for changing due dates.  But that, along with my scheme for defining ‘relative due dates’ (to make it easier to move assignments to a new quarter), are topics for future posts.

In this post I’ll talk about how the very basics of using jQuery with CakePHP.  But just the uber-basics of using the jQuery library, and not the jQuery UI.  I’m assuming that you’ve both used jQuery a bit, and that you’ve used CakePHP a bit.  If you want help with their of those technologies on their own, you should search the web for tutorials on them.

In a nutshell:

  1. You’re now off and running with jQuery and CakePHP!

Let’s go through the above steps in more detail.

1. Create your normal CakePHP pages

File_PlacementFigure 1: Directory structure

You’ll need to create all the normal CakePHP classes, so you’ll need to create a file that contains your Controller, and a file that contains your View.  For this simple demonstration we’re going to leave out the ‘Model’, but adding that in won’t be difficult.

On the right, you can see a picture of the files, and where they go.  I’ve decided to make the web page that you’ll visit available at http://localhost/Demos/demo/jQueryDemo/, so the controller must therefore be placed in a file named demo_controller.php (in the app/controllers folder).

The contents of the controller file are pretty straightforward, and are included in this post below.  The only tricky parts are setting the $uses field to be an empty array (so that Cake does not try to use any particular model for this model-free controller), and adding the JavaScript helper in the jQueryDemo action (rather than to the entire controller).  You could easily add the JavaScript helper to the entire controller, but if you’re only using it in a limited subset of the actions, then only adding that helper where it’s used prevent clutter.

Just in case it’s useful, I’m also providing a .ZIP file that contains all the files I discuss in this post.  It’s set up so that you should be able to extract this into your app directory, and have the files and folders appear in the correct place.

<?php
class DemoController extends AppController {var $name = ‘Demo’;

// For this simple demo, make sure that

// the controller does not use any model

var $uses = array();

    function jQueryDemo() {

//        Configure::write(’debug’, 3);

$this->helpers[] = ‘Javascript’;

}

}

?>

 

You’ll notice (in the above code) I want the name of the action to be jQueryDemo, so I’ll need to create a file named j_query_demo.ctp (in the app/views folder).  The contents of this file is a little bit more interesting, as we’ll explore in the next step

2. Link to jQuery

At this point, you’ve got a couple of options.  You can either link to Google’s copy (and thus take advantage of their Content Delivery Network – CDN), or else link to your own, local copy.  Using Google’s copy is easier, but you then rely upon them continuing to provide these files.  Using your local copy means that you don’t need to worry about the files disappearing/changing versions/etc, but then you have to supply them to anyone who visits your site (and thus pay for that bandwidth).

http://encosia.com/2008/12/10/3-reasons-why-you-should-let-google-host-jquery-for-you/ makes a pretty compelling argument that one ought to use Google’s CDN for internet-facing apps, and local copies for LAN-based apps, but you can choose whichever you feel is best for your app.  I’m guessing that linking to Google’s copy and downloading a local backup copy for yourself is probably the best way to go.

We’ll look at what we’ve got here in the <?php ?> element.  Everything else in the file is straight HTML, which we’ll use later on in the jQuery code that we write. (Alternately, you could use Cake to generate some of this HTML, but the overall process is the same)

2a: Link to jQuery (Google’s copy)

This is pretty straightforward.  We’ll use the JavaScript helper that we pulled in back in the controller to link to Google’s copy.  That second ‘false’ parameter says NOT to put the code here (i.e., not to inline it), but instead to put it in the <head> of the document.

The second $javascript->link will create a link to our jQuery code, which (because we’re writing it ourselves) must reside locally.

<?php
echo $javascript->link(’http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js’, false);     echo $javascript->link(’demo/jQueryDemo’, false);

?>

<p id=”theTarget”>Starting Text</p>

<a href=”#”>Click me!”</a>

<p id=”clickTarget”>Number: 0</p>

 

2b: Link to jQuery (your own, local copy)

This is a tad more involved, just because you need to obtain your own copy of jQuery, and then install it into the app/webroot/js directory somewhere.  The upside is that you don’t have to worry about Google’s copy going away, or changing (such as when the ‘latest’ version changes to something newer), plus this will work if you’re going to deploy your app onto a LAN that might not be connected to the internet.

You can get yourself a copy of the core jQuery library at: http://docs.jquery.com/Downloading_jQuery

I recommend creating a app/webroot/js/jquery folder, just to keep the jQuery libraries (such as the jQuery UI) separated from all your other JavaScript.  You can see this depicted in Figure 1, above.  We’ll use the JavaScript helper that we pulled in back in the controller to link to our copy, as you can see in the code listed below.  That second ‘false’ parameter says NOT to put the code here (i.e., not to inline it), but instead to put it in the <head> of the document.

The second $javascript->link will create a link to our jQuery code, which (because we’re writing it ourselves) must reside locally.

<?php
echo $javascript->link(’jquery/jquery’, false); // in app/webroot/js/jquery folder,// link to ‘jquery.js’ file

    echo $javascript->link(’demo/jQueryDemo’, false);

?>

<p id=”theTarget”>Starting Text</p>

<a href=”#”>Click me!”</a>

<p id=”clickTarget”>Number: 0</p>

 

3. Create your own JavaScript file in the app/webroot/js directory, and link to it

The last thing to do is to create a JavaScript file that contains all your jQuery-based code.  In order to use this with Cake’s JavaScript helper, you should put it in the app/webroot/js folder, probably in a sub-folder named for your controller.  In Figure 1 (above) I put the jQueryDemo.js file into the folder app/webroot/js/demo, since the controller is named DemoController.

Remember that we linked to this file in the view (.ctp) file using the line:

    echo $javascript->link(’demo/jQueryDemo’, false);

At this point, we’ve done all the heavy Cake lifting, and from here, it’s all just using jQuery to do what we want.  If you read through a tutorial like http://docs.jquery.com/Tutorials:Getting_Started_with_jQuery, you’ll be up and running with jQuery (and now CakePHP!).  Here’s a quick, commented tour through the jQueryDemo.js file:

/*** @author Mike Panitz*/

var counter = 1;

$(document).ready(function(){

               alert(”Executing jQuery ‘onReady’ script!”);// pop up this alert when

// jQuery runs this method.  You’ll be able

// to see the “Starting Text”

// at the top of the page     $(”#theTarget”).text(”New text!”);

    // This will change the ‘Starting Text’ to

    // “New Text!”     // When any link is clicked, alert the user,

    // then change the text of #clickTarget

$(”a”).click( function() {

alert(”You clicked on the link!”);

$(”#clickTarget”).text(”Number:” + counter++);

}  );

});

4. You’re now off and running with jQuery and CakePHP!

Load up the page, and you’ll see something like:

Starting_View

Once you’ve clicked on the alert box, the top line of text will change and you’ll see something like:

After_ready

Click that link a couple of times, and you’ll see the bottom line of text update, like so:

After_several_clicks

I’d love to hear if this post helped you out, if you’ve got further questions, or if you’ve got feedback on my blog. 

June 22 2009 | CakePHP and jQuery | No Comments »