You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tapestry.apache.org by hl...@apache.org on 2012/10/24 18:51:19 UTC

[7/15] Reorganize a number of assets under META-INF/assets

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/02ed252a/tapestry-core/src/main/resources/META-INF/assets/tapestry5/scriptaculous_1_9_0/scriptaculous.js
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/resources/META-INF/assets/tapestry5/scriptaculous_1_9_0/scriptaculous.js b/tapestry-core/src/main/resources/META-INF/assets/tapestry5/scriptaculous_1_9_0/scriptaculous.js
new file mode 100644
index 0000000..7383588
--- /dev/null
+++ b/tapestry-core/src/main/resources/META-INF/assets/tapestry5/scriptaculous_1_9_0/scriptaculous.js
@@ -0,0 +1,59 @@
+// script.aculo.us scriptaculous.js v1.9.0, Thu Dec 23 16:54:48 -0500 2010
+
+// Copyright (c) 2005-2010 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+// For details, see the script.aculo.us web site: http://script.aculo.us/
+
+var Scriptaculous = {
+  Version: '1.9.0',
+  // require: function() { ... } removed (not used in Tapestry)
+  REQUIRED_PROTOTYPE: '1.6.0.3',
+  load: function() {
+    function convertVersionString(versionString) {
+      var v = versionString.replace(/_.*|\./g, '');
+      v = parseInt(v + '0'.times(4-v.length));
+      return versionString.indexOf('_') > -1 ? v-1 : v;
+    }
+
+    if((typeof Prototype=='undefined') ||
+       (typeof Element == 'undefined') ||
+       (typeof Element.Methods=='undefined') ||
+       (convertVersionString(Prototype.Version) <
+        convertVersionString(Scriptaculous.REQUIRED_PROTOTYPE)))
+       throw("script.aculo.us requires the Prototype JavaScript framework >= " +
+        Scriptaculous.REQUIRED_PROTOTYPE);
+
+// Tapestry turns off this mechanism, and replaces it with the JavaScriptSupport environmental.
+
+//    var js = /scriptaculous\.js(\?.*)?$/;
+//    $$('head script[src]').findAll(function(s) {
+//      return s.src.match(js);
+//    }).each(function(s) {
+//      var path = s.src.replace(js, ''),
+//      includes = s.src.match(/\?.*load=([a-z,]*)/);
+//      (includes ? includes[1] : 'builder,effects,dragdrop,controls,slider,sound').split(',').each(
+//       function(include) { Scriptaculous.require(path+include+'.js') });
+//    });
+  }
+};
+
+Scriptaculous.load();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/02ed252a/tapestry-core/src/main/resources/META-INF/assets/tapestry5/scriptaculous_1_9_0/slider.js
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/resources/META-INF/assets/tapestry5/scriptaculous_1_9_0/slider.js b/tapestry-core/src/main/resources/META-INF/assets/tapestry5/scriptaculous_1_9_0/slider.js
new file mode 100644
index 0000000..438e689
--- /dev/null
+++ b/tapestry-core/src/main/resources/META-INF/assets/tapestry5/scriptaculous_1_9_0/slider.js
@@ -0,0 +1,275 @@
+// script.aculo.us slider.js v1.9.0, Thu Dec 23 16:54:48 -0500 2010
+
+// Copyright (c) 2005-2010 Marty Haught, Thomas Fuchs
+//
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
+// For details, see the script.aculo.us web site: http://script.aculo.us/
+
+if (!Control) var Control = { };
+
+// options:
+//  axis: 'vertical', or 'horizontal' (default)
+//
+// callbacks:
+//  onChange(value)
+//  onSlide(value)
+Control.Slider = Class.create({
+  initialize: function(handle, track, options) {
+    var slider = this;
+
+    if (Object.isArray(handle)) {
+      this.handles = handle.collect( function(e) { return $(e) });
+    } else {
+      this.handles = [$(handle)];
+    }
+
+    this.track   = $(track);
+    this.options = options || { };
+
+    this.axis      = this.options.axis || 'horizontal';
+    this.increment = this.options.increment || 1;
+    this.step      = parseInt(this.options.step || '1');
+    this.range     = this.options.range || $R(0,1);
+
+    this.value     = 0; // assure backwards compat
+    this.values    = this.handles.map( function() { return 0 });
+    this.spans     = this.options.spans ? this.options.spans.map(function(s){ return $(s) }) : false;
+    this.options.startSpan = $(this.options.startSpan || null);
+    this.options.endSpan   = $(this.options.endSpan || null);
+
+    this.restricted = this.options.restricted || false;
+
+    this.maximum   = this.options.maximum || this.range.end;
+    this.minimum   = this.options.minimum || this.range.start;
+
+    // Will be used to align the handle onto the track, if necessary
+    this.alignX = parseInt(this.options.alignX || '0');
+    this.alignY = parseInt(this.options.alignY || '0');
+
+    this.trackLength = this.maximumOffset() - this.minimumOffset();
+
+    this.handleLength = this.isVertical() ?
+      (this.handles[0].offsetHeight != 0 ?
+        this.handles[0].offsetHeight : this.handles[0].style.height.replace(/px$/,"")) :
+      (this.handles[0].offsetWidth != 0 ? this.handles[0].offsetWidth :
+        this.handles[0].style.width.replace(/px$/,""));
+
+    this.active   = false;
+    this.dragging = false;
+    this.disabled = false;
+
+    if (this.options.disabled) this.setDisabled();
+
+    // Allowed values array
+    this.allowedValues = this.options.values ? this.options.values.sortBy(Prototype.K) : false;
+    if (this.allowedValues) {
+      this.minimum = this.allowedValues.min();
+      this.maximum = this.allowedValues.max();
+    }
+
+    this.eventMouseDown = this.startDrag.bindAsEventListener(this);
+    this.eventMouseUp   = this.endDrag.bindAsEventListener(this);
+    this.eventMouseMove = this.update.bindAsEventListener(this);
+
+    // Initialize handles in reverse (make sure first handle is active)
+    this.handles.each( function(h,i) {
+      i = slider.handles.length-1-i;
+      slider.setValue(parseFloat(
+        (Object.isArray(slider.options.sliderValue) ?
+          slider.options.sliderValue[i] : slider.options.sliderValue) ||
+         slider.range.start), i);
+      h.makePositioned().observe("mousedown", slider.eventMouseDown);
+    });
+
+    this.track.observe("mousedown", this.eventMouseDown);
+    document.observe("mouseup", this.eventMouseUp);
+    document.observe("mousemove", this.eventMouseMove);
+
+    this.initialized = true;
+  },
+  dispose: function() {
+    var slider = this;
+    Event.stopObserving(this.track, "mousedown", this.eventMouseDown);
+    Event.stopObserving(document, "mouseup", this.eventMouseUp);
+    Event.stopObserving(document, "mousemove", this.eventMouseMove);
+    this.handles.each( function(h) {
+      Event.stopObserving(h, "mousedown", slider.eventMouseDown);
+    });
+  },
+  setDisabled: function(){
+    this.disabled = true;
+  },
+  setEnabled: function(){
+    this.disabled = false;
+  },
+  getNearestValue: function(value){
+    if (this.allowedValues){
+      if (value >= this.allowedValues.max()) return(this.allowedValues.max());
+      if (value <= this.allowedValues.min()) return(this.allowedValues.min());
+
+      var offset = Math.abs(this.allowedValues[0] - value);
+      var newValue = this.allowedValues[0];
+      this.allowedValues.each( function(v) {
+        var currentOffset = Math.abs(v - value);
+        if (currentOffset <= offset){
+          newValue = v;
+          offset = currentOffset;
+        }
+      });
+      return newValue;
+    }
+    if (value > this.range.end) return this.range.end;
+    if (value < this.range.start) return this.range.start;
+    return value;
+  },
+  setValue: function(sliderValue, handleIdx){
+    if (!this.active) {
+      this.activeHandleIdx = handleIdx || 0;
+      this.activeHandle    = this.handles[this.activeHandleIdx];
+      this.updateStyles();
+    }
+    handleIdx = handleIdx || this.activeHandleIdx || 0;
+    if (this.initialized && this.restricted) {
+      if ((handleIdx>0) && (sliderValue<this.values[handleIdx-1]))
+        sliderValue = this.values[handleIdx-1];
+      if ((handleIdx < (this.handles.length-1)) && (sliderValue>this.values[handleIdx+1]))
+        sliderValue = this.values[handleIdx+1];
+    }
+    sliderValue = this.getNearestValue(sliderValue);
+    this.values[handleIdx] = sliderValue;
+    this.value = this.values[0]; // assure backwards compat
+
+    this.handles[handleIdx].style[this.isVertical() ? 'top' : 'left'] =
+      this.translateToPx(sliderValue);
+
+    this.drawSpans();
+    if (!this.dragging || !this.event) this.updateFinished();
+  },
+  setValueBy: function(delta, handleIdx) {
+    this.setValue(this.values[handleIdx || this.activeHandleIdx || 0] + delta,
+      handleIdx || this.activeHandleIdx || 0);
+  },
+  translateToPx: function(value) {
+    return Math.round(
+      ((this.trackLength-this.handleLength)/(this.range.end-this.range.start)) *
+      (value - this.range.start)) + "px";
+  },
+  translateToValue: function(offset) {
+    return ((offset/(this.trackLength-this.handleLength) *
+      (this.range.end-this.range.start)) + this.range.start);
+  },
+  getRange: function(range) {
+    var v = this.values.sortBy(Prototype.K);
+    range = range || 0;
+    return $R(v[range],v[range+1]);
+  },
+  minimumOffset: function(){
+    return(this.isVertical() ? this.alignY : this.alignX);
+  },
+  maximumOffset: function(){
+    return(this.isVertical() ?
+      (this.track.offsetHeight != 0 ? this.track.offsetHeight :
+        this.track.style.height.replace(/px$/,"")) - this.alignY :
+      (this.track.offsetWidth != 0 ? this.track.offsetWidth :
+        this.track.style.width.replace(/px$/,"")) - this.alignX);
+  },
+  isVertical:  function(){
+    return (this.axis == 'vertical');
+  },
+  drawSpans: function() {
+    var slider = this;
+    if (this.spans)
+      $R(0, this.spans.length-1).each(function(r) { slider.setSpan(slider.spans[r], slider.getRange(r)) });
+    if (this.options.startSpan)
+      this.setSpan(this.options.startSpan,
+        $R(0, this.values.length>1 ? this.getRange(0).min() : this.value ));
+    if (this.options.endSpan)
+      this.setSpan(this.options.endSpan,
+        $R(this.values.length>1 ? this.getRange(this.spans.length-1).max() : this.value, this.maximum));
+  },
+  setSpan: function(span, range) {
+    if (this.isVertical()) {
+      span.style.top = this.translateToPx(range.start);
+      span.style.height = this.translateToPx(range.end - range.start + this.range.start);
+    } else {
+      span.style.left = this.translateToPx(range.start);
+      span.style.width = this.translateToPx(range.end - range.start + this.range.start);
+    }
+  },
+  updateStyles: function() {
+    this.handles.each( function(h){ Element.removeClassName(h, 'selected') });
+    Element.addClassName(this.activeHandle, 'selected');
+  },
+  startDrag: function(event) {
+    if (Event.isLeftClick(event)) {
+      if (!this.disabled){
+        this.active = true;
+
+        var handle = Event.element(event);
+        var pointer  = [Event.pointerX(event), Event.pointerY(event)];
+        var track = handle;
+        if (track==this.track) {
+          var offsets  = this.track.cumulativeOffset();
+          this.event = event;
+          this.setValue(this.translateToValue(
+           (this.isVertical() ? pointer[1]-offsets[1] : pointer[0]-offsets[0])-(this.handleLength/2)
+          ));
+          var offsets  = this.activeHandle.cumulativeOffset();
+          this.offsetX = (pointer[0] - offsets[0]);
+          this.offsetY = (pointer[1] - offsets[1]);
+        } else {
+          // find the handle (prevents issues with Safari)
+          while((this.handles.indexOf(handle) == -1) && handle.parentNode)
+            handle = handle.parentNode;
+
+          if (this.handles.indexOf(handle)!=-1) {
+            this.activeHandle    = handle;
+            this.activeHandleIdx = this.handles.indexOf(this.activeHandle);
+            this.updateStyles();
+
+            var offsets  = this.activeHandle.cumulativeOffset();
+            this.offsetX = (pointer[0] - offsets[0]);
+            this.offsetY = (pointer[1] - offsets[1]);
+          }
+        }
+      }
+      Event.stop(event);
+    }
+  },
+  update: function(event) {
+   if (this.active) {
+      if (!this.dragging) this.dragging = true;
+      this.draw(event);
+      if (Prototype.Browser.WebKit) window.scrollBy(0,0);
+      Event.stop(event);
+   }
+  },
+  draw: function(event) {
+    var pointer = [Event.pointerX(event), Event.pointerY(event)];
+    var offsets = this.track.cumulativeOffset();
+    pointer[0] -= this.offsetX + offsets[0];
+    pointer[1] -= this.offsetY + offsets[1];
+    this.event = event;
+    this.setValue(this.translateToValue( this.isVertical() ? pointer[1] : pointer[0] ));
+    if (this.initialized && this.options.onSlide)
+      this.options.onSlide(this.values.length>1 ? this.values : this.value, this);
+  },
+  endDrag: function(event) {
+    if (this.active && this.dragging) {
+      this.finishDrag(event, true);
+      Event.stop(event);
+    }
+    this.active = false;
+    this.dragging = false;
+  },
+  finishDrag: function(event, success) {
+    this.active = false;
+    this.dragging = false;
+    this.updateFinished();
+  },
+  updateFinished: function() {
+    if (this.initialized && this.options.onChange)
+      this.options.onChange(this.values.length>1 ? this.values : this.value, this);
+    this.event = null;
+  }
+});
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/02ed252a/tapestry-core/src/main/resources/META-INF/assets/tapestry5/scriptaculous_1_9_0/sound.js
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/resources/META-INF/assets/tapestry5/scriptaculous_1_9_0/sound.js b/tapestry-core/src/main/resources/META-INF/assets/tapestry5/scriptaculous_1_9_0/sound.js
new file mode 100644
index 0000000..d3f8464
--- /dev/null
+++ b/tapestry-core/src/main/resources/META-INF/assets/tapestry5/scriptaculous_1_9_0/sound.js
@@ -0,0 +1,59 @@
+// script.aculo.us sound.js v1.9.0, Thu Dec 23 16:54:48 -0500 2010
+
+// Copyright (c) 2005-2010 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+//
+// Based on code created by Jules Gravinese (http://www.webveteran.com/)
+//
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
+// For details, see the script.aculo.us web site: http://script.aculo.us/
+
+Sound = {
+  tracks: {},
+  _enabled: true,
+  template:
+    new Template('<embed style="height:0" id="sound_#{track}_#{id}" src="#{url}" loop="false" autostart="true" hidden="true"/>'),
+  enable: function(){
+    Sound._enabled = true;
+  },
+  disable: function(){
+    Sound._enabled = false;
+  },
+  play: function(url){
+    if(!Sound._enabled) return;
+    var options = Object.extend({
+      track: 'global', url: url, replace: false
+    }, arguments[1] || {});
+
+    if(options.replace && this.tracks[options.track]) {
+      $R(0, this.tracks[options.track].id).each(function(id){
+        var sound = $('sound_'+options.track+'_'+id);
+        sound.Stop && sound.Stop();
+        sound.remove();
+      });
+      this.tracks[options.track] = null;
+    }
+
+    if(!this.tracks[options.track])
+      this.tracks[options.track] = { id: 0 };
+    else
+      this.tracks[options.track].id++;
+
+    options.id = this.tracks[options.track].id;
+    $$('body')[0].insert(
+      Prototype.Browser.IE ? new Element('bgsound',{
+        id: 'sound_'+options.track+'_'+options.id,
+        src: options.url, loop: 1, autostart: true
+      }) : Sound.template.evaluate(options));
+  }
+};
+
+if(Prototype.Browser.Gecko && navigator.userAgent.indexOf("Win") > 0){
+  if(navigator.plugins && $A(navigator.plugins).detect(function(p){ return p.name.indexOf('QuickTime') != -1 }))
+    Sound.template = new Template('<object id="sound_#{track}_#{id}" width="0" height="0" type="audio/mpeg" data="#{url}"/>');
+  else if(navigator.plugins && $A(navigator.plugins).detect(function(p){ return p.name.indexOf('Windows Media') != -1 }))
+    Sound.template = new Template('<object id="sound_#{track}_#{id}" type="application/x-mplayer2" data="#{url}"></object>');
+  else if(navigator.plugins && $A(navigator.plugins).detect(function(p){ return p.name.indexOf('RealPlayer') != -1 }))
+    Sound.template = new Template('<embed type="audio/x-pn-realaudio-plugin" style="height:0" id="sound_#{track}_#{id}" src="#{url}" loop="false" autostart="true" hidden="true"/>');
+  else
+    Sound.play = function(){};
+} 
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/02ed252a/tapestry-core/src/main/resources/META-INF/assets/tapestry5/scriptaculous_1_9_0/unittest.js
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/resources/META-INF/assets/tapestry5/scriptaculous_1_9_0/unittest.js b/tapestry-core/src/main/resources/META-INF/assets/tapestry5/scriptaculous_1_9_0/unittest.js
new file mode 100644
index 0000000..9555c22
--- /dev/null
+++ b/tapestry-core/src/main/resources/META-INF/assets/tapestry5/scriptaculous_1_9_0/unittest.js
@@ -0,0 +1,568 @@
+// script.aculo.us unittest.js v1.9.0, Thu Dec 23 16:54:48 -0500 2010
+
+// Copyright (c) 2005-2010 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+//           (c) 2005-2010 Jon Tirsen (http://www.tirsen.com)
+//           (c) 2005-2010 Michael Schuerig (http://www.schuerig.de/michael/)
+//
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
+// For details, see the script.aculo.us web site: http://script.aculo.us/
+
+// experimental, Firefox-only
+Event.simulateMouse = function(element, eventName) {
+  var options = Object.extend({
+    pointerX: 0,
+    pointerY: 0,
+    buttons:  0,
+    ctrlKey:  false,
+    altKey:   false,
+    shiftKey: false,
+    metaKey:  false
+  }, arguments[2] || {});
+  var oEvent = document.createEvent("MouseEvents");
+  oEvent.initMouseEvent(eventName, true, true, document.defaultView, 
+    options.buttons, options.pointerX, options.pointerY, options.pointerX, options.pointerY, 
+    options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, 0, $(element));
+  
+  if(this.mark) Element.remove(this.mark);
+  this.mark = document.createElement('div');
+  this.mark.appendChild(document.createTextNode(" "));
+  document.body.appendChild(this.mark);
+  this.mark.style.position = 'absolute';
+  this.mark.style.top = options.pointerY + "px";
+  this.mark.style.left = options.pointerX + "px";
+  this.mark.style.width = "5px";
+  this.mark.style.height = "5px;";
+  this.mark.style.borderTop = "1px solid red;";
+  this.mark.style.borderLeft = "1px solid red;";
+  
+  if(this.step)
+    alert('['+new Date().getTime().toString()+'] '+eventName+'/'+Test.Unit.inspect(options));
+  
+  $(element).dispatchEvent(oEvent);
+};
+
+// Note: Due to a fix in Firefox 1.0.5/6 that probably fixed "too much", this doesn't work in 1.0.6 or DP2.
+// You need to downgrade to 1.0.4 for now to get this working
+// See https://bugzilla.mozilla.org/show_bug.cgi?id=289940 for the fix that fixed too much
+Event.simulateKey = function(element, eventName) {
+  var options = Object.extend({
+    ctrlKey: false,
+    altKey: false,
+    shiftKey: false,
+    metaKey: false,
+    keyCode: 0,
+    charCode: 0
+  }, arguments[2] || {});
+
+  var oEvent = document.createEvent("KeyEvents");
+  oEvent.initKeyEvent(eventName, true, true, window, 
+    options.ctrlKey, options.altKey, options.shiftKey, options.metaKey,
+    options.keyCode, options.charCode );
+  $(element).dispatchEvent(oEvent);
+};
+
+Event.simulateKeys = function(element, command) {
+  for(var i=0; i<command.length; i++) {
+    Event.simulateKey(element,'keypress',{charCode:command.charCodeAt(i)});
+  }
+};
+
+var Test = {};
+Test.Unit = {};
+
+// security exception workaround
+Test.Unit.inspect = Object.inspect;
+
+Test.Unit.Logger = Class.create();
+Test.Unit.Logger.prototype = {
+  initialize: function(log) {
+    this.log = $(log);
+    if (this.log) {
+      this._createLogTable();
+    }
+  },
+  start: function(testName) {
+    if (!this.log) return;
+    this.testName = testName;
+    this.lastLogLine = document.createElement('tr');
+    this.statusCell = document.createElement('td');
+    this.nameCell = document.createElement('td');
+    this.nameCell.className = "nameCell";
+    this.nameCell.appendChild(document.createTextNode(testName));
+    this.messageCell = document.createElement('td');
+    this.lastLogLine.appendChild(this.statusCell);
+    this.lastLogLine.appendChild(this.nameCell);
+    this.lastLogLine.appendChild(this.messageCell);
+    this.loglines.appendChild(this.lastLogLine);
+  },
+  finish: function(status, summary) {
+    if (!this.log) return;
+    this.lastLogLine.className = status;
+    this.statusCell.innerHTML = status;
+    this.messageCell.innerHTML = this._toHTML(summary);
+    this.addLinksToResults();
+  },
+  message: function(message) {
+    if (!this.log) return;
+    this.messageCell.innerHTML = this._toHTML(message);
+  },
+  summary: function(summary) {
+    if (!this.log) return;
+    this.logsummary.innerHTML = this._toHTML(summary);
+  },
+  _createLogTable: function() {
+    this.log.innerHTML =
+    '<div id="logsummary"></div>' +
+    '<table id="logtable">' +
+    '<thead><tr><th>Status</th><th>Test</th><th>Message</th></tr></thead>' +
+    '<tbody id="loglines"></tbody>' +
+    '</table>';
+    this.logsummary = $('logsummary');
+    this.loglines = $('loglines');
+  },
+  _toHTML: function(txt) {
+    return txt.escapeHTML().replace(/\n/g,"<br/>");
+  },
+  addLinksToResults: function(){ 
+    $$("tr.failed .nameCell").each( function(td){ // todo: limit to children of this.log
+      td.title = "Run only this test";
+      Event.observe(td, 'click', function(){ window.location.search = "?tests=" + td.innerHTML;});
+    });
+    $$("tr.passed .nameCell").each( function(td){ // todo: limit to children of this.log
+      td.title = "Run all tests";
+      Event.observe(td, 'click', function(){ window.location.search = "";});
+    });
+  }
+};
+
+Test.Unit.Runner = Class.create();
+Test.Unit.Runner.prototype = {
+  initialize: function(testcases) {
+    this.options = Object.extend({
+      testLog: 'testlog'
+    }, arguments[1] || {});
+    this.options.resultsURL = this.parseResultsURLQueryParameter();
+    this.options.tests      = this.parseTestsQueryParameter();
+    if (this.options.testLog) {
+      this.options.testLog = $(this.options.testLog) || null;
+    }
+    if(this.options.tests) {
+      this.tests = [];
+      for(var i = 0; i < this.options.tests.length; i++) {
+        if(/^test/.test(this.options.tests[i])) {
+          this.tests.push(new Test.Unit.Testcase(this.options.tests[i], testcases[this.options.tests[i]], testcases["setup"], testcases["teardown"]));
+        }
+      }
+    } else {
+      if (this.options.test) {
+        this.tests = [new Test.Unit.Testcase(this.options.test, testcases[this.options.test], testcases["setup"], testcases["teardown"])];
+      } else {
+        this.tests = [];
+        for(var testcase in testcases) {
+          if(/^test/.test(testcase)) {
+            this.tests.push(
+               new Test.Unit.Testcase(
+                 this.options.context ? ' -> ' + this.options.titles[testcase] : testcase, 
+                 testcases[testcase], testcases["setup"], testcases["teardown"]
+               ));
+          }
+        }
+      }
+    }
+    this.currentTest = 0;
+    this.logger = new Test.Unit.Logger(this.options.testLog);
+    setTimeout(this.runTests.bind(this), 1000);
+  },
+  parseResultsURLQueryParameter: function() {
+    return window.location.search.parseQuery()["resultsURL"];
+  },
+  parseTestsQueryParameter: function(){
+    if (window.location.search.parseQuery()["tests"]){
+        return window.location.search.parseQuery()["tests"].split(',');
+    };
+  },
+  // Returns:
+  //  "ERROR" if there was an error,
+  //  "FAILURE" if there was a failure, or
+  //  "SUCCESS" if there was neither
+  getResult: function() {
+    var hasFailure = false;
+    for(var i=0;i<this.tests.length;i++) {
+      if (this.tests[i].errors > 0) {
+        return "ERROR";
+      }
+      if (this.tests[i].failures > 0) {
+        hasFailure = true;
+      }
+    }
+    if (hasFailure) {
+      return "FAILURE";
+    } else {
+      return "SUCCESS";
+    }
+  },
+  postResults: function() {
+    if (this.options.resultsURL) {
+      new Ajax.Request(this.options.resultsURL, 
+        { method: 'get', parameters: 'result=' + this.getResult(), asynchronous: false });
+    }
+  },
+  runTests: function() {
+    var test = this.tests[this.currentTest];
+    if (!test) {
+      // finished!
+      this.postResults();
+      this.logger.summary(this.summary());
+      return;
+    }
+    if(!test.isWaiting) {
+      this.logger.start(test.name);
+    }
+    test.run();
+    if(test.isWaiting) {
+      this.logger.message("Waiting for " + test.timeToWait + "ms");
+      setTimeout(this.runTests.bind(this), test.timeToWait || 1000);
+    } else {
+      this.logger.finish(test.status(), test.summary());
+      this.currentTest++;
+      // tail recursive, hopefully the browser will skip the stackframe
+      this.runTests();
+    }
+  },
+  summary: function() {
+    var assertions = 0;
+    var failures = 0;
+    var errors = 0;
+    var messages = [];
+    for(var i=0;i<this.tests.length;i++) {
+      assertions +=   this.tests[i].assertions;
+      failures   +=   this.tests[i].failures;
+      errors     +=   this.tests[i].errors;
+    }
+    return (
+      (this.options.context ? this.options.context + ': ': '') + 
+      this.tests.length + " tests, " + 
+      assertions + " assertions, " + 
+      failures   + " failures, " +
+      errors     + " errors");
+  }
+};
+
+Test.Unit.Assertions = Class.create();
+Test.Unit.Assertions.prototype = {
+  initialize: function() {
+    this.assertions = 0;
+    this.failures   = 0;
+    this.errors     = 0;
+    this.messages   = [];
+  },
+  summary: function() {
+    return (
+      this.assertions + " assertions, " + 
+      this.failures   + " failures, " +
+      this.errors     + " errors" + "\n" +
+      this.messages.join("\n"));
+  },
+  pass: function() {
+    this.assertions++;
+  },
+  fail: function(message) {
+    this.failures++;
+    this.messages.push("Failure: " + message);
+  },
+  info: function(message) {
+    this.messages.push("Info: " + message);
+  },
+  error: function(error) {
+    this.errors++;
+    this.messages.push(error.name + ": "+ error.message + "(" + Test.Unit.inspect(error) +")");
+  },
+  status: function() {
+    if (this.failures > 0) return 'failed';
+    if (this.errors > 0) return 'error';
+    return 'passed';
+  },
+  assert: function(expression) {
+    var message = arguments[1] || 'assert: got "' + Test.Unit.inspect(expression) + '"';
+    try { expression ? this.pass() : 
+      this.fail(message); }
+    catch(e) { this.error(e); }
+  },
+  assertEqual: function(expected, actual) {
+    var message = arguments[2] || "assertEqual";
+    try { (expected == actual) ? this.pass() :
+      this.fail(message + ': expected "' + Test.Unit.inspect(expected) + 
+        '", actual "' + Test.Unit.inspect(actual) + '"'); }
+    catch(e) { this.error(e); }
+  },
+  assertInspect: function(expected, actual) {
+    var message = arguments[2] || "assertInspect";
+    try { (expected == actual.inspect()) ? this.pass() :
+      this.fail(message + ': expected "' + Test.Unit.inspect(expected) + 
+        '", actual "' + Test.Unit.inspect(actual) + '"'); }
+    catch(e) { this.error(e); }
+  },
+  assertEnumEqual: function(expected, actual) {
+    var message = arguments[2] || "assertEnumEqual";
+    try { $A(expected).length == $A(actual).length && 
+      expected.zip(actual).all(function(pair) { return pair[0] == pair[1] }) ?
+        this.pass() : this.fail(message + ': expected ' + Test.Unit.inspect(expected) + 
+          ', actual ' + Test.Unit.inspect(actual)); }
+    catch(e) { this.error(e); }
+  },
+  assertNotEqual: function(expected, actual) {
+    var message = arguments[2] || "assertNotEqual";
+    try { (expected != actual) ? this.pass() : 
+      this.fail(message + ': got "' + Test.Unit.inspect(actual) + '"'); }
+    catch(e) { this.error(e); }
+  },
+  assertIdentical: function(expected, actual) { 
+    var message = arguments[2] || "assertIdentical"; 
+    try { (expected === actual) ? this.pass() : 
+      this.fail(message + ': expected "' + Test.Unit.inspect(expected) +  
+        '", actual "' + Test.Unit.inspect(actual) + '"'); } 
+    catch(e) { this.error(e); } 
+  },
+  assertNotIdentical: function(expected, actual) { 
+    var message = arguments[2] || "assertNotIdentical"; 
+    try { !(expected === actual) ? this.pass() : 
+      this.fail(message + ': expected "' + Test.Unit.inspect(expected) +  
+        '", actual "' + Test.Unit.inspect(actual) + '"'); } 
+    catch(e) { this.error(e); } 
+  },
+  assertNull: function(obj) {
+    var message = arguments[1] || 'assertNull';
+    try { (obj==null) ? this.pass() : 
+      this.fail(message + ': got "' + Test.Unit.inspect(obj) + '"'); }
+    catch(e) { this.error(e); }
+  },
+  assertMatch: function(expected, actual) {
+    var message = arguments[2] || 'assertMatch';
+    var regex = new RegExp(expected);
+    try { (regex.exec(actual)) ? this.pass() :
+      this.fail(message + ' : regex: "' +  Test.Unit.inspect(expected) + ' did not match: ' + Test.Unit.inspect(actual) + '"'); }
+    catch(e) { this.error(e); }
+  },
+  assertHidden: function(element) {
+    var message = arguments[1] || 'assertHidden';
+    this.assertEqual("none", element.style.display, message);
+  },
+  assertNotNull: function(object) {
+    var message = arguments[1] || 'assertNotNull';
+    this.assert(object != null, message);
+  },
+  assertType: function(expected, actual) {
+    var message = arguments[2] || 'assertType';
+    try { 
+      (actual.constructor == expected) ? this.pass() : 
+      this.fail(message + ': expected "' + Test.Unit.inspect(expected) +  
+        '", actual "' + (actual.constructor) + '"'); }
+    catch(e) { this.error(e); }
+  },
+  assertNotOfType: function(expected, actual) {
+    var message = arguments[2] || 'assertNotOfType';
+    try { 
+      (actual.constructor != expected) ? this.pass() : 
+      this.fail(message + ': expected "' + Test.Unit.inspect(expected) +  
+        '", actual "' + (actual.constructor) + '"'); }
+    catch(e) { this.error(e); }
+  },
+  assertInstanceOf: function(expected, actual) {
+    var message = arguments[2] || 'assertInstanceOf';
+    try { 
+      (actual instanceof expected) ? this.pass() : 
+      this.fail(message + ": object was not an instance of the expected type"); }
+    catch(e) { this.error(e); } 
+  },
+  assertNotInstanceOf: function(expected, actual) {
+    var message = arguments[2] || 'assertNotInstanceOf';
+    try { 
+      !(actual instanceof expected) ? this.pass() : 
+      this.fail(message + ": object was an instance of the not expected type"); }
+    catch(e) { this.error(e); } 
+  },
+  assertRespondsTo: function(method, obj) {
+    var message = arguments[2] || 'assertRespondsTo';
+    try {
+      (obj[method] && typeof obj[method] == 'function') ? this.pass() : 
+      this.fail(message + ": object doesn't respond to [" + method + "]"); }
+    catch(e) { this.error(e); }
+  },
+  assertReturnsTrue: function(method, obj) {
+    var message = arguments[2] || 'assertReturnsTrue';
+    try {
+      var m = obj[method];
+      if(!m) m = obj['is'+method.charAt(0).toUpperCase()+method.slice(1)];
+      m() ? this.pass() : 
+      this.fail(message + ": method returned false"); }
+    catch(e) { this.error(e); }
+  },
+  assertReturnsFalse: function(method, obj) {
+    var message = arguments[2] || 'assertReturnsFalse';
+    try {
+      var m = obj[method];
+      if(!m) m = obj['is'+method.charAt(0).toUpperCase()+method.slice(1)];
+      !m() ? this.pass() : 
+      this.fail(message + ": method returned true"); }
+    catch(e) { this.error(e); }
+  },
+  assertRaise: function(exceptionName, method) {
+    var message = arguments[2] || 'assertRaise';
+    try { 
+      method();
+      this.fail(message + ": exception expected but none was raised"); }
+    catch(e) {
+      ((exceptionName == null) || (e.name==exceptionName)) ? this.pass() : this.error(e); 
+    }
+  },
+  assertElementsMatch: function() {
+    var expressions = $A(arguments), elements = $A(expressions.shift());
+    if (elements.length != expressions.length) {
+      this.fail('assertElementsMatch: size mismatch: ' + elements.length + ' elements, ' + expressions.length + ' expressions');
+      return false;
+    }
+    elements.zip(expressions).all(function(pair, index) {
+      var element = $(pair.first()), expression = pair.last();
+      if (element.match(expression)) return true;
+      this.fail('assertElementsMatch: (in index ' + index + ') expected ' + expression.inspect() + ' but got ' + element.inspect());
+    }.bind(this)) && this.pass();
+  },
+  assertElementMatches: function(element, expression) {
+    this.assertElementsMatch([element], expression);
+  },
+  benchmark: function(operation, iterations) {
+    var startAt = new Date();
+    (iterations || 1).times(operation);
+    var timeTaken = ((new Date())-startAt);
+    this.info((arguments[2] || 'Operation') + ' finished ' + 
+       iterations + ' iterations in ' + (timeTaken/1000)+'s' );
+    return timeTaken;
+  },
+  _isVisible: function(element) {
+    element = $(element);
+    if(!element.parentNode) return true;
+    this.assertNotNull(element);
+    if(element.style && Element.getStyle(element, 'display') == 'none')
+      return false;
+    
+    return this._isVisible(element.parentNode);
+  },
+  assertNotVisible: function(element) {
+    this.assert(!this._isVisible(element), Test.Unit.inspect(element) + " was not hidden and didn't have a hidden parent either. " + ("" || arguments[1]));
+  },
+  assertVisible: function(element) {
+    this.assert(this._isVisible(element), Test.Unit.inspect(element) + " was not visible. " + ("" || arguments[1]));
+  },
+  benchmark: function(operation, iterations) {
+    var startAt = new Date();
+    (iterations || 1).times(operation);
+    var timeTaken = ((new Date())-startAt);
+    this.info((arguments[2] || 'Operation') + ' finished ' + 
+       iterations + ' iterations in ' + (timeTaken/1000)+'s' );
+    return timeTaken;
+  }
+};
+
+Test.Unit.Testcase = Class.create();
+Object.extend(Object.extend(Test.Unit.Testcase.prototype, Test.Unit.Assertions.prototype), {
+  initialize: function(name, test, setup, teardown) {
+    Test.Unit.Assertions.prototype.initialize.bind(this)();
+    this.name           = name;
+    
+    if(typeof test == 'string') {
+      test = test.gsub(/(\.should[^\(]+\()/,'#{0}this,');
+      test = test.gsub(/(\.should[^\(]+)\(this,\)/,'#{1}(this)');
+      this.test = function() {
+        eval('with(this){'+test+'}');
+      }
+    } else {
+      this.test = test || function() {};
+    }
+    
+    this.setup          = setup || function() {};
+    this.teardown       = teardown || function() {};
+    this.isWaiting      = false;
+    this.timeToWait     = 1000;
+  },
+  wait: function(time, nextPart) {
+    this.isWaiting = true;
+    this.test = nextPart;
+    this.timeToWait = time;
+  },
+  run: function() {
+    try {
+      try {
+        if (!this.isWaiting) this.setup.bind(this)();
+        this.isWaiting = false;
+        this.test.bind(this)();
+      } finally {
+        if(!this.isWaiting) {
+          this.teardown.bind(this)();
+        }
+      }
+    }
+    catch(e) { this.error(e); }
+  }
+});
+
+// *EXPERIMENTAL* BDD-style testing to please non-technical folk
+// This draws many ideas from RSpec http://rspec.rubyforge.org/
+
+Test.setupBDDExtensionMethods = function(){
+  var METHODMAP = {
+    shouldEqual:     'assertEqual',
+    shouldNotEqual:  'assertNotEqual',
+    shouldEqualEnum: 'assertEnumEqual',
+    shouldBeA:       'assertType',
+    shouldNotBeA:    'assertNotOfType',
+    shouldBeAn:      'assertType',
+    shouldNotBeAn:   'assertNotOfType',
+    shouldBeNull:    'assertNull',
+    shouldNotBeNull: 'assertNotNull',
+    
+    shouldBe:        'assertReturnsTrue',
+    shouldNotBe:     'assertReturnsFalse',
+    shouldRespondTo: 'assertRespondsTo'
+  };
+  var makeAssertion = function(assertion, args, object) { 
+   	this[assertion].apply(this,(args || []).concat([object]));
+  };
+  
+  Test.BDDMethods = {};   
+  $H(METHODMAP).each(function(pair) { 
+    Test.BDDMethods[pair.key] = function() { 
+       var args = $A(arguments); 
+       var scope = args.shift(); 
+       makeAssertion.apply(scope, [pair.value, args, this]); }; 
+  });
+  
+  [Array.prototype, String.prototype, Number.prototype, Boolean.prototype].each(
+    function(p){ Object.extend(p, Test.BDDMethods) }
+  );
+};
+
+Test.context = function(name, spec, log){
+  Test.setupBDDExtensionMethods();
+  
+  var compiledSpec = {};
+  var titles = {};
+  for(specName in spec) {
+    switch(specName){
+      case "setup":
+      case "teardown":
+        compiledSpec[specName] = spec[specName];
+        break;
+      default:
+        var testName = 'test'+specName.gsub(/\s+/,'-').camelize();
+        var body = spec[specName].toString().split('\n').slice(1);
+        if(/^\{/.test(body[0])) body = body.slice(1);
+        body.pop();
+        body = body.map(function(statement){ 
+          return statement.strip()
+        });
+        compiledSpec[testName] = body.join('\n');
+        titles[testName] = specName;
+    }
+  }
+  new Test.Unit.Runner(compiledSpec, { titles: titles, testLog: log || 'testlog', context: name });
+};
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/02ed252a/tapestry-core/src/main/resources/META-INF/assets/tapestry5/spacer.gif
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/resources/META-INF/assets/tapestry5/spacer.gif b/tapestry-core/src/main/resources/META-INF/assets/tapestry5/spacer.gif
new file mode 100644
index 0000000..1ef86d5
Binary files /dev/null and b/tapestry-core/src/main/resources/META-INF/assets/tapestry5/spacer.gif differ

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/02ed252a/tapestry-core/src/main/resources/META-INF/assets/tapestry5/t5-alerts.css
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/resources/META-INF/assets/tapestry5/t5-alerts.css b/tapestry-core/src/main/resources/META-INF/assets/tapestry5/t5-alerts.css
new file mode 100644
index 0000000..762164f
--- /dev/null
+++ b/tapestry-core/src/main/resources/META-INF/assets/tapestry5/t5-alerts.css
@@ -0,0 +1,61 @@
+DIV.t-alert-container {
+
+}
+
+DIV.t-alert-controls {
+    display: inline-block;
+    cursor: pointer;
+    background: #e8e8e8 url("../../../org/apache/tapestry5/silk/delete.png") no-repeat;
+    border-radius: 5px;
+    -moz-border-radius: 5px;
+    -webkit-border-radius: 5px;
+    margin-bottom: 5px;
+    padding-left: 22px;
+    padding-right: 5px;
+}
+
+DIV.t-alert-control A:hover {
+    text-decoration: underline;
+}
+
+DIV.t-alert-container DIV.t-error {
+    background: red url("../../../org/apache/tapestry5/silk/cancel.png") no-repeat;
+    font-weight: bold;
+    color: white;
+    border-radius: 5px;
+    -moz-border-radius: 5px;
+    -webkit-border-radius: 5px;
+    margin-bottom: 5px;
+    padding-left: 22px;
+}
+
+DIV.t-alert-container DIV.t-warn {
+    background: #f4da5b url("../../../org/apache/tapestry5/silk/error.png") no-repeat;
+    font-weight: bold;
+    border-radius: 5px;
+    -moz-border-radius: 5px;
+    -webkit-border-radius: 5px;
+    margin-bottom: 5px;
+    padding-left: 22px;
+}
+
+DIV.t-alert-container DIV.t-info {
+    background: green url("../../../org/apache/tapestry5/silk/information.png") no-repeat;
+    border-radius: 5px;
+    -moz-border-radius: 5px;
+    -webkit-border-radius: 5px;
+    margin-bottom: 5px;
+    padding-left: 22px;
+    color: white;
+}
+
+DIV.t-dismiss {
+    float: right;
+    margin-left: 3px;
+    margin-bottom: 3px;
+    cursor: pointer;
+    width: 16px;
+    height: 16px;
+    background: url("../../../org/apache/tapestry5/silk/delete.png")
+}
+

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/02ed252a/tapestry-core/src/main/resources/META-INF/assets/tapestry5/t53-compatibility.js
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/resources/META-INF/assets/tapestry5/t53-compatibility.js b/tapestry-core/src/main/resources/META-INF/assets/tapestry5/t53-compatibility.js
new file mode 100644
index 0000000..f395864
--- /dev/null
+++ b/tapestry-core/src/main/resources/META-INF/assets/tapestry5/t53-compatibility.js
@@ -0,0 +1,49 @@
+// Copyright 2012 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http:#www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// ##core/t53-compatibility (Service Provider Interface)
+//
+// Provides a small amount of backwards compatibility to the Tapestry 5.3 approach.
+// This provides placeholders for the following:
+//
+// * `T5` namespace, including `extend`, `define`, and `initializers`, `extendInitializers`, and `_` properties
+// * `Tapestry` namespace: just the `Initializer` property, as an alias of `T5.initializers`
+define("core/t53-compatibility", ["_"], function(_) {
+    var T5, Tapestry;
+    T5 = {
+        _: _.noConflict(),
+        extend: function(destination, source) {
+            if (_.isFunction(source)) {
+                source = source();
+            }
+            return _.extend(destination, source);
+        },
+        define: function(name, source) {
+            var namespace;
+            namespace = {};
+            T5[name] = namespace;
+            return T5.extend(namespace, source);
+        },
+        initializers: {},
+        extendInitializers: function(source) {
+            return T5.extend(T5.initializers, source);
+        }
+    };
+    Tapestry = {
+        Initializer: T5.initializers
+    };
+    window.T5 = T5;
+    window.Tapestry = Tapestry;
+    return null;
+});

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/02ed252a/tapestry-core/src/main/resources/META-INF/assets/tapestry5/tapestry-console.css
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/resources/META-INF/assets/tapestry5/tapestry-console.css b/tapestry-core/src/main/resources/META-INF/assets/tapestry5/tapestry-console.css
new file mode 100644
index 0000000..eae678e
--- /dev/null
+++ b/tapestry-core/src/main/resources/META-INF/assets/tapestry5/tapestry-console.css
@@ -0,0 +1,42 @@
+/* The console is used to show debugging messages. */
+DIV.t-console {
+    position: fixed;
+    z-index: 1;
+    top: 2px;
+    left: 2px;
+}
+
+DIV.t-console-entry {
+    font-weight: bold;
+    padding: 0px 2px;
+    cursor: pointer;
+    border-radius: 5px;
+    -moz-border-radius: 5px;
+    -webkit-border-radius: 5px;
+    margin-bottom: 5px;
+    padding-left: 22px;
+}
+
+DIV.t-console DIV.t-err {
+    background: red url("../../../org/apache/tapestry5/silk/error.png") no-repeat;
+    color: white;
+    padding-left: 22px;
+}
+
+DIV.t-console DIV.t-warn {
+    background: #f4da5b url("../../../org/apache/tapestry5/silk/error.png") no-repeat;
+    color: black;
+    padding-left: 22px;
+}
+
+DIV.t-console DIV.t-info {
+    background: green url("../../../org/apache/tapestry5/silk/information.png") no-repeat;
+    color: white;
+    padding-left: 22px;
+}
+
+DIV.t-console DIV.t-debug {
+    background-color: silver;
+    color: black;
+    padding-left: 22px;
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/02ed252a/tapestry-core/src/main/resources/META-INF/assets/tapestry5/tapestry.js
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/resources/META-INF/assets/tapestry5/tapestry.js b/tapestry-core/src/main/resources/META-INF/assets/tapestry5/tapestry.js
new file mode 100644
index 0000000..01b654d
--- /dev/null
+++ b/tapestry-core/src/main/resources/META-INF/assets/tapestry5/tapestry.js
@@ -0,0 +1,954 @@
+/* Copyright 2007-2012 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// TEMPORARY: KEPT AROUND FOR REFERENCE, TO BE DELETED SHORTLY
+
+define("core/compat/tapestry", [
+    "_",
+    "core/spi",
+    "core/events",
+    "core/ajax",
+    "core/forms",
+    "core/compat/t5-dom",
+    "core/compat/t5-console",
+    "core/compat/t5-init"], function (_, spi, events, ajax, forms) {
+
+    window.Tapestry = {
+
+        Logging: T5.console,
+
+        /**
+         * Event that allows observers to perform cross-form validation after
+         * individual fields have performed their validation. The form element is
+         * passed as the event memo. Observers may set the validationError property
+         * of the Form's Tapestry object to true (which will prevent form
+         * submission).
+         */
+        FORM_VALIDATE_EVENT: events.form.validate,
+
+        /**
+         * Event fired just before the form submits, to allow observers to make
+         * final preparations for the submission, such as updating hidden form
+         * fields. The form element is passed as the event memo.
+         */
+        FORM_PREPARE_FOR_SUBMIT_EVENT: events.form.prepareForSubmit,
+
+        /**
+         * Form event fired after prepare.
+         */
+        FORM_PROCESS_SUBMIT_EVENT: events.form.processSubmit,
+
+        /**
+         * Event, fired on a field element, to cause observers to validate the
+         * input. Passed a memo object with two keys: "value" (the raw input value)
+         * and "translated" (the parsed value, usually meaning a number parsed from
+         * a string). Observers may invoke Element.showValidationMessage() to
+         * identify that the field is in error (and decorate the field and show a
+         * popup error message).
+         */
+        FIELD_VALIDATE_EVENT: "tapestry:field-validate",
+
+        /**
+         * Event notification, on a form object, that is used to trigger validation
+         * on all fields within the form (observed by each field's
+         * Tapestry.FieldEventManager).  Note: no longer used in 5.4.
+         */
+        FORM_VALIDATE_FIELDS_EVENT: "tapestry:form-validate-fields",
+
+        /**
+         * Event, fired on the document object, which identifies the current focus
+         * input element. Starting in Tapestry 5.4, focus changing is not longer tracked.
+         */
+        FOCUS_CHANGE_EVENT: "tapestry:focuschange",
+
+        /** Event, triggered on a zone element after the zone's content has been updated. */
+        ZONE_UPDATED_EVENT: events.zone.didUpdate,
+
+        /**
+         * Event fired on a form fragment element to change the visibility of the
+         * fragment. The event memo object includes a key, visible, that should be
+         * true or false.
+         */
+        CHANGE_VISIBILITY_EVENT: events.formfragment.changeVisibility,
+
+        /**
+         * Event fired on a form fragment element to hide the element and remove it
+         * from the DOM.
+         */
+        HIDE_AND_REMOVE_EVENT: events.formfragment.remove,
+
+        /**
+         * Event fired on a link or submit to request that it request that the
+         * correct ZoneManager update from a provided URL.
+         */
+        TRIGGER_ZONE_UPDATE_EVENT: "tapestry:triggerzoneupdate",
+
+        /** Event used when intercepting and canceling the normal click event. */
+        ACTION_EVENT: "tapestry:action",
+
+        /** When false, the default, the Tapestry.debug() function will be a no-op. */
+        DEBUG_ENABLED: false,
+
+        /** Time, in seconds, that console messages are visible. */
+        CONSOLE_DURATION: 10,
+
+        /**
+         * CSS Class added to a &lt;form&gt; element that directs Tapestry 5.3 to
+         * prevent normal (HTTP POST) form submission, in favor of Ajax
+         * (XmlHttpRequest) submission. It is ignored under 5.4.
+         *
+         */
+        PREVENT_SUBMISSION: "t-prevent-submission",
+
+        /** Initially, false, set to true once the page is fully loaded. */
+        pageLoaded: false,
+
+        /**
+         * Invoked from onclick event handlers built into links and forms. Raises a
+         * dialog if the page is not yet fully loaded. Gutted in 5.4, does nothing ...
+         * the page loads fast enough :-).
+         */
+        waitForPage: function () {
+        },
+
+        /**
+         * Adds a callback function that will be invoked when the DOM is loaded
+         * (which occurs *before* window.onload, which has to wait for images and
+         * such to load first. This simply observes the dom:loaded event on the
+         * document object (support for which is provided by Prototype).
+         */
+        onDOMLoaded: spi.domReady,
+
+        /**
+         * This is invoked when the DOM is first loaded.
+         */
+        onDomLoadedCallback: function () {
+
+            Tapestry.pageLoaded = true;
+
+        },
+
+        /*
+         * Generalized initialize function for Tapestry, used to help minimize the
+         * amount of JavaScript for the page by removing redundancies such as
+         * repeated Object and method names. The spec is a hash whose keys are the
+         * names of methods of the Tapestry.Initializer object. The value is an
+         * array of arrays. The outer arrays represent invocations of the method.
+         * The inner array are the parameters for each invocation. As an
+         * optimization, the inner value may not be an array but instead a single
+         * value.
+         */
+        // Probably not used anymore:
+        init: function (spec) {
+            $H(spec).each(function (pair) {
+                var functionName = pair.key;
+
+                var initf = Tapestry.Initializer[functionName];
+
+                if (initf == undefined) {
+                    Tapestry.error(Tapestry.Messages.missingInitializer, {
+                        name: functionName
+                    });
+                    return;
+                }
+
+                pair.value.each(function (parameterList) {
+                    if (!Object.isArray(parameterList)) {
+                        parameterList = [ parameterList ];
+                    }
+
+                    initf.apply(this, parameterList);
+                });
+            });
+        },
+
+        /**
+         * Processes a typical Ajax request for a URL. In the simple case, a success
+         * handler is provided (as options). In a more complex case, an options
+         * object is provided, with keys as per Ajax.Request. The onSuccess key will
+         * be overwritten, and defaults for onException and onFailure will be
+         * provided. The handler should take up-to two parameters: the
+         * XMLHttpRequest object itself, and the JSON Response (from the X-JSON
+         * response header, usually null).
+         *
+         * This has been re-implemented in 5.4 as a wrapper around the core/ajax module.
+         *
+         * @param url
+         *            of Ajax request
+         * @param options
+         *            either a success handler, or a set of options compatible with the core/ajax module.
+         * @return the Ajax.Request object (perhaps, if prototype is the underlying
+         */
+        ajaxRequest: function (url, options) {
+            if (_.isFunction(options)) {
+                options = { onsuccess: options };
+            }
+
+            // Prototype and Tapestry 5.3 uses "onSuccess", "onException", "onFailure", but
+            // core/ajax (Tapestry 5.4) uses "onsuccess", etc.
+
+            var newOptions = {};
+            _.each(options, function (value, key) {
+                var newKey = key.substr(0, 2) === "on" ? key.toLowerCase() : key;
+
+                newOptions[newKey] = value;
+            });
+
+            return ajax(url, newOptions);
+        },
+
+        /** Formats and displays an error message on the console. */
+        error: function (message, substitutions) {
+            Tapestry.invokeLogger(message, substitutions, Tapestry.Logging.error);
+        },
+
+        /** Formats and displays a warning on the console. */
+        warn: function (message, substitutions) {
+            Tapestry.invokeLogger(message, substitutions, Tapestry.Logging.warn);
+        },
+
+        /** Formats and displays an info message on the console. */
+        info: function (message, substitutions) {
+            Tapestry.invokeLogger(message, substitutions, Tapestry.Logging.info);
+        },
+
+        /**
+         * Formats and displays a debug message on the console. This function is a no-op unless Tapestry.DEBUG_ENABLED is true
+         * (which will be the case when the application is running in development mode).
+         */
+        debug: function (message, substitutions) {
+            if (Tapestry.DEBUG_ENABLED) {
+                Tapestry.invokeLogger(message, substitutions, Tapestry.Logging.debug);
+            }
+        },
+
+        invokeLogger: function (message, substitutions, loggingFunction) {
+            if (substitutions != undefined)
+                message = message.interpolate(substitutions);
+
+            loggingFunction.call(this, message);
+        },
+
+
+        /**
+         * Convert a user-provided localized number to an ordinary number (not a
+         * string). Removes seperators and leading/trailing whitespace. Disallows
+         * the decimal point if isInteger is true.
+         *
+         * @param number
+         *            string provided by user
+         * @param isInteger
+         *            if true, disallow decimal point
+         */
+        formatLocalizedNumber: function (number, isInteger) {
+            /*
+             * We convert from localized string to a canonical string, stripping out
+             * group seperators (normally commas). If isInteger is true, we don't
+             * allow a decimal point.
+             */
+
+            var minus = Tapestry.decimalFormatSymbols.minusSign;
+            var grouping = Tapestry.decimalFormatSymbols.groupingSeparator;
+            var decimal = Tapestry.decimalFormatSymbols.decimalSeparator;
+
+            var canonical = "";
+
+            number.strip().toArray().each(function (ch) {
+                if (ch == minus) {
+                    canonical += "-";
+                    return;
+                }
+
+                if (ch == grouping) {
+                    return;
+                }
+
+                if (ch == decimal) {
+                    if (isInteger)
+                        throw Tapestry.Messages.notAnInteger;
+
+                    ch = ".";
+                } else if (ch < "0" || ch > "9")
+                    throw Tapestry.Messages.invalidCharacter;
+
+                canonical += ch;
+            });
+
+            return Number(canonical);
+        },
+
+        /**
+         * Removes an element and all of its direct and indirect children. The
+         * element is first purged, to ensure that Internet Explorer doesn't leak
+         * memory if event handlers associated with the element (or its children)
+         * have references back to the element.
+         *
+         * @since 5.2.0
+         * @deprecated Since 5.3, use T5.dom.remove() instead
+         */
+        remove: T5.dom.remove,
+
+        /** @deprecated Since 5.3, use T5.dom.purgeChildren instead */
+        purgeChildren: T5.dom.purgeChildren
+    };
+
+    Element.addMethods({
+
+        /**
+         * Works upward from the element, checking to see if the element is visible.
+         * Returns false if it finds an invisible container. Returns true if it
+         * makes it as far as a (visible) FORM element.
+         *
+         * Note that this only applies to the CSS definition of visible; it doesn't
+         * check that the element is scrolled into view.
+         *
+         * @param element
+         *            to search up from
+         * @param options
+         *            Optional map of options. Only used key currently is "bound" which should be a javascript function name
+         *            that determines whether the current element bounds the search. The default is to stop the search when
+         *            the
+         * @return true if visible (and containers visible), false if it or
+         *         container are not visible
+         */
+        isDeepVisible: function (element, options) {
+            var current = $(element);
+            var boundFunc = (options && options.bound) || function (el) {
+                return el.tagName == "FORM"
+            };
+
+            while (true) {
+                if (!current.visible())
+                    return false;
+
+                if (boundFunc(current))
+                    break;
+
+                current = $(current.parentNode);
+            }
+
+            return true;
+        },
+
+        /**
+         * Observes an event and turns it into a Tapestry.ACTION_EVENT. The original
+         * event is stopped. The original event object is passed as the memo when
+         * the action event is fired. This allows the logic for clicking an element
+         * to be separated from the logic for processing that click event, which is
+         * often useful when the click logic needs to be intercepted, or when the
+         * action logic needs to be triggered outside the context of a DOM event.
+         *
+         * $T(element).hasAction will be true after invoking this method.
+         *
+         * @param element
+         *            to observe events from
+         * @param eventName
+         *            name of event to observer, typically "click"
+         * @param handler
+         *            function to be invoked; it will be registered as a observer of
+         *            the Tapestry.ACTION_EVENT.
+         */
+        observeAction: function (element, eventName, handler) {
+            element.observe(eventName, function (event) {
+
+                event.stop();
+
+                element.fire(Tapestry.ACTION_EVENT, event);
+            });
+
+            element.observe(Tapestry.ACTION_EVENT, handler);
+
+            $T(element).hasAction = true;
+        }
+    });
+
+    Element
+            .addMethods(
+            'FORM',
+            {
+                /**
+                 * Identifies in the form what is the cause of the
+                 * submission. The element's id is stored into the t:submit
+                 * hidden field (created as needed).
+                 *
+                 * @param form
+                 *            to update
+                 * @param element
+                 *            id or element that is the cause of the submit
+                 *            (a Submit or LinkSubmit)
+                 */
+                setSubmittingElement: function (form, element) {
+                    forms.setSubmittingElement(spi(form), spi(element));
+                },
+
+                /**
+                 * Turns off client validation for the next submission of
+                 * the form.
+                 */
+                skipValidation: function (form) {
+                    forms.skipValidation(spi(form));
+                },
+
+                /**
+                 * Programmatically perform a submit, invoking the onsubmit
+                 * event handler (if present) before calling form.submit().
+                 */
+                performSubmit: function (form, event) {
+                    if (form.onsubmit == undefined
+                            || form.onsubmit.call(window.document, event)) {
+                        form.submit();
+                    }
+                },
+
+                /**
+                 * Sends an Ajax request to the Form's action. This
+                 * encapsulates a few things, such as a default onFailure
+                 * handler, and working around bugs/features in Prototype
+                 * concerning how submit buttons are processed.
+                 *
+                 * @param form
+                 *            used to define the data to be sent in the
+                 *            request
+                 * @param options
+                 *            standard Prototype Ajax Options
+                 * @return Ajax.Request the Ajax.Request created for the
+                 *         request
+                 */
+                sendAjaxRequest: function (form, url, options) {
+                    form = $(form);
+
+                    /*
+                     * Generally, options should not be null or missing,
+                     * because otherwise there's no way to provide any
+                     * callbacks!
+                     */
+                    options = Object.clone(options || {});
+
+                    /*
+                     * Find the elements, skipping over any submit buttons.
+                     * This works around bugs in Prototype 1.6.0.2.
+                     */
+                    var elements = form.getElements().reject(function (e) {
+                        return e.tagName == "INPUT" && e.type == "submit";
+                    });
+
+                    var hash = Form.serializeElements(elements, true);
+
+                    /*
+                     * Copy the parameters in, overwriting field values,
+                     * because Prototype 1.6.0.2 does not.
+                     */
+                    Object.extend(hash, options.parameters);
+
+                    options.parameters = hash;
+
+                    /*
+                     * ajaxRequest will convert the hash into a query
+                     * string and post it.
+                     */
+
+                    return Tapestry.ajaxRequest(url, options);
+                }
+            });
+
+    Element.addMethods([ 'INPUT', 'SELECT', 'TEXTAREA' ], {
+        /**
+         * Invoked on a form element (INPUT, SELECT, etc.), gets or creates the
+         * Tapestry.FieldEventManager for that field.
+         *
+         * @param field
+         *            field element
+         */
+        getFieldEventManager: function (field) {
+            field = $(field);
+            var t = $T(field);
+
+            var manager = t.fieldEventManager;
+
+            if (manager == undefined) {
+                manager = new Tapestry.FieldEventManager(field);
+                t.fieldEventManager = manager;
+            }
+
+            return manager;
+        },
+
+        /**
+         * Obtains the Tapestry.FieldEventManager and asks it to show the validation
+         * message. Sets the validationError property of the elements tapestry
+         * object to true.
+         *
+         * @param element
+         * @param message
+         *            to display
+         */
+        showValidationMessage: function (element, message) {
+            element = $(element);
+
+            element.getFieldEventManager().showValidationMessage(message);
+
+            return element;
+        },
+
+        /**
+         * Removes any validation decorations on the field, and hides the error
+         * popup (if any) for the field.
+         */
+        removeDecorations: function (element) {
+            $(element).getFieldEventManager().removeDecorations();
+
+            return element;
+        },
+
+        /**
+         * Adds a standard validator for the element, an observer of
+         * Tapestry.FIELD_VALIDATE_EVENT. The validator function will be passed the
+         * current field value and should throw an error message if the field's
+         * value is not valid.
+         *
+         * @param element
+         *            field element to validate
+         * @param validator
+         *            function to be passed the field value
+         */
+        addValidator: function (element, validator) {
+
+            element.observe(Tapestry.FIELD_VALIDATE_EVENT, function (event) {
+                try {
+                    validator.call(this, event.memo.translated);
+                } catch (message) {
+                    element.showValidationMessage(message);
+                }
+            });
+
+            return element;
+        }
+    });
+
+    /** Compatibility: set Tapestry.Initializer equal to T5.initializers. */
+
+    Tapestry.Initializer = T5.initializers;
+
+    /** Container of functions that may be invoked by the Tapestry.init() function. */
+    T5.extendInitializers({
+
+        ajaxFormLoop: function (spec) {
+            var rowInjector = $(spec.rowInjector);
+
+            $(spec.addRowTriggers).each(function (triggerId) {
+                $(triggerId).observeAction("click", function (event) {
+                    $(rowInjector).trigger();
+                });
+            });
+        },
+
+        formLoopRemoveLink: function (spec) {
+            var link = $(spec.link);
+            var fragmentId = spec.fragment;
+
+            link.observeAction("click", function (event) {
+                var successHandler = function (transport) {
+                    var container = $(fragmentId);
+
+                    Tapestry.remove(container);
+                };
+
+                ajax(spec.url, { onsuccess: successHandler});
+            });
+        },
+
+
+        /**
+         * Keys in the masterSpec are ids of field control elements. Value
+         * is a list of validation specs. Each validation spec is a 2 or 3
+         * element array.
+         */
+        validate: function (masterSpec) {
+            $H(masterSpec)
+                    .each(
+                    function (pair) {
+
+                        var field = $(pair.key);
+
+                        /*
+                         * Force the creation of the field event
+                         * manager.
+                         */
+
+                        $(field).getFieldEventManager();
+
+                        $A(pair.value)
+                                .each(function (spec) {
+                                    /*
+                                     * Each pair value is an array of specs, each spec is a 2 or 3 element array. validator function name, message, optional constraint
+                                     */
+
+                                    var name = spec[0];
+                                    var message = spec[1];
+                                    var constraint = spec[2];
+
+                                    var vfunc = Tapestry.Validator[name];
+
+                                    if (vfunc == undefined) {
+                                        Tapestry
+                                                .error(Tapestry.Messages.missingValidator, {
+                                                    name: name,
+                                                    fieldName: field.id
+                                                });
+                                        return;
+                                    }
+
+                                    /*
+                                     * Pass the extended field, the provided message, and the constraint object to the Tapestry.Validator function, so that it can, typically, invoke field.addValidator().
+                                     */
+                                    vfunc.call(this, field, message, constraint);
+                                });
+                    });
+        },
+
+        formInjector: function (spec) {
+            new Tapestry.FormInjector(spec);
+        },
+
+
+        /**
+         * Invoked on a submit element to indicate that it forces form to submit as a cancel (bypassing client-side validation
+         * and most server-side processing).
+         * @param clientId of submit element
+         */
+        enableBypassValidation: function (clientId) {
+
+            /*
+             * Set the form's skipValidation property and allow the event to
+             * continue, which will ultimately submit the form.
+             */
+            $(clientId).observeAction("click", function (event) {
+                $(this.form).skipValidation();
+                $(this.form).setSubmittingElement($(clientId));
+                $(this.form).performSubmit(event);
+            });
+        }
+
+    });
+
+    /*
+     * Collection of field based functions related to validation. Each function
+     * takes a field, a message and an optional constraint value. Some functions are
+     * related to Translators and work on the format event, other's are from
+     * Validators and work on the validate event.
+     */
+
+    Tapestry.Validator = {
+
+        required: function (field, message) {
+            $(field).getFieldEventManager().requiredCheck = function (value) {
+                if ((_.isString(value) && value.strip() == '')
+                        || value == null)
+                    $(field).showValidationMessage(message);
+            };
+        },
+
+        /** Supplies a client-side numeric translator for the field. */
+        numericformat: function (field, message, isInteger) {
+            $(field).getFieldEventManager().translator = function (input) {
+                try {
+                    return Tapestry.formatLocalizedNumber(input, isInteger);
+                } catch (e) {
+                    $(field).showValidationMessage(message);
+                }
+            };
+        },
+
+        minlength: function (field, message, length) {
+            field.addValidator(function (value) {
+                if (value.length < length)
+                    throw message;
+            });
+        },
+
+        maxlength: function (field, message, maxlength) {
+            field.addValidator(function (value) {
+                if (value.length > maxlength)
+                    throw message;
+            });
+        },
+
+        min: function (field, message, minValue) {
+            field.addValidator(function (value) {
+                if (value < minValue)
+                    throw message;
+            });
+        },
+
+        max: function (field, message, maxValue) {
+            field.addValidator(function (value) {
+                if (value > maxValue)
+                    throw message;
+            });
+        },
+
+        regexp: function (field, message, pattern) {
+            var regexp = new RegExp(pattern);
+
+            field.addValidator(function (value) {
+                if (!regexp.test(value))
+                    throw message;
+            });
+        }
+    };
+
+    Tapestry.FieldEventManager = Class.create({
+
+        initialize: function (field) {
+
+            this.field = $(field);
+
+            this.translator = Prototype.K;
+
+            // This marker clues in the Form that validation should be triggered on this
+            // element.
+            this.field.writeAttribute("data-validation", true);
+
+            var _this = this;
+
+            $(this.field).observe(events.field.validate,
+                    function (event) {
+
+                        _this.validateInput();
+
+                        if (_this.inError()) {
+                            event.memo.error = true;
+                        }
+                    }
+            );
+        },
+
+        getLabel: function () {
+            if (!this.label) {
+                var selector = "label[for='" + this.field.id + "']";
+                this.label = this.field.form.down(selector);
+            }
+
+            return this.label;
+        },
+
+        getIcon: function () {
+            return null;
+        },
+
+        /**
+         * Removes validation messages, etc.
+         */
+        removeDecorations: function () {
+
+            this.field.removeClassName("t-error");
+            this.field.fire(events.field.clearValidationError);
+        },
+
+        /**
+         * Show a validation error message.
+         *
+         * @param message
+         *            validation message to display
+         */
+        showValidationMessage: function (message) {
+
+            this.field.addClassName("t-error");
+
+            this.field.fire(events.field.showValidationError, { message: message });
+        },
+
+        inError: function () {
+            return this.field.hasClassName("t-error");
+        },
+
+        /**
+         * Invoked when a form is submitted to perform
+         * field validations. Field validations are skipped for disabled fields or fields that are not visible.
+         * If any validation fails, an error popup is raised for the field, to display the
+         * validation error message.
+         *
+         */
+        validateInput: function () {
+            this.removeDecorations();
+
+            if (this.field.disabled)
+                return;
+
+            if (!this.field.isDeepVisible())
+                return;
+
+            var value = $F(this.field);
+
+            if (this.requiredCheck) {
+                this.requiredCheck.call(this, value);
+
+                if (this.inError()) { return; }
+            }
+
+            /*
+             * Don't try to validate blank values; if the field is required, that
+             * error is already noted and presented to the user.
+             */
+            if (!(_.isString(value) && value.blank())) {
+                var translated = this.translator(value);
+
+                /*
+                 * If Format went ok, perhaps do the other validations.
+                 */
+                if (!this.inError()) {
+                    this.field.fire(Tapestry.FIELD_VALIDATE_EVENT, {
+                        value: value,
+                        translated: translated
+                    });
+                }
+
+            }
+        }
+    });
+
+    function _show(element) {
+        return new spi(element).show();
+    }
+
+    function _hide(element) {
+        return new spi(element).hide();
+    }
+
+    function _none(element) {
+        return new spi(element);
+    }
+
+    /*
+     * Wrappers around Prototype and Scriptaculous effects. All the functions of
+     * this object should have all-lowercase names. The methods all return the
+     * Effect object they create.
+     *
+     * 5.4 notes: there are no longer any effects, and what's returned is
+     * an ElementWrapper (as defined in module "core/spi").
+     */
+    Tapestry.ElementEffect = {
+
+        /**
+         * Was: Fades the element in.
+         * Now: show the element.
+         */
+        show: _show,
+
+        /**
+         * Was: The classic yellow background fade.
+         * Now: Does nothing, returns the ElementWrapper.
+         */
+        highlight: _none,
+
+        /* Was: Scrolls the content down.
+         * Now: show the element.
+         * */
+        slidedown: _show,
+
+        /**
+         * Was: Slides the content back up (opposite of slidedown).
+         * Now: Hides the element.
+         */
+        slideup: _hide,
+
+        /**
+         * Was: Fades the content out (opposite of show).
+         * Now: Hides the element.
+         */
+        fade: _hide,
+
+        /**
+         * Was: Does nothing to the element, returns the element.
+         * Now: Does nothing, returns the ElementWrapper.
+         */
+        none: _none
+    };
+
+    Tapestry.FormInjector = Class.create({
+
+        initialize: function (spec) {
+            this.element = $(spec.element);
+            this.url = spec.url;
+            this.below = spec.below;
+
+            this.showFunc = Tapestry.ElementEffect[spec.show]
+                    || Tapestry.ElementEffect.highlight;
+
+            this.element.trigger = function () {
+
+                var successHandler = function (transport) {
+
+                    var reply = transport.responseJSON;
+
+                    /*
+                     * Clone the FormInjector element (usually a div) to create the
+                     * new element, that gets inserted before or after the
+                     * FormInjector's element.
+                     */
+                    var newElement = new Element(this.element.tagName, {
+                        'class': this.element.className
+                    });
+
+                    /* Insert the new element before or after the existing element. */
+
+                    var param = {};
+                    param[this.below ? "after" : "before"] = newElement;
+
+                    this.element.insert(param);
+
+                    /*
+                     * Update the empty element with the content from the server
+                     */
+
+                    newElement.update(reply.content);
+
+                    newElement.id = reply.elementId;
+
+                    /*
+                     * Add some animation to reveal it all.
+                     */
+
+                    this.showFunc(newElement);
+                }.bind(this);
+
+                ajax(this.url, { onsuccess: successHandler });
+
+                return false;
+
+            }.bind(this);
+        }
+    });
+
+    /**
+     * In the spirit of $(), $T() exists to access a hash of extra data about an
+     * element. In release 5.1 and prior, a hash attached to the element by Tapestry
+     * was returned. In 5.2, Prototype's storage object is returned, which is less
+     * likely to cause memory leaks in IE.
+     *
+     * @deprecated With no specific replacement. To be removed after Tapestry 5.2.
+     * @param element
+     *            an element instance or element id
+     * @return object Prototype storage object for the element
+     */
+    window.$T = function (element) {
+        return $(element).getStorage();
+    }
+
+    spi.domReady(Tapestry.onDomLoadedCallback);
+    spi.on(window, "beforeunload", function () { Tapestry.windowUnloaded = true; });
+
+    return Tapestry;
+});

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/02ed252a/tapestry-core/src/main/resources/META-INF/assets/tapestry5/tree-branch.png
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/resources/META-INF/assets/tapestry5/tree-branch.png b/tapestry-core/src/main/resources/META-INF/assets/tapestry5/tree-branch.png
new file mode 100644
index 0000000..0dd92ca
Binary files /dev/null and b/tapestry-core/src/main/resources/META-INF/assets/tapestry5/tree-branch.png differ

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/02ed252a/tapestry-core/src/main/resources/META-INF/assets/tapestry5/tree-branchend.png
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/resources/META-INF/assets/tapestry5/tree-branchend.png b/tapestry-core/src/main/resources/META-INF/assets/tapestry5/tree-branchend.png
new file mode 100644
index 0000000..baa616c
Binary files /dev/null and b/tapestry-core/src/main/resources/META-INF/assets/tapestry5/tree-branchend.png differ

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/02ed252a/tapestry-core/src/main/resources/META-INF/assets/tapestry5/tree-sprites.png
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/resources/META-INF/assets/tapestry5/tree-sprites.png b/tapestry-core/src/main/resources/META-INF/assets/tapestry5/tree-sprites.png
new file mode 100644
index 0000000..e95f4b1
Binary files /dev/null and b/tapestry-core/src/main/resources/META-INF/assets/tapestry5/tree-sprites.png differ

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/02ed252a/tapestry-core/src/main/resources/META-INF/assets/tapestry5/tree-vpipe.png
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/resources/META-INF/assets/tapestry5/tree-vpipe.png b/tapestry-core/src/main/resources/META-INF/assets/tapestry5/tree-vpipe.png
new file mode 100644
index 0000000..baaa448
Binary files /dev/null and b/tapestry-core/src/main/resources/META-INF/assets/tapestry5/tree-vpipe.png differ

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/02ed252a/tapestry-core/src/main/resources/META-INF/assets/tapestry5/tree.css
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/resources/META-INF/assets/tapestry5/tree.css b/tapestry-core/src/main/resources/META-INF/assets/tapestry5/tree.css
new file mode 100644
index 0000000..b00a81b
--- /dev/null
+++ b/tapestry-core/src/main/resources/META-INF/assets/tapestry5/tree.css
@@ -0,0 +1,92 @@
+
+DIV.t-tree-container
+{
+  padding: 0;
+  margin: 0;
+}
+
+DIV.t-tree-container UL
+{
+  list-style-type: none;
+  background-image: url(tree-vpipe.png);
+  background-repeat: repeat-y;
+  margin: 0 0 0 12px;
+  padding: 0; 
+}
+
+DIV.t-tree-container UL UL
+{
+  /* Line up the nested list's vertical bar under the element's folder icon. */
+  margin: 0 0 0 24px;
+}
+
+
+DIV.t-tree-container LI {
+  margin: 0;
+  padding: 0 0 0 16px;
+  background-image: url(tree-branch.png);
+  background-repeat: no-repeat;
+  line-height: 1.5;
+}
+
+DIV.t-tree-container LI.t-last
+{
+    background-color: white;
+    background-image: url(tree-branchend.png);    
+}
+
+/* Assume its a collapsed, but expandable, tree node. Later CSS rules overwrite this. */
+
+SPAN.t-tree-icon {
+  display: inline-block;
+  width: 32px;
+  height: 16px;
+  cursor: pointer;
+  background-image: url(tree-sprites.png);
+  background-position: 0px 0px;
+}
+
+SPAN.t-tree-icon.t-leaf-node {
+  cursor: default;
+  background-position: -32px -16px;
+}
+
+SPAN.t-tree-label.t-selectable {
+    cursor: pointer;
+}
+
+SPAN.t-tree-label.t-selected-leaf-node-label {
+  font-weight: bold;
+}
+
+SPAN.t-tree-icon.t-empty-node {
+  cursor: default;
+  background-position: -32px 0px !important;
+}
+
+SPAN.t-tree-expanded {
+  background-position: 0px -16px;
+}
+
+SPAN.t-ajax-wait {
+  width: 16px;
+  height: 16px;
+  display: inline-block;
+  background-image: url(ajax-loader.gif);
+}
+
+SPAN.t-tree-icon.t-expand {
+  width: 16px;
+  height: 16px;
+  background-image: url(expand.png);
+  display: inline-block;
+  cursor: pointer;
+}
+
+SPAN.t-tree-icon.t-collapse {
+  width: 16px;
+  height: 16px;
+  background-image: url(collapse.png);
+  display: inline-block;
+  cursor: pointer;
+}