Here is a minimal taskpad in jQuery. It was a multi-skill demonstration for a company that I interviewed with just a few weeks ago. They were interested in a UI demonstration, along with some basic PHP and MySQL. Opportunity: 18 of 30.1
Minimal Taskpad
Add Task: Enter text and click add task. (or Tab)
Complete: Click the checkbox to mark tasks complete.
Edit: Double click on task text to edit. (Tab to finish editing.)
Sort: Drag tasks up or down.
var taskpad = { model: { task_items: [],//List of task items response: "Unset", task_list_name: "tasks_list", status_container_class: "task_status", remove_container_class: "remove", status_on_class: "not_complete", status_off_class: "complete", new_task_button: 'new_task_label', new_task_input_id: 'new_task', remove_all_button: 'remove_all', pending_get: false, pending_changes: false, pending_remote: false, controller_url: "" //URL OF WEB SERVICE }, view: { remove_button_text: 'remove', appendTask: function(task_item, task_class) { //text if (task_item.length > 0) { if (task_class == true) { task_class = 'complete'; } else { task_class = 'not_complete'; } var model = taskpad.model; $('#'+model.new_task_input_id).val(''); var html = ""+ "<li class='task "+task_class+"' >"+ "<div class='task_status'> </div>"+ "<div class='task_item'>"+task_item+"</div>"+ "<div class='remove'>"+this.remove_button_text+"</div>"+ "</li>"; $('#'+model.task_list_name).append(html); taskpad.controller.addButtonBehavior($('#'+model.task_list_name+' .task:last')); } taskpad.controller.setRemoteTaskpad(); }, setEditable: function(item) {//element var current_item_text = $(item).text(); var html = ""+ "<input type='text' value='"+current_item_text+ "' onblur='taskpad.view.disableEditable(this)'>"; $(item).html(html); ($(item).children()[0]).focus(); }, disableEditable: function(item) { if (($(item).val()).length <= 0) { $(item).parent().parent().remove(); } else { $(item).parent().html($(item).val()); taskpad.controller.setRemoteTaskpad(); } } }, controller: { addButtonBehavior: function(item) { /* Methods like these are broken out for two reasons. 1. Reuse 2. Ease of maintenance */ //Create Complete Button ($($(item).children()[0])).click(function(event, id){ if ($($(this).parent()).hasClass(taskpad.model.status_on_class)) { $(this).parent().switchClass(taskpad.model.status_off_class, taskpad.model.status_on_class); //Remove/Add } else { $(this).parent().switchClass(taskpad.model.status_on_class, taskpad.model.status_off_class); //Remove/Add } //FOR COMPLETE BUTTON, because it's not broken out. taskpad.controller.setRemoteTaskpad(); }); //Create Remove Button ($($(item).children()[2])).click(function(event, id){ $(this).parent().remove(); //FOR REMOVE, because it's not broken out. taskpad.controller.setRemoteTaskpad(); }); //Create Editable Button ($($(item).children()[1])).dblclick(function(event, id){ taskpad.view.setEditable(this); }); }, getRemoteTaskpad: function() { var url= taskpad.model.controller_url; if (taskpad.model.pending_get == false) { taskpad.model.pending_get = true; $.getJSON(url, function(data){ $.each(data.items, function(i,item){ taskpad.view.appendTask(data.items[i].task_item, data.items[i].complete); }); taskpad.model.pending_get = false; } ); } }, setRemoteTaskpad: function() { var url= taskpad.model.controller_url; var data = taskpad.controller.buildTaskPadXML(); if ((data.length > 0) && (taskpad.model.pending_get == false) //If there is a pending remote, bail. && (taskpad.model.pending_remote == false) ) { taskpad.model.pending_remote = true; taskpad.model.pending_changes = false; $.post(url, 'updated_list='+data, function() { taskpad.model.pending_remote = false; if (taskpad.model.pending_changes == true) { taskpad.controller.setRemoteTaskpad(); } }, "html"); } else if ((data.length > 0) && (taskpad.model.pending_get == false) && (taskpad.model.pending_remote == true) && (taskpad.model.pending_changes == false)) { taskpad.model.pending_changes = true; } }, buildTaskPadXML: function() { var taskPadXML = '<?xml version="1.0" ?><taskpad>'; $('.task').each( function() { var text = $($(this).children()[1]).text(); var status = $(this).hasClass(taskpad.model.status_off_class); taskPadXML = taskPadXML + '<task><text>'+text+'</text><status>'+status+'</status></task>'; }); taskPadXML += "</taskpad>"; return taskPadXML; } } } var init = function() { var model = taskpad.model; var view = taskpad.view; var controller = taskpad.controller; //TASK LIST Sortable|Properties $('#'+model.task_list_name).sortable( //Setting draggable properties. Too loose by default. { axis: 'y', containment: 'parent', cursor: 'pointer', items: 'li', update: function(event, ui) { taskpad.controller.setRemoteTaskpad(); } } ); taskpad.controller.getRemoteTaskpad(); //ADD TASK BUTTON $('#'+model.new_task_button).click(function(event){ //Validation, etc if ($('#'+model.new_task_input_id).val().length > 1) { view.appendTask($('#'+model.new_task_input_id).val()); } }); //ADD TASK BUTTON (TAB) $('#'+model.new_task_input_id).blur(function(event){ if ($('#'+model.new_task_input_id).val().length > 1) { view.appendTask($('#'+model.new_task_input_id).val()); } }); //ADD REMOVE ALL BUTTON (TAB) $('#'+model.remove_all_button).click(function(event){ $('#'+model.task_list_name).html(""); taskpad.controller.setRemoteTaskpad(); }); }taskpad.js
1Those of you who are following my job search know the 30-point scale of opportunity.

