Proof of Concept:

Building Web Pages using Scriptaculous and Prototype:

[Extending Scriptaculous and Switching Draggables with Sortables]


Index:

  1. Introduction
  2. Switching Draggables with Sortables
  3. Overridding Scriptaculous
  4. Application Design
  5. Application Process Flow
  6. Future Development
  7. Summary

Other projects by EverAge Consulting:
Check out Story Wheel - it is the newest educational game for the iPhone

Introduction:

My company has the desire to create an application which would allow the user to build web pages by dragging HTML components from a tree (each displayed as a thumbnail preview image) into sections of a web page which may be then sorted.

Sounds like a reasonable request - but it turned out to involve many steps to get there. I hope that my explanation is of use to someone out there trying to do something similar.

The overall process was to be designed to allow the user to drag HTML elements from the tree on the left side of the screen into predefined 'containers' within the web page. After dropping the element, the user would be able to reorder the element within the container. The kicker is that the item being dragged may be a preview thumbnail image of the html fragment - but when the user drops the item, the actual HTML is to be inserted. Tricky.

To see what the solution looks like, click here

While going through the explanation, please refer to the javascript file which contains all of the javascript functions:

Click here to download the main javascript file for review

Also, scriptaculous is extended to add some call backs (for start drag and finish drag). For this part of the explanation, please refer to the following javascript file:

Click here to download the scriptaculous extensions javascript file for review
[Back to index]

Switching Draggables with Sortables:

The first hurdle was the concept that you can dynamically change the behavior of elements on the fly. So a Sortable DIV may be changed to be a Droppable DIV - then back again. This was the technique used here - the overall process was:

I'll cover the details of how I did this later... [Back to index]


Overriding Scriptaculous:

In order to switch draggables and sortables, I needed to know when a drag event started. At the time of development, there was no such callback. So I had to extend the existing 'startDrag' function within scriptaculous to include the callback that I needed. Please refer to the extensions file (Extensions file)

Without going into too much detail regarding the entire function, you will see that I simply copied the existing 'startDrag' function and have added a single line.

For 'startDrag', I have added the following line:
if(this.options.dragStart) this.options.dragStart(this.element); // NOTE: Added new callback for dragStart
When a Draggable object is created, a 'dragStart' object may define a function to be called when this event occurs.
For example, here is the line used to create the Draggable object:
new Draggable(asset,{ghosting: true,revert:true,dragStart:function(element){dragStart(element)}});

As you can see, when the drag start event occurs, the 'dragStart' function (which I wrote) is called (passing the element along).

Note that you must include any custom extension files in the 'scriptaculous.js' file. For example, append the custom filename to the existing list:
builder,effects,dragdrop,controls,slider,your filename here (withint the extension)

[Back to index]


Application Design:

There are a couple of design features worth mentioning before going over the process flow.

Referencing elements by classname
When creating Draggables and Sortables, Scriptaculous expects a DIV element (or id) as the input. A goal of the application is to allow any number of items in the tree to be draggable and the number of containers within the web page could change. Therefore, I chose to give each one of these DIV element a common classname (tree items are 'treeItem' and containers are 'container').

I then utilize the Prototype function getElementsByClassName to get al elements of a given class name. Then it's just a matter of looping over the elements to perform the desired action on all of them.

Technique of dragging a thumbnail but dropping an HTML fragment
If you view the source of the demo page (The demo page) and scroll to the bottom, you will see the tree item data. As you will see, the tree contains both the thumbnail image and the actual data. For example:
<div class="treeItem"><img src="everage_thumb.gif"> <--- The thumnail image is displayed
	<div class="assetData" style="display:none">	<--- The HTML fragment data is hidden (within the tree)
		<div class="mainBorderSmall">
			<div class="greenBar"></div>
			<div class="body"><img src="everage.jpg"></div>
			<div class="greenBar"></div>
		</div>
	</div>
</div>

When the element is dropped onto a container, a function is caled which extracts the data from the DIV element and inserts it at the top of the container.
Storing class names globally
The entire process is kicked-off within the demo.html file (The demo page). The body onload event calls the 'init()' function.
function init() {
    initDraggableDroppable({draggableClass:'treeItem',droppableClass:'container',assetDataClass:'assetData',containerDataClass:'containerAssetData'});
}
This passes the class names used throughout the application into the 'initDraggableDroppable' function which retains these variables globally. (I'm pointing this out right away because I know that I'm breaking some design principles here... go easy on me ;) )

[Back to index]


Application Process Flow:

Chances are that you can probably figure out the process flow by looking at the code, but I'll run through the process here.
At this point you should have all of the source files open for review.

  1. Application(demo.html): Loads the HTML page which fires the BODY onLoad event
  2. Application(demo.html): The onLoad event calls the init() function which calls to the initDraggableDroppable function passing all of the classnames used.
  3. Application(draggable_droppable.js): initDraggableDroppable calls to createAllDraggables and createAllSortables to make the tree items draggable and the container items sortable.
  4. User: Drags an item from the tree
  5. Application(draggable_droppable.js): The Draggable 'startDrag' event is fired which calls to the 'dragStart' function.
  6. Application(draggable_droppable.js): The 'dragStart' function calls to 'createAllDroppables()' which makes the containers Droppables.
  7. User: Drops the item onto a container
  8. Application(draggable_droppable.js): The Droppable 'onDrop' event fires which calls to the 'dropObject' function.
  9. Application(draggable_droppable.js): The 'dropObject' function calls to 'addObjectToContainer' (which extracts the HTML fragement and inserts it into the container) then makes the Droppables Sortables (allowing the user to sort the dropped elements within the containers).
  10. User: Sorts elements within a container
  11. Application: Scriptaculous handles the sorting as per usual

[Back to index]


Future Development:

This solution is only a proof of concept. The ability to remove items using a context menu is something I have completed (along with many other features), but I didn't want to clutter to code unnecessarily. These pages that are created do contain additional DIV tags for the containers, so they would need to be transformed using an XSLT (or some other process) if this is a concern.

[Back to index]


Summary:

It took me a while to ultimately come up with the solution to this problem - I hope I have saved someone some time.
If you use this technique, I'd love to hear about it. Please contact me at cgibb@everage.ca

[Back to index]