You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@struts.apache.org by mu...@apache.org on 2007/03/03 06:49:21 UTC
svn commit: r514083 [34/49] - in /struts/struts2/trunk/plugins/dojo: ./ src/
src/main/ src/main/java/ src/main/java/org/ src/main/java/org/apache/
src/main/java/org/apache/struts2/
src/main/java/org/apache/struts2/components/ src/main/java/org/apache/s...
Added: struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/InlineEditBox.js
URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/InlineEditBox.js?view=auto&rev=514083
==============================================================================
--- struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/InlineEditBox.js (added)
+++ struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/InlineEditBox.js Fri Mar 2 21:48:54 2007
@@ -0,0 +1,212 @@
+/*
+ Copyright (c) 2004-2006, The Dojo Foundation
+ All Rights Reserved.
+
+ Licensed under the Academic Free License version 2.1 or above OR the
+ modified BSD license. For more information on Dojo licensing, see:
+
+ http://dojotoolkit.org/community/licensing.shtml
+*/
+
+dojo.provide("dojo.widget.InlineEditBox");
+
+dojo.require("dojo.widget.*");
+dojo.require("dojo.event.*");
+dojo.require("dojo.lfx.*");
+dojo.require("dojo.gfx.color");
+dojo.require("dojo.string");
+dojo.require("dojo.html.*");
+dojo.require("dojo.html.layout");
+
+dojo.widget.defineWidget(
+ "dojo.widget.InlineEditBox",
+ dojo.widget.HtmlWidget,
+ function(){
+ // mutable objects need to be in constructor to give each instance its own copy
+ this.history = [];
+ },
+{
+ templatePath: dojo.uri.dojoUri("src/widget/templates/InlineEditBox.html"),
+ templateCssPath: dojo.uri.dojoUri("src/widget/templates/InlineEditBox.css"),
+
+ form: null,
+ editBox: null,
+ edit: null,
+ text: null,
+ textarea: null,
+ submitButton: null,
+ cancelButton: null,
+ mode: "text",
+ name: "",
+
+ minWidth: 100, //px. minimum width of edit box
+ minHeight: 200, //px. minimum width of edit box, if it's a TA
+
+ editing: false,
+ textValue: "",
+ defaultText: "",
+ doFade: false,
+
+ onSave: function(newValue, oldValue, name){},
+ onUndo: function(value){},
+
+ postCreate: function(args, frag){
+ // put original node back in the document, and attach handlers
+ // which hide it and display the editor
+ this.editable = this.getFragNodeRef(frag);
+ dojo.html.insertAfter(this.editable, this.form);
+ dojo.event.connect(this.editable, "onmouseover", this, "onMouseOver");
+ dojo.event.connect(this.editable, "onmouseout", this, "onMouseOut");
+ dojo.event.connect(this.editable, "onclick", this, "beginEdit");
+
+ this.textValue = dojo.string.trim(this.editable.innerHTML);
+ if(dojo.string.trim(this.textValue).length == 0){
+ this.editable.innerHTML = this.defaultText;
+ }
+ },
+
+ onMouseOver: function(){
+ if(!this.editing){
+ if (!this.isEnabled){
+ dojo.html.addClass(this.editable, "editableRegionDisabled");
+ } else {
+ dojo.html.addClass(this.editable, "editableRegion");
+ if(this.mode == "textarea"){
+ dojo.html.addClass(this.editable, "editableTextareaRegion");
+ }
+ }
+ }
+
+ this.mouseover();
+ },
+
+ mouseover: function(e){
+ // TODO: How do we deprecate a function without going into overkill with debug statements?
+ // dojo.deprecated("onMouseOver should be used instead of mouseover to listen for mouse events");
+ },
+
+ onMouseOut: function(){
+ if(!this.editing){
+ dojo.html.removeClass(this.editable, "editableRegion");
+ dojo.html.removeClass(this.editable, "editableTextareaRegion");
+ dojo.html.removeClass(this.editable, "editableRegionDisabled");
+ }
+
+ this.mouseout();
+ },
+
+ mouseout: function(e){
+ // dojo.deprecated("onMouseOut should be used instead of mouseout to listen for mouse events");
+ },
+
+ // When user clicks the text, then start editing.
+ // Hide the text and display the form instead.
+ beginEdit: function(e){
+ if(this.editing || !this.isEnabled){ return; }
+ this.onMouseOut();
+ this.editing = true;
+
+ // setup the form's <input> or <textarea> field, as specified by mode
+ var ee = this[this.mode.toLowerCase()];
+ ee.value = dojo.string.trim(this.textValue);
+ ee.style.fontSize = dojo.html.getStyle(this.editable, "font-size");
+ ee.style.fontWeight = dojo.html.getStyle(this.editable, "font-weight");
+ ee.style.fontStyle = dojo.html.getStyle(this.editable, "font-style");
+ var bb = dojo.html.getBorderBox(this.editable);
+ ee.style.width = Math.max(bb.width, this.minWidth) + "px";
+ if(this.mode.toLowerCase()=="textarea"){
+ ee.style.display = "block";
+ ee.style.height = Math.max(bb.height, this.minHeight) + "px";
+ } else {
+ ee.style.display = "";
+ }
+
+ // show the edit form and hide the read only version of the text
+ this.form.style.display = "";
+ this.editable.style.display = "none";
+
+ ee.focus();
+ ee.select();
+ this.submitButton.disabled = true;
+ },
+
+ saveEdit: function(e){
+ e.preventDefault();
+ e.stopPropagation();
+ var ee = this[this.mode.toLowerCase()];
+ if((this.textValue != ee.value)&&
+ (dojo.string.trim(ee.value) != "")){
+ this.doFade = true;
+ this.history.push(this.textValue);
+ this.onSave(ee.value, this.textValue, this.name);
+ this.textValue = ee.value;
+ this.editable.innerHTML = "";
+ var textNode = document.createTextNode( this.textValue );
+ this.editable.appendChild( textNode );
+ }else{
+ this.doFade = false;
+ }
+ this.finishEdit(e);
+ },
+
+ cancelEdit: function(e){
+ if(!this.editing){ return false; }
+ this.editing = false;
+ this.form.style.display="none";
+ this.editable.style.display = "";
+ return true;
+ },
+
+ finishEdit: function(e){
+ if(!this.cancelEdit(e)){ return; }
+ if(this.doFade) {
+ dojo.lfx.highlight(this.editable, dojo.gfx.color.hex2rgb("#ffc"), 700).play(300);
+ }
+ this.doFade = false;
+ },
+
+ setText: function(txt){
+ // sets the text without informing the server
+ txt = "" + txt;
+ var tt = dojo.string.trim(txt);
+ this.textValue = tt
+ this.editable.innerHTML = tt;
+ },
+
+ undo: function(){
+ if(this.history.length > 0){
+ var curValue = this.textValue;
+ var value = this.history.pop();
+ this.editable.innerHTML = value;
+ this.textValue = value;
+ this.onUndo(value);
+ this.onSave(value, curValue, this.name);
+ }
+ },
+
+ checkForValueChange: function(){
+ var ee = this[this.mode.toLowerCase()];
+ if((this.textValue != ee.value)&&
+ (dojo.string.trim(ee.value) != "")){
+ this.submitButton.disabled = false;
+ }
+ },
+
+ disable: function(){
+ this.submitButton.disabled = true;
+ this.cancelButton.disabled = true;
+ var ee = this[this.mode.toLowerCase()];
+ ee.disabled = true;
+
+ dojo.widget.Widget.prototype.disable.call(this);
+ },
+
+ enable: function(){
+ this.checkForValueChange();
+ this.cancelButton.disabled = false;
+ var ee = this[this.mode.toLowerCase()];
+ ee.disabled = false;
+
+ dojo.widget.Widget.prototype.enable.call(this);
+ }
+});
Added: struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/IntegerTextbox.js
URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/IntegerTextbox.js?view=auto&rev=514083
==============================================================================
--- struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/IntegerTextbox.js (added)
+++ struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/IntegerTextbox.js Fri Mar 2 21:48:54 2007
@@ -0,0 +1,66 @@
+/*
+ Copyright (c) 2004-2006, The Dojo Foundation
+ All Rights Reserved.
+
+ Licensed under the Academic Free License version 2.1 or above OR the
+ modified BSD license. For more information on Dojo licensing, see:
+
+ http://dojotoolkit.org/community/licensing.shtml
+*/
+
+dojo.provide("dojo.widget.IntegerTextbox");
+
+dojo.require("dojo.widget.ValidationTextbox");
+dojo.require("dojo.validate.common");
+
+/*
+ ****** IntegerTextbox ******
+
+ A subclass of ValidationTextbox.
+ Over-rides isValid/isInRange to test for integer input.
+ Has 4 new properties that can be specified as attributes in the markup.
+
+ @attr signed The leading plus-or-minus sign. Can be true or false, default is either.
+ @attr separator The character used as the thousands separator. Default is no separator.
+ @attr min Minimum signed value. Default is -Infinity
+ @attr max Maximum signed value. Default is +Infinity
+*/
+dojo.widget.defineWidget(
+ "dojo.widget.IntegerTextbox",
+ dojo.widget.ValidationTextbox,
+ {
+ mixInProperties: function(localProperties, frag){
+ // First initialize properties in super-class.
+ dojo.widget.IntegerTextbox.superclass.mixInProperties.apply(this, arguments);
+
+ // Get properties from markup attributes, and assign to flags object.
+ if((localProperties.signed == "true")||
+ (localProperties.signed == "always")){
+ this.flags.signed = true;
+ }else if((localProperties.signed == "false")||
+ (localProperties.signed == "never")){
+ this.flags.signed = false;
+ this.flags.min = 0;
+ }else{
+ this.flags.signed = [ true, false ]; // optional
+ }
+ if(localProperties.separator){
+ this.flags.separator = localProperties.separator;
+ }
+ if(localProperties.min){
+ this.flags.min = parseInt(localProperties.min);
+ }
+ if(localProperties.max){
+ this.flags.max = parseInt(localProperties.max);
+ }
+ },
+
+ // Over-ride for integer validation
+ isValid: function(){
+ return dojo.validate.isInteger(this.textbox.value, this.flags);
+ },
+ isInRange: function(){
+ return dojo.validate.isInRange(this.textbox.value, this.flags);
+ }
+ }
+);
Added: struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/InternetTextbox.js
URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/InternetTextbox.js?view=auto&rev=514083
==============================================================================
--- struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/InternetTextbox.js (added)
+++ struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/InternetTextbox.js Fri Mar 2 21:48:54 2007
@@ -0,0 +1,168 @@
+/*
+ Copyright (c) 2004-2006, The Dojo Foundation
+ All Rights Reserved.
+
+ Licensed under the Academic Free License version 2.1 or above OR the
+ modified BSD license. For more information on Dojo licensing, see:
+
+ http://dojotoolkit.org/community/licensing.shtml
+*/
+
+dojo.provide("dojo.widget.InternetTextbox");
+
+dojo.require("dojo.widget.ValidationTextbox");
+dojo.require("dojo.validate.web");
+
+dojo.widget.defineWidget(
+ "dojo.widget.IpAddressTextbox",
+ dojo.widget.ValidationTextbox,
+ {
+ // summary: A Textbox which tests for a valid IP address
+ // description: Can specify formats for ipv4 or ipv6 as attributes in the markup.
+ //
+ // allowDottedDecimal true or false, default is true.
+ // allowDottedHex true or false, default is true.
+ // allowDottedOctal true or false, default is true.
+ // allowDecimal true or false, default is true.
+ // allowHex true or false, default is true.
+ // allowIPv6 true or false, default is true.
+ // allowHybrid true or false, default is true.
+
+ mixInProperties: function(/*Object*/localProperties){
+ // summary: see dojo.widget.Widget
+
+ // First initialize properties in super-class.
+ dojo.widget.IpAddressTextbox.superclass.mixInProperties.apply(this, arguments);
+
+ // Get properties from markup attributes, and assign to flags object.
+ if(localProperties.allowdotteddecimal){
+ this.flags.allowDottedDecimal = (localProperties.allowdotteddecimal == "true");
+ }
+ if(localProperties.allowdottedhex){
+ this.flags.allowDottedHex = (localProperties.allowdottedhex == "true");
+ }
+ if(localProperties.allowdottedoctal){
+ this.flags.allowDottedOctal = (localProperties.allowdottedoctal == "true");
+ }
+ if(localProperties.allowdecimal){
+ this.flags.allowDecimal = (localProperties.allowdecimal == "true");
+ }
+ if(localProperties.allowhex){
+ this.flags.allowHex = (localProperties.allowhex == "true");
+ }
+ if(localProperties.allowipv6){
+ this.flags.allowIPv6 = (localProperties.allowipv6 == "true");
+ }
+ if(localProperties.allowhybrid){
+ this.flags.allowHybrid = (localProperties.allowhybrid == "true");
+ }
+ },
+
+ isValid: function(){
+ // summary: see dojo.widget.ValidationTextbox
+ return dojo.validate.isIpAddress(this.textbox.value, this.flags);
+ }
+ }
+);
+
+dojo.widget.defineWidget(
+ "dojo.widget.UrlTextbox",
+ dojo.widget.IpAddressTextbox,
+ {
+ // summary: A Textbox which tests for a valid URL
+ // scheme Can be true or false. If omitted the scheme is optional.
+ // allowIP Allow an IP address for hostname. Default is true.
+ // allowLocal Allow the host to be "localhost". Default is false.
+ // allowCC Allow 2 letter country code domains. Default is true.
+ // allowGeneric Allow generic domains. Can be true or false, default is true.
+
+ mixInProperties: function(/*Object*/localProperties){
+ // summary: see dojo.widget.Widget
+
+ // First initialize properties in super-class.
+ dojo.widget.UrlTextbox.superclass.mixInProperties.apply(this, arguments);
+
+ // Get properties from markup attributes, and assign to flags object.
+ if ( localProperties.scheme ) {
+ this.flags.scheme = ( localProperties.scheme == "true" );
+ }
+ if ( localProperties.allowip ) {
+ this.flags.allowIP = ( localProperties.allowip == "true" );
+ }
+ if ( localProperties.allowlocal ) {
+ this.flags.allowLocal = ( localProperties.allowlocal == "true" );
+ }
+ if ( localProperties.allowcc ) {
+ this.flags.allowCC = ( localProperties.allowcc == "true" );
+ }
+ if ( localProperties.allowgeneric ) {
+ this.flags.allowGeneric = ( localProperties.allowgeneric == "true" );
+ }
+ },
+
+ isValid: function(){
+ // summary: see dojo.widget.ValidationTextbox
+ return dojo.validate.isUrl(this.textbox.value, this.flags);
+ }
+ }
+);
+
+//FIXME: DOC: need more consistent explanation on whether attributes are inherited from the parent. Some make sense, some don't?
+
+dojo.widget.defineWidget(
+ "dojo.widget.EmailTextbox",
+ dojo.widget.UrlTextbox,
+ {
+ // summary: A Textbox which tests for a valid email address
+ // description:
+ // Can use all markup attributes/properties of UrlTextbox except scheme.
+ //
+ // allowCruft: Allow address like <ma...@yahoo.com>. Default is false.
+
+ mixInProperties: function(/*Object*/localProperties){
+ // summary: see dojo.widget.Widget
+
+ // First initialize properties in super-class.
+ dojo.widget.EmailTextbox.superclass.mixInProperties.apply(this, arguments);
+
+ // Get properties from markup attributes, and assign to flags object.
+ if(localProperties.allowcruft){
+ this.flags.allowCruft = (localProperties.allowcruft == "true");
+ }
+ },
+
+ isValid: function(){
+ // summary: see dojo.widget.ValidationTextbox
+ return dojo.validate.isEmailAddress(this.textbox.value, this.flags);
+ }
+ }
+);
+
+//TODO: perhaps combine with EmailTextbox?
+dojo.widget.defineWidget(
+ "dojo.widget.EmailListTextbox",
+ dojo.widget.EmailTextbox,
+ {
+ // summary: A Textbox which tests for a list of valid email addresses
+ //
+ // listSeparator: The character used to separate email addresses.
+ // Default is ";", ",", "\n" or " "
+
+ mixInProperties: function(/*Object*/localProperties){
+ // summary: see dojo.widget.Widget
+
+ // First initialize properties in super-class.
+ dojo.widget.EmailListTextbox.superclass.mixInProperties.apply(this, arguments);
+
+ // Get properties from markup attributes, and assign to flags object.
+ if(localProperties.listseparator){
+ this.flags.listSeparator = localProperties.listseparator;
+ }
+ },
+
+ isValid: function(){
+ // summary: see dojo.widget.ValidationTextbox
+ return dojo.validate.isEmailAddressList(this.textbox.value, this.flags);
+ }
+ }
+);
Added: struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/LayoutContainer.js
URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/LayoutContainer.js?view=auto&rev=514083
==============================================================================
--- struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/LayoutContainer.js (added)
+++ struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/LayoutContainer.js Fri Mar 2 21:48:54 2007
@@ -0,0 +1,97 @@
+/*
+ Copyright (c) 2004-2006, The Dojo Foundation
+ All Rights Reserved.
+
+ Licensed under the Academic Free License version 2.1 or above OR the
+ modified BSD license. For more information on Dojo licensing, see:
+
+ http://dojotoolkit.org/community/licensing.shtml
+*/
+
+dojo.provide("dojo.widget.LayoutContainer");
+
+dojo.require("dojo.widget.*");
+dojo.require("dojo.widget.html.layout");
+
+// summary
+// Provides Delphi-style panel layout semantics.
+//
+// details
+// A LayoutContainer is a box with a specified size (like style="width: 500px; height: 500px;"),
+// that contains children widgets marked with "layoutAlign" of "left", "right", "bottom", "top", and "client".
+// It takes it's children marked as left/top/bottom/right, and lays them out along the edges of the box,
+// and then it takes the child marked "client" and puts it into the remaining space in the middle.
+//
+// Left/right positioning is similar to CSS's "float: left" and "float: right",
+// and top/bottom positioning would be similar to "float: top" and "float: bottom", if there were such
+// CSS.
+//
+// Note that there can only be one client element, but there can be multiple left, right, top,
+// or bottom elements.
+//
+// usage
+// <style>
+// html, body{ height: 100%; width: 100%; }
+// </style>
+// <div dojoType="LayoutContainer" style="width: 100%; height: 100%">
+// <div dojoType="ContentPane" layoutAlign="top">header text</div>
+// <div dojoType="ContentPane" layoutAlign="left" style="width: 200px;">table of contents</div>
+// <div dojoType="ContentPane" layoutAlign="client">client area</div>
+// </div>
+dojo.widget.defineWidget(
+ "dojo.widget.LayoutContainer",
+ dojo.widget.HtmlWidget,
+{
+ isContainer: true,
+
+ // String
+ // - If the value is "top-bottom", then LayoutContainer will first position the "top" and "bottom" aligned elements,
+ // to and then put the left and right aligned elements in the remaining space, between the top and the bottom elements.
+ // It aligns the client element at the very end, in the remaining space.
+ //
+ // - If the value is "left-right", then it first positions the "left" and "right" elements, and then puts the
+ // "top" and "bottom" elements in the remaining space, between the left and the right elements.
+ // It aligns the client element at the very end, in the remaining space.
+ //
+ // - If the value is "none", then it will lay out each child in the natural order the children occur in.
+ // Basically each child is laid out into the "remaining space", where "remaining space" is initially
+ // the content area of this widget, but is reduced to a smaller rectangle each time a child is added.
+ //
+ layoutChildPriority: 'top-bottom',
+
+ postCreate: function(){
+ dojo.widget.html.layout(this.domNode, this.children, this.layoutChildPriority);
+ },
+
+ addChild: function(child, overrideContainerNode, pos, ref, insertIndex){
+ dojo.widget.LayoutContainer.superclass.addChild.call(this, child, overrideContainerNode, pos, ref, insertIndex);
+ dojo.widget.html.layout(this.domNode, this.children, this.layoutChildPriority);
+ },
+
+ removeChild: function(pane){
+ dojo.widget.LayoutContainer.superclass.removeChild.call(this,pane);
+ dojo.widget.html.layout(this.domNode, this.children, this.layoutChildPriority);
+ },
+
+ onResized: function(){
+ dojo.widget.html.layout(this.domNode, this.children, this.layoutChildPriority);
+ },
+
+ show: function(){
+ // If this node was created while display=="none" then it
+ // hasn't been laid out yet. Do that now.
+ this.domNode.style.display="";
+ this.checkSize();
+ this.domNode.style.display="none";
+ this.domNode.style.visibility="";
+
+ dojo.widget.LayoutContainer.superclass.show.call(this);
+ }
+});
+
+// This argument can be specified for the children of a LayoutContainer.
+// Since any widget can be specified as a LayoutContainer child, mix it
+// into the base widget class. (This is a hack, but it's effective.)
+dojo.lang.extend(dojo.widget.Widget, {
+ layoutAlign: 'none'
+});
Added: struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/LinkPane.js
URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/LinkPane.js?view=auto&rev=514083
==============================================================================
--- struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/LinkPane.js (added)
+++ struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/LinkPane.js Fri Mar 2 21:48:54 2007
@@ -0,0 +1,42 @@
+/*
+ Copyright (c) 2004-2006, The Dojo Foundation
+ All Rights Reserved.
+
+ Licensed under the Academic Free License version 2.1 or above OR the
+ modified BSD license. For more information on Dojo licensing, see:
+
+ http://dojotoolkit.org/community/licensing.shtml
+*/
+
+dojo.provide("dojo.widget.LinkPane");
+
+dojo.require("dojo.widget.*");
+dojo.require("dojo.widget.ContentPane");
+dojo.require("dojo.html.style");
+
+// summary
+// LinkPane is just a ContentPane that loads data remotely (via the href attribute),
+// and has markup similar to an anchor. The anchor's body (the words between <a> and </a>)
+// become the label of the widget (used for TabContainer, AccordionContainer, etc.)
+// usage
+// <a href="foo.html">my label</a>
+dojo.widget.defineWidget(
+ "dojo.widget.LinkPane",
+ dojo.widget.ContentPane,
+{
+ // I'm using a template because the user may specify the input as
+ // <a href="foo.html">label</a>, in which case we need to get rid of the
+ // <a> because we don't want a link.
+ templateString: '<div class="dojoLinkPane"></div>',
+
+ fillInTemplate: function(args, frag){
+ var source = this.getFragNodeRef(frag);
+
+ // If user has specified node contents, they become the label
+ // (the link must be plain text)
+ this.label += source.innerHTML;
+
+ var source = this.getFragNodeRef(frag);
+ dojo.html.copyStyle(this.domNode, source);
+ }
+});
Added: struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/Manager.js
URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/Manager.js?view=auto&rev=514083
==============================================================================
--- struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/Manager.js (added)
+++ struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/Manager.js Fri Mar 2 21:48:54 2007
@@ -0,0 +1,363 @@
+/*
+ Copyright (c) 2004-2006, The Dojo Foundation
+ All Rights Reserved.
+
+ Licensed under the Academic Free License version 2.1 or above OR the
+ modified BSD license. For more information on Dojo licensing, see:
+
+ http://dojotoolkit.org/community/licensing.shtml
+*/
+
+dojo.provide("dojo.widget.Manager");
+dojo.require("dojo.lang.array");
+dojo.require("dojo.lang.func");
+dojo.require("dojo.event.*");
+
+// Manager class
+dojo.widget.manager = new function(){
+ this.widgets = [];
+ this.widgetIds = [];
+
+ // map of widgetId-->widget for widgets without parents (top level widgets)
+ this.topWidgets = {};
+
+ var widgetTypeCtr = {};
+ var renderPrefixCache = [];
+
+ this.getUniqueId = function (widgetType) {
+ var widgetId;
+ do{
+ widgetId = widgetType + "_" + (widgetTypeCtr[widgetType] != undefined ?
+ ++widgetTypeCtr[widgetType] : widgetTypeCtr[widgetType] = 0);
+ }while(this.getWidgetById(widgetId));
+ return widgetId;
+ }
+
+ this.add = function(widget){
+ //dojo.profile.start("dojo.widget.manager.add");
+ this.widgets.push(widget);
+ // Opera9 uses ID (caps)
+ if(!widget.extraArgs["id"]){
+ widget.extraArgs["id"] = widget.extraArgs["ID"];
+ }
+ // FIXME: the rest of this method is very slow!
+ if(widget.widgetId == ""){
+ if(widget["id"]){
+ widget.widgetId = widget["id"];
+ }else if(widget.extraArgs["id"]){
+ widget.widgetId = widget.extraArgs["id"];
+ }else{
+ widget.widgetId = this.getUniqueId(widget.widgetType);
+ }
+ }
+ if(this.widgetIds[widget.widgetId]){
+ dojo.debug("widget ID collision on ID: "+widget.widgetId);
+ }
+ this.widgetIds[widget.widgetId] = widget;
+ // Widget.destroy already calls removeById(), so we don't need to
+ // connect() it here
+ //dojo.profile.end("dojo.widget.manager.add");
+ }
+
+ this.destroyAll = function(){
+ for(var x=this.widgets.length-1; x>=0; x--){
+ try{
+ // this.widgets[x].destroyChildren();
+ this.widgets[x].destroy(true);
+ delete this.widgets[x];
+ }catch(e){ }
+ }
+ }
+
+ // FIXME: we should never allow removal of the root widget until all others
+ // are removed!
+ this.remove = function(widgetIndex){
+ if(dojo.lang.isNumber(widgetIndex)){
+ var tw = this.widgets[widgetIndex].widgetId;
+ delete this.widgetIds[tw];
+ this.widgets.splice(widgetIndex, 1);
+ }else{
+ this.removeById(widgetIndex);
+ }
+ }
+
+ // FIXME: suboptimal performance
+ this.removeById = function(id) {
+ if(!dojo.lang.isString(id)){
+ id = id["widgetId"];
+ if(!id){ dojo.debug("invalid widget or id passed to removeById"); return; }
+ }
+ for (var i=0; i<this.widgets.length; i++){
+ if(this.widgets[i].widgetId == id){
+ this.remove(i);
+ break;
+ }
+ }
+ }
+
+ this.getWidgetById = function(id){
+ if(dojo.lang.isString(id)){
+ return this.widgetIds[id];
+ }
+ return id;
+ }
+
+ this.getWidgetsByType = function(type){
+ var lt = type.toLowerCase();
+ var getType = (type.indexOf(":") < 0 ?
+ function(x) { return x.widgetType.toLowerCase(); } :
+ function(x) { return x.getNamespacedType(); }
+ );
+ var ret = [];
+ dojo.lang.forEach(this.widgets, function(x){
+ if(getType(x) == lt){ret.push(x);}
+ });
+ return ret;
+ }
+
+ this.getWidgetsByFilter = function(unaryFunc, onlyOne){
+ var ret = [];
+ dojo.lang.every(this.widgets, function(x){
+ if(unaryFunc(x)){
+ ret.push(x);
+ if(onlyOne){return false;}
+ }
+ return true;
+ });
+ return (onlyOne ? ret[0] : ret);
+ }
+
+ this.getAllWidgets = function() {
+ return this.widgets.concat();
+ }
+
+ // added, trt 2006-01-20
+ this.getWidgetByNode = function(/* DOMNode */ node){
+ var w=this.getAllWidgets();
+ node = dojo.byId(node);
+ for(var i=0; i<w.length; i++){
+ if(w[i].domNode==node){
+ return w[i];
+ }
+ }
+ return null;
+ }
+
+ // shortcuts, baby
+ this.byId = this.getWidgetById;
+ this.byType = this.getWidgetsByType;
+ this.byFilter = this.getWidgetsByFilter;
+ this.byNode = this.getWidgetByNode;
+
+ // map of previousally discovered implementation names to constructors
+ var knownWidgetImplementations = {};
+
+ // support manually registered widget packages
+ var widgetPackages = ["dojo.widget"];
+ for (var i=0; i<widgetPackages.length; i++) {
+ // convenience for checking if a package exists (reverse lookup)
+ widgetPackages[widgetPackages[i]] = true;
+ }
+
+ this.registerWidgetPackage = function(pname) {
+ if(!widgetPackages[pname]){
+ widgetPackages[pname] = true;
+ widgetPackages.push(pname);
+ }
+ }
+
+ this.getWidgetPackageList = function() {
+ return dojo.lang.map(widgetPackages, function(elt) { return(elt!==true ? elt : undefined); });
+ }
+
+ this.getImplementation = function(widgetName, ctorObject, mixins, ns){
+ // try and find a name for the widget
+ var impl = this.getImplementationName(widgetName, ns);
+ if(impl){
+ // var tic = new Date();
+ var ret = ctorObject ? new impl(ctorObject) : new impl();
+ // dojo.debug(new Date() - tic);
+ return ret;
+ }
+ }
+
+ function buildPrefixCache() {
+ for(var renderer in dojo.render){
+ if(dojo.render[renderer]["capable"] === true){
+ var prefixes = dojo.render[renderer].prefixes;
+ for(var i=0; i<prefixes.length; i++){
+ renderPrefixCache.push(prefixes[i].toLowerCase());
+ }
+ }
+ }
+ // make sure we don't HAVE to prefix widget implementation names
+ // with anything to get them to render
+ //renderPrefixCache.push("");
+ // empty prefix is included automatically
+ }
+
+ var findImplementationInModule = function(lowerCaseWidgetName, module){
+ if(!module){return null;}
+ for(var i=0, l=renderPrefixCache.length, widgetModule; i<=l; i++){
+ widgetModule = (i<l ? module[renderPrefixCache[i]] : module);
+ if(!widgetModule){continue;}
+ for(var name in widgetModule){
+ if(name.toLowerCase() == lowerCaseWidgetName){
+ return widgetModule[name];
+ }
+ }
+ }
+ return null;
+ }
+
+ var findImplementation = function(lowerCaseWidgetName, moduleName){
+ // locate registered widget module
+ var module = dojo.evalObjPath(moduleName, false);
+ // locate a widget implementation in the registered module for our current rendering environment
+ return (module ? findImplementationInModule(lowerCaseWidgetName, module) : null);
+ }
+
+ this.getImplementationName = function(widgetName, ns){
+ /*
+ * Locate an implementation (constructor) for 'widgetName' in namespace 'ns'
+ * widgetNames are case INSENSITIVE
+ *
+ * 1. Return value from implementation cache, if available, for quick turnaround.
+ * 2. Locate a namespace registration for 'ns'
+ * 3. If no namespace found, register the conventional one (ns.widget)
+ * 4. Allow the namespace resolver (if any) to load a module for this widget.
+ * 5. Permute the widget name and capable rendering prefixes to locate, cache, and return
+ * an appropriate widget implementation.
+ * 6. If no implementation is found, attempt to load the namespace manifest,
+ * and then look again for an implementation to cache and return.
+ * 7. Use the deprecated widgetPackages registration system to attempt to locate the widget
+ * 8. Fail
+ */
+ var lowerCaseWidgetName = widgetName.toLowerCase();
+
+ // default to dojo namespace
+ ns=ns||"dojo";
+ // use cache if available
+ var imps = knownWidgetImplementations[ns] || (knownWidgetImplementations[ns]={});
+ //if(!knownWidgetImplementations[ns]){knownWidgetImplementations[ns]={};}
+ var impl = imps[lowerCaseWidgetName];
+ if(impl){
+ return impl;
+ }
+
+ // (one time) store a list of the render prefixes we are capable of rendering
+ if(!renderPrefixCache.length){
+ buildPrefixCache();
+ }
+
+ // lookup namespace
+ var nsObj = dojo.ns.get(ns);
+ if(!nsObj){
+ // default to <ns>.widget by convention
+ dojo.ns.register(ns, ns + '.widget');
+ nsObj = dojo.ns.get(ns);
+ }
+
+ // allow the namespace to resolve the widget module
+ if(nsObj){nsObj.resolve(widgetName);}
+
+ // locate a widget implementation in the registered module for our current rendering environment
+ impl = findImplementation(lowerCaseWidgetName, nsObj.module);
+ if(impl){return(imps[lowerCaseWidgetName] = impl)};
+
+ // try to load a manifest to resolve this implemenation
+ nsObj = dojo.ns.require(ns);
+ if((nsObj)&&(nsObj.resolver)){
+ nsObj.resolve(widgetName);
+ impl = findImplementation(lowerCaseWidgetName, nsObj.module);
+ if(impl){return(imps[lowerCaseWidgetName] = impl)};
+ }
+
+ // this is an error condition under new rules
+ dojo.deprecated('dojo.widget.Manager.getImplementationName',
+ 'Could not locate widget implementation for "' + widgetName + '" in "' + nsObj.module + '" registered to namespace "' + nsObj.name + '". '
+ + "Developers must specify correct namespaces for all non-Dojo widgets", "0.5");
+
+ // backward compat: if the user has not specified any namespace and their widget is not in dojo.widget.*
+ // search registered widget packages [sic]
+ // note: registerWidgetPackage itself is now deprecated
+ for(var i=0; i<widgetPackages.length; i++){
+ impl = findImplementation(lowerCaseWidgetName, widgetPackages[i]);
+ if(impl){return(imps[lowerCaseWidgetName] = impl)};
+ }
+
+ throw new Error('Could not locate widget implementation for "' + widgetName + '" in "' + nsObj.module + '" registered to namespace "' + nsObj.name + '"');
+ }
+
+ // FIXME: does it even belong in this module?
+ // NOTE: this method is implemented by DomWidget.js since not all
+ // hostenv's would have an implementation.
+ /*this.getWidgetFromPrimitive = function(baseRenderType){
+ dojo.unimplemented("dojo.widget.manager.getWidgetFromPrimitive");
+ }
+
+ this.getWidgetFromEvent = function(nativeEvt){
+ dojo.unimplemented("dojo.widget.manager.getWidgetFromEvent");
+ }*/
+
+ // Catch window resize events and notify top level widgets
+ this.resizing=false;
+ this.onWindowResized = function(){
+ if(this.resizing){
+ return; // duplicate event
+ }
+ try{
+ this.resizing=true;
+ for(var id in this.topWidgets){
+ var child = this.topWidgets[id];
+ if(child.checkSize ){
+ child.checkSize();
+ }
+ }
+ }catch(e){
+ }finally{
+ this.resizing=false;
+ }
+ }
+ if(typeof window != "undefined") {
+ dojo.addOnLoad(this, 'onWindowResized'); // initial sizing
+ dojo.event.connect(window, 'onresize', this, 'onWindowResized'); // window resize
+ }
+
+ // FIXME: what else?
+};
+
+(function(){
+ var dw = dojo.widget;
+ var dwm = dw.manager;
+ var h = dojo.lang.curry(dojo.lang, "hitch", dwm);
+ var g = function(oldName, newName){
+ dw[(newName||oldName)] = h(oldName);
+ }
+ // copy the methods from the default manager (this) to the widget namespace
+ g("add", "addWidget");
+ g("destroyAll", "destroyAllWidgets");
+ g("remove", "removeWidget");
+ g("removeById", "removeWidgetById");
+ g("getWidgetById");
+ g("getWidgetById", "byId");
+ g("getWidgetsByType");
+ g("getWidgetsByFilter");
+ g("getWidgetsByType", "byType");
+ g("getWidgetsByFilter", "byFilter");
+ g("getWidgetByNode", "byNode");
+ dw.all = function(n){
+ var widgets = dwm.getAllWidgets.apply(dwm, arguments);
+ if(arguments.length > 0) {
+ return widgets[n];
+ }
+ return widgets;
+ }
+ g("registerWidgetPackage");
+ g("getImplementation", "getWidgetImplementation");
+ g("getImplementationName", "getWidgetImplementationName");
+
+ dw.widgets = dwm.widgets;
+ dw.widgetIds = dwm.widgetIds;
+ dw.root = dwm.root;
+})();
Added: struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/Menu2.js
URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/Menu2.js?view=auto&rev=514083
==============================================================================
--- struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/Menu2.js (added)
+++ struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/Menu2.js Fri Mar 2 21:48:54 2007
@@ -0,0 +1,765 @@
+/*
+ Copyright (c) 2004-2006, The Dojo Foundation
+ All Rights Reserved.
+
+ Licensed under the Academic Free License version 2.1 or above OR the
+ modified BSD license. For more information on Dojo licensing, see:
+
+ http://dojotoolkit.org/community/licensing.shtml
+*/
+
+dojo.provide("dojo.widget.Menu2");
+
+dojo.require("dojo.widget.PopupContainer");
+
+// summary
+// provides a menu that can be used as a context menu (typically shown by right-click),
+// or as the drop down on a DropDownButton, ComboButton, etc.
+dojo.widget.defineWidget(
+ "dojo.widget.PopupMenu2",
+ dojo.widget.PopupContainer,
+ function(){
+ this.targetNodeIds = []; // fill this with nodeIds upon widget creation and it becomes context menu for those nodes
+
+ this.eventNames = {
+ open: ""
+ };
+ },
+{
+ snarfChildDomOutput: true,
+
+ // String
+ // if "default" event names are based on widget id, otherwise user must define
+ // TODO: write real documentation about the events
+ eventNaming: "default",
+
+ templateString: '<table class="dojoPopupMenu2" border=0 cellspacing=0 cellpadding=0 style="display: none;"><tbody dojoAttachPoint="containerNode"></tbody></table>',
+ templateCssPath: dojo.uri.dojoUri("src/widget/templates/Menu2.css"),
+ templateCssString: "",
+
+ // Integer
+ // number of milliseconds before hovering (without clicking) causes the submenu to automatically open
+ submenuDelay: 500,
+
+ // Integer
+ // a submenu usually appears to the right, but slightly overlapping, it's parent menu;
+ // this controls the number of pixels the two menus overlap.
+ submenuOverlap: 5,
+
+ // Boolean
+ // if true, right clicking anywhere on the window will cause this context menu to open;
+ // if false, must specify targetNodeIds
+ contextMenuForWindow: false,
+
+ // Array
+ // Array of dom node ids of nodes to attach to
+ targetNodeIds: [],
+
+ initialize: function(args, frag) {
+ if (this.eventNaming == "default") {
+ for (var eventName in this.eventNames) {
+ this.eventNames[eventName] = this.widgetId+"/"+eventName;
+ }
+ }
+ },
+
+ postCreate: function(){
+ if (this.contextMenuForWindow){
+ var doc = dojo.body();
+ this.bindDomNode(doc);
+ } else if ( this.targetNodeIds.length > 0 ){
+ dojo.lang.forEach(this.targetNodeIds, this.bindDomNode, this);
+ }
+
+ this._subscribeSubitemsOnOpen();
+ },
+
+ _subscribeSubitemsOnOpen: function() {
+ var subItems = this.getChildrenOfType(dojo.widget.MenuItem2);
+
+ for(var i=0; i<subItems.length; i++) {
+ dojo.event.topic.subscribe(this.eventNames.open, subItems[i], "menuOpen")
+ }
+ },
+
+ getTopOpenEvent: function() {
+ // summary: get event that initially caused current chain of menus to open
+ var menu = this;
+ while (menu.parentPopup){ menu = menu.parentPopup; }
+ return menu.openEvent; // Event
+ },
+
+ bindDomNode: function(/*String|DomNode*/ node){
+ // summary: attach menu to given node
+ node = dojo.byId(node);
+
+ var win = dojo.html.getElementWindow(node);
+ if(dojo.html.isTag(node,'iframe') == 'iframe'){
+ win = dojo.html.iframeContentWindow(node);
+ node = dojo.withGlobal(win, dojo.body);
+ }
+ // fixes node so that it supports oncontextmenu if not natively supported, Konqueror, Opera more?
+ dojo.widget.Menu2.OperaAndKonqFixer.fixNode(node);
+
+ dojo.event.kwConnect({
+ srcObj: node,
+ srcFunc: "oncontextmenu",
+ targetObj: this,
+ targetFunc: "onOpen",
+ once: true
+ });
+
+ //normal connect does not work if document.designMode is on in FF, use addListener instead
+ if(dojo.render.html.moz && win.document.designMode.toLowerCase() == 'on'){
+ dojo.event.browser.addListener(node, "contextmenu", dojo.lang.hitch(this, "onOpen"));
+ }
+ dojo.widget.PopupManager.registerWin(win);
+ },
+
+ unBindDomNode: function(/*String|DomNode*/ nodeName){
+ // summary: detach menu from given node
+ var node = dojo.byId(nodeName);
+ dojo.event.kwDisconnect({
+ srcObj: node,
+ srcFunc: "oncontextmenu",
+ targetObj: this,
+ targetFunc: "onOpen",
+ once: true
+ });
+
+ // cleans a fixed node, konqueror and opera
+ dojo.widget.Menu2.OperaAndKonqFixer.cleanNode(node);
+ },
+
+ _moveToNext: function(/*Event*/ evt){
+ this._highlightOption(1);
+ return true; //do not pass to parent menu
+ },
+
+ _moveToPrevious: function(/*Event*/ evt){
+ this._highlightOption(-1);
+ return true; //do not pass to parent menu
+ },
+
+ _moveToParentMenu: function(/*Event*/ evt){
+ if(this._highlighted_option && this.parentPopup){
+ //only process event in the focused menu
+ //and its immediate parentPopup to support
+ //MenuBar2
+ if(evt._menu2UpKeyProcessed){
+ return true; //do not pass to parent menu
+ }else{
+ this._highlighted_option.onUnhover();
+ this.closeSubpopup();
+ evt._menu2UpKeyProcessed = true;
+ }
+ }
+ return false;
+ },
+
+ _moveToChildMenu: function(/*Event*/ evt){
+ if(this._highlighted_option && this._highlighted_option.submenuId){
+ this._highlighted_option._onClick(true);
+ return true; //do not pass to parent menu
+ }
+ return false;
+ },
+
+ _selectCurrentItem: function(/*Event*/ evt){
+ if(this._highlighted_option){
+ this._highlighted_option._onClick();
+ return true;
+ }
+ return false;
+ },
+
+ processKey: function(/*Event*/ evt){
+ // summary
+ // callback to process key strokes
+ // return true to stop the event being processed by the
+ // parent popupmenu
+
+ if(evt.ctrlKey || evt.altKey || !evt.key){ return false; }
+
+ var rval = false;
+ switch(evt.key){
+ case evt.KEY_DOWN_ARROW:
+ rval = this._moveToNext(evt);
+ break;
+ case evt.KEY_UP_ARROW:
+ rval = this._moveToPrevious(evt);
+ break;
+ case evt.KEY_RIGHT_ARROW:
+ rval = this._moveToChildMenu(evt);
+ break;
+ case evt.KEY_LEFT_ARROW:
+ rval = this._moveToParentMenu(evt);
+ break;
+ case " ": //fall through
+ case evt.KEY_ENTER:
+ if(rval = this._selectCurrentItem(evt)){
+ break;
+ }
+ //fall through
+ case evt.KEY_ESCAPE:
+ dojo.widget.PopupManager.currentMenu.close();
+ rval = true;
+ break;
+ }
+
+ return rval;
+ },
+
+ _findValidItem: function(dir, curItem){
+ if(curItem){
+ curItem = dir>0 ? curItem.getNextSibling() : curItem.getPreviousSibling();
+ }
+
+ for(var i=0; i < this.children.length; ++i){
+ if(!curItem){
+ curItem = dir>0 ? this.children[0] : this.children[this.children.length-1];
+ }
+ //find next/previous visible menu item, not including separators
+ if(curItem.onHover && curItem.isShowing()){
+ return curItem;
+ }
+ curItem = dir>0 ? curItem.getNextSibling() : curItem.getPreviousSibling();
+ }
+ },
+
+ _highlightOption: function(dir){
+ var item;
+ // || !this._highlighted_option.parentNode
+ if((!this._highlighted_option)){
+ item = this._findValidItem(dir);
+ }else{
+ item = this._findValidItem(dir, this._highlighted_option);
+ }
+ if(item){
+ if(this._highlighted_option) {
+ this._highlighted_option.onUnhover();
+ }
+ item.onHover();
+ dojo.html.scrollIntoView(item.domNode);
+ // navigate into the item table and select the first caption tag
+ try {
+ var node = dojo.html.getElementsByClass("dojoMenuItem2Label", item.domNode)[0];
+ node.focus();
+ } catch(e) { }
+ }
+ },
+
+ onItemClick: function(/*Widget*/ item) {
+ // summary: user defined function to handle clicks on an item
+ },
+
+ close: function(/*Boolean*/ force){
+ // summary: close the menu
+ if(this.animationInProgress){
+ dojo.widget.PopupMenu2.superclass.close.apply(this, arguments);
+ return;
+ }
+
+ if(this._highlighted_option){
+ this._highlighted_option.onUnhover();
+ }
+
+ dojo.widget.PopupMenu2.superclass.close.apply(this, arguments);
+ },
+
+ closeSubpopup: function(force){
+ // summary: close the currently displayed submenu
+ if (this.currentSubpopup == null){ return; }
+
+ this.currentSubpopup.close(force);
+ this.currentSubpopup = null;
+
+ this.currentSubmenuTrigger.is_open = false;
+ this.currentSubmenuTrigger._closedSubmenu(force);
+ this.currentSubmenuTrigger = null;
+ },
+
+ _openSubmenu: function(submenu, from_item){
+ // summary: open the menu to the right of the current menu item
+ var fromPos = dojo.html.getAbsolutePosition(from_item.domNode, true);
+ var our_w = dojo.html.getMarginBox(this.domNode).width;
+ var x = fromPos.x + our_w - this.submenuOverlap;
+ var y = fromPos.y;
+
+ //the following is set in open, so we do not need it
+ //this.currentSubpopup = submenu;
+ submenu.open(x, y, this, from_item.domNode);
+
+ this.currentSubmenuTrigger = from_item;
+ this.currentSubmenuTrigger.is_open = true;
+ },
+
+ onOpen: function(/*Event*/ e){
+ // summary: callback when menu is opened
+ this.openEvent = e;
+ if(e["target"]){
+ this.openedForWindow = dojo.html.getElementWindow(e.target);
+ }else{
+ this.openedForWindow = null;
+ }
+ var x = e.pageX, y = e.pageY;
+
+ var win = dojo.html.getElementWindow(e.target);
+ var iframe = win._frameElement || win.frameElement;
+ if(iframe){
+ var cood = dojo.html.abs(iframe, true);
+ x += cood.x - dojo.withGlobal(win, dojo.html.getScroll).left;
+ y += cood.y - dojo.withGlobal(win, dojo.html.getScroll).top;
+ }
+ this.open(x, y, null, [x, y]);
+
+ e.preventDefault();
+ e.stopPropagation();
+ }
+});
+
+// summary
+// A line item in a Menu2
+dojo.widget.defineWidget(
+ "dojo.widget.MenuItem2",
+ dojo.widget.HtmlWidget,
+ function(){
+ this.eventNames = {
+ engage: ""
+ };
+ },
+{
+ // Make 4 columns
+ // icon, label, accelerator-key, and right-arrow indicating sub-menu
+ templateString:
+ '<tr class="dojoMenuItem2" dojoAttachEvent="onMouseOver: onHover; onMouseOut: onUnhover; onClick: _onClick; onKey:onKey;">'
+ +'<td><div class="${this.iconClass}" style="${this.iconStyle}"></div></td>'
+ +'<td tabIndex="-1" class="dojoMenuItem2Label">${this.caption}</td>'
+ +'<td class="dojoMenuItem2Accel">${this.accelKey}</td>'
+ +'<td><div class="dojoMenuItem2Submenu" style="display:${this.arrowDisplay};"></div></td>'
+ +'</tr>',
+
+ //
+ // internal settings
+ //
+
+ is_hovering: false,
+ hover_timer: null,
+ is_open: false,
+ topPosition: 0,
+
+ //
+ // options
+ //
+
+ // String
+ // text of the menu item
+ caption: 'Untitled',
+
+ // String
+ // accelerator key (not supported yet!)
+ accelKey: '',
+
+ // String
+ // path to icon to display to the left of the menu text
+ iconSrc: '',
+
+ // String
+ // CSS class name to use for menu item (if CSS class specifies a background image then iconSrc is not necessary)
+ iconClass: 'dojoMenuItem2Icon',
+
+ // String
+ // widget ID of Menu2 widget to open when this menu item is clicked
+ submenuId: '',
+
+ // Boolean
+ // if true, this menu item cannot be selected
+ disabled: false,
+
+ // String
+ // event names for announcing when menu item is clicked.
+ // if "default", then use the default name, based on the widget ID
+ eventNaming: "default",
+
+ // String
+ // CSS class for menu item when it's hovered over
+ highlightClass: 'dojoMenuItem2Hover',
+
+ postMixInProperties: function(){
+ this.iconStyle="";
+ if (this.iconSrc){
+ if ((this.iconSrc.toLowerCase().substring(this.iconSrc.length-4) == ".png") && (dojo.render.html.ie55 || dojo.render.html.ie60)){
+ this.iconStyle="filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='"+this.iconSrc+"', sizingMethod='image')";
+ }else{
+ this.iconStyle="background-image: url("+this.iconSrc+")";
+ }
+ }
+ this.arrowDisplay = this.submenuId ? 'block' : 'none';
+ dojo.widget.MenuItem2.superclass.postMixInProperties.apply(this, arguments);
+ },
+
+ fillInTemplate: function(){
+ dojo.html.disableSelection(this.domNode);
+
+ if (this.disabled){
+ this.setDisabled(true);
+ }
+
+ if (this.eventNaming == "default") {
+ for (var eventName in this.eventNames) {
+ this.eventNames[eventName] = this.widgetId+"/"+eventName;
+ }
+ }
+ },
+
+ onHover: function(){
+ // summary: callback when mouse is moved onto menu item
+
+ //this is to prevent some annoying behavior when both mouse and keyboard are used
+ this.onUnhover();
+
+ if (this.is_hovering){ return; }
+ if (this.is_open){ return; }
+
+ if(this.parent._highlighted_option){
+ this.parent._highlighted_option.onUnhover();
+ }
+ this.parent.closeSubpopup();
+ this.parent._highlighted_option = this;
+ dojo.widget.PopupManager.setFocusedMenu(this.parent);
+
+ this._highlightItem();
+
+ if (this.is_hovering){ this._stopSubmenuTimer(); }
+ this.is_hovering = true;
+ this._startSubmenuTimer();
+ },
+
+ onUnhover: function(){
+ // summary: callback when mouse is moved off of menu item
+ if(!this.is_open){ this._unhighlightItem(); }
+
+ this.is_hovering = false;
+
+ this.parent._highlighted_option = null;
+
+ if(this.parent.parentPopup){
+ dojo.widget.PopupManager.setFocusedMenu(this.parent.parentPopup);
+ }
+
+ this._stopSubmenuTimer();
+ },
+
+ _onClick: function(focus){
+ // summary: internal function for clicks
+ var displayingSubMenu = false;
+ if (this.disabled){ return false; }
+
+ if (this.submenuId){
+ if (!this.is_open){
+ this._stopSubmenuTimer();
+ this._openSubmenu();
+ }
+ displayingSubMenu = true;
+ }else{
+ // for some browsers the onMouseOut doesn't get called (?), so call it manually
+ this.onUnhover(); //only onUnhover when no submenu is available
+ this.parent.closeAll(true);
+ }
+
+ // user defined handler for click
+ this.onClick();
+
+ dojo.event.topic.publish(this.eventNames.engage, this);
+
+ if(displayingSubMenu && focus){
+ dojo.widget.getWidgetById(this.submenuId)._highlightOption(1);
+ }
+ return;
+ },
+
+ onClick: function() {
+ // summary
+ // User defined function to handle clicks
+ // this default function call the parent
+ // menu's onItemClick
+ this.parent.onItemClick(this);
+ },
+
+ _highlightItem: function(){
+ dojo.html.addClass(this.domNode, this.highlightClass);
+ },
+
+ _unhighlightItem: function(){
+ dojo.html.removeClass(this.domNode, this.highlightClass);
+ },
+
+ _startSubmenuTimer: function(){
+ this._stopSubmenuTimer();
+
+ if (this.disabled){ return; }
+
+ var self = this;
+ var closure = function(){ return function(){ self._openSubmenu(); } }();
+
+ this.hover_timer = dojo.lang.setTimeout(closure, this.parent.submenuDelay);
+ },
+
+ _stopSubmenuTimer: function(){
+ if (this.hover_timer){
+ dojo.lang.clearTimeout(this.hover_timer);
+ this.hover_timer = null;
+ }
+ },
+
+ _openSubmenu: function(){
+ if (this.disabled){ return; }
+
+ // first close any other open submenu
+ this.parent.closeSubpopup();
+
+ var submenu = dojo.widget.getWidgetById(this.submenuId);
+ if (submenu){
+ this.parent._openSubmenu(submenu, this);
+ }
+ },
+
+ _closedSubmenu: function(){
+ this.onUnhover();
+ },
+
+ setDisabled: function(/*Boolean*/ value){
+ // summary: enable or disable this menu item
+ this.disabled = value;
+
+ if (this.disabled){
+ dojo.html.addClass(this.domNode, 'dojoMenuItem2Disabled');
+ }else{
+ dojo.html.removeClass(this.domNode, 'dojoMenuItem2Disabled');
+ }
+ },
+
+ enable: function(){
+ // summary: enable this menu item so user can click it
+ this.setDisabled(false);
+ },
+
+ disable: function(){
+ // summary: disable this menu item so user can't click it
+ this.setDisabled(true);
+ },
+
+ menuOpen: function(message) {
+ // summary: callback when menu is opened
+ // TODO: I don't see anyone calling this menu item
+ }
+
+});
+
+// summary
+// A line between two menu items
+dojo.widget.defineWidget(
+ "dojo.widget.MenuSeparator2",
+ dojo.widget.HtmlWidget,
+{
+ templateString: '<tr class="dojoMenuSeparator2"><td colspan=4>'
+ +'<div class="dojoMenuSeparator2Top"></div>'
+ +'<div class="dojoMenuSeparator2Bottom"></div>'
+ +'</td></tr>',
+
+ postCreate: function(){
+ dojo.html.disableSelection(this.domNode);
+ }
+});
+
+// summary
+// A menu bar, listing menu choices horizontally, like the "File" menu in most desktop applications
+dojo.widget.defineWidget(
+ "dojo.widget.MenuBar2",
+ dojo.widget.PopupMenu2,
+{
+ menuOverlap: 2,
+
+ templateString: '<div class="dojoMenuBar2" tabIndex="0"><table class="dojoMenuBar2Client"><tr dojoAttachPoint="containerNode"></tr></table></div>',
+
+ close: function(force){
+ if(this._highlighted_option){
+ this._highlighted_option.onUnhover();
+ }
+
+ this.closeSubpopup(force);
+ },
+
+ processKey: function(/*Event*/ evt){
+ if(evt.ctrlKey || evt.altKey){ return false; }
+
+ if (!dojo.html.hasClass(evt.target,"dojoMenuBar2")) { return false; }
+ var rval = false;
+
+ switch(evt.key){
+ case evt.KEY_DOWN_ARROW:
+ rval = this._moveToChildMenu(evt);
+ break;
+ case evt.KEY_UP_ARROW:
+ rval = this._moveToParentMenu(evt);
+ break;
+ case evt.KEY_RIGHT_ARROW:
+ rval = this._moveToNext(evt);
+ break;
+ case evt.KEY_LEFT_ARROW:
+ rval = this._moveToPrevious(evt);
+ break;
+ default:
+ rval = dojo.widget.MenuBar2.superclass.processKey.apply(this, arguments);
+ break;
+ }
+
+ return rval;
+ },
+
+ postCreate: function(){
+ dojo.widget.MenuBar2.superclass.postCreate.apply(this, arguments);
+ dojo.widget.PopupManager.opened(this);
+ this.isShowingNow = true;
+ },
+
+ /*
+ * override PopupMenu2 to open the submenu below us rather than to our right
+ */
+ _openSubmenu: function(submenu, from_item){
+ var fromPos = dojo.html.getAbsolutePosition(from_item.domNode, true);
+ var ourPos = dojo.html.getAbsolutePosition(this.domNode, true);
+ var our_h = dojo.html.getBorderBox(this.domNode).height;
+ var x = fromPos.x;
+ var y = ourPos.y + our_h - this.menuOverlap;
+
+ submenu.open(x, y, this, from_item.domNode);
+
+ this.currentSubmenuTrigger = from_item;
+ this.currentSubmenuTrigger.is_open = true;
+ }
+});
+
+// summary
+// Item in a Menu2Bar
+dojo.widget.defineWidget(
+ "dojo.widget.MenuBarItem2",
+ dojo.widget.MenuItem2,
+{
+ templateString:
+ '<td class="dojoMenuBarItem2" dojoAttachEvent="onMouseOver: onHover; onMouseOut: onUnhover; onClick: _onClick;">'
+ +'<span>${this.caption}</span>'
+ +'</td>',
+
+ highlightClass: 'dojoMenuBarItem2Hover',
+
+ setDisabled: function(value){
+ this.disabled = value;
+ if (this.disabled){
+ dojo.html.addClass(this.domNode, 'dojoMenuBarItem2Disabled');
+ }else{
+ dojo.html.removeClass(this.domNode, 'dojoMenuBarItem2Disabled');
+ }
+ }
+});
+
+
+// ************************** make contextmenu work in konqueror and opera *********************
+dojo.widget.Menu2.OperaAndKonqFixer = new function(){
+ var implement = true;
+ var delfunc = false;
+
+ /** dom event check
+ *
+ * make a event and dispatch it and se if it calls function below,
+ * if it indeed is supported and we dont need to implement our own
+ */
+
+ // gets called if we have support for oncontextmenu
+ if (!dojo.lang.isFunction(dojo.doc().oncontextmenu)){
+ dojo.doc().oncontextmenu = function(){
+ implement = false;
+ delfunc = true;
+ }
+ }
+
+ if (dojo.doc().createEvent){ // moz, safari has contextmenu event, need to do livecheck on this env.
+ try {
+ var e = dojo.doc().createEvent("MouseEvents");
+ e.initMouseEvent("contextmenu", 1, 1, dojo.global(), 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, null);
+ dojo.doc().dispatchEvent(e);
+ } catch (e) {/* assume not supported */}
+ } else {
+ // IE no need to implement custom contextmenu
+ implement = false;
+ }
+
+ // clear this one if it wasn't there before
+ if (delfunc){
+ delete dojo.doc().oncontextmenu;
+ }
+ /***** end dom event check *****/
+
+
+ /**
+ * this fixes a dom node by attaching a custom oncontextmenu function that gets called when apropriate
+ * @param node a dom node
+ *
+ * no returns
+ */
+ this.fixNode = function(node){
+ if (implement){
+ // attach stub oncontextmenu function
+ if (!dojo.lang.isFunction(node.oncontextmenu)){
+ node.oncontextmenu = function(e){/*stub*/}
+ }
+
+ // attach control function for oncontextmenu
+ if (dojo.render.html.opera){
+ // opera
+ // listen to ctrl-click events
+ node._menufixer_opera = function(e){
+ if (e.ctrlKey){
+ this.oncontextmenu(e);
+ }
+ };
+
+ dojo.event.connect(node, "onclick", node, "_menufixer_opera");
+
+ } else {
+ // konqueror
+ // rightclick, listen to mousedown events
+ node._menufixer_konq = function(e){
+ if (e.button==2 ){
+ e.preventDefault(); // need to prevent browsers menu
+ this.oncontextmenu(e);
+ }
+ };
+
+ dojo.event.connect(node, "onmousedown", node, "_menufixer_konq");
+ }
+ }
+ }
+
+ /**
+ * this cleans up a fixed node, prevent memoryleak?
+ * @param node node to clean
+ *
+ * no returns
+ */
+ this.cleanNode = function(node){
+ if (implement){
+ // checks needed if we gets a non fixed node
+ if (node._menufixer_opera){
+ dojo.event.disconnect(node, "onclick", node, "_menufixer_opera");
+ delete node._menufixer_opera;
+ } else if(node._menufixer_konq){
+ dojo.event.disconnect(node, "onmousedown", node, "_menufixer_konq");
+ delete node._menufixer_konq;
+ }
+ if (node.oncontextmenu){
+ delete node.oncontextmenu;
+ }
+ }
+ }
+};
\ No newline at end of file
Added: struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/MonthlyCalendar.js
URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/MonthlyCalendar.js?view=auto&rev=514083
==============================================================================
--- struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/MonthlyCalendar.js (added)
+++ struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/MonthlyCalendar.js Fri Mar 2 21:48:54 2007
@@ -0,0 +1,188 @@
+/*
+ Copyright (c) 2004-2006, The Dojo Foundation
+ All Rights Reserved.
+
+ Licensed under the Academic Free License version 2.1 or above OR the
+ modified BSD license. For more information on Dojo licensing, see:
+
+ http://dojotoolkit.org/community/licensing.shtml
+*/
+
+dojo.provide("dojo.widget.MonthlyCalendar");
+dojo.require("dojo.date.common");
+dojo.require("dojo.date.format");
+dojo.require("dojo.widget.*");
+dojo.require("dojo.widget.DatePicker");
+dojo.require("dojo.event.*");
+dojo.require("dojo.html.*");
+dojo.require("dojo.experimental");
+
+dojo.experimental("dojo.widget.MonthlyCalendar");
+
+dojo.widget.defineWidget(
+ "dojo.widget.MonthlyCalendar",
+ dojo.widget.DatePicker,
+ {
+ dayWidth: 'wide',
+
+ templatePath: dojo.uri.dojoUri("src/widget/templates/MonthlyCalendar.html"),
+ templateCssPath: dojo.uri.dojoUri("src/widget/templates/MonthlyCalendar.css"),
+
+ initializer: function(){
+ this.iCalendars = [];
+ },
+
+ /*
+ cache: function(){
+ },
+ */
+
+ addCalendar: function(/* dojo.iCalendar */ cal) {
+ dojo.debug("Adding Calendar");
+ this.iCalendars.push(cal);
+ dojo.debug("Starting init");
+ this.initUI();
+ dojo.debug("done init");
+ },
+
+ createDayContents: function(node,mydate) {
+ dojo.html.removeChildren(node);
+ node.appendChild(document.createTextNode(mydate.getDate()));
+ for(var x=0; x<this.iCalendars.length; x++) {
+ var evts = this.iCalendars[x].getEvents(mydate);
+ if ((dojo.lang.isArray(evts)) && (evts.length>0)) {
+ for(var y=0;y<evts.length;y++) {
+ var el = document.createElement("div");
+ dojo.html.addClass(el, "dojoMonthlyCalendarEvent");
+ el.appendChild(document.createTextNode(evts[y].summary.value));
+ el.width = dojo.html.getContentBox(node).width;
+ node.appendChild(el);
+ }
+ }
+ }
+ },
+
+ initUI: function() {
+ var dayLabels = dojo.date.getNames('days', this.dayWidth, 'standAlone', this.lang);
+ var dayLabelNodes = this.dayLabelsRow.getElementsByTagName("td");
+ for(var i=0; i<7; i++) {
+ dayLabelNodes.item(i).innerHTML = dayLabels[i];
+ }
+
+ this.selectedIsUsed = false;
+ this.currentIsUsed = false;
+ var currentClassName = "";
+ var previousDate = new Date();
+ var calendarNodes = this.calendarDatesContainerNode.getElementsByTagName("td");
+ var currentCalendarNode;
+ // set hours of date such that there is no chance of rounding error due to
+ // time change in local time zones
+ previousDate.setHours(8);
+ var nextDate = new Date(this.firstSaturday.year, this.firstSaturday.month, this.firstSaturday.date, 8);
+ var lastDay = new Date(this.firstSaturday.year, this.firstSaturday.month, this.firstSaturday.date + 42, 8);
+
+ if (this.iCalendars.length > 0) {
+ for (var x=0; x<this.iCalendars.length;x++) {
+ this.iCalendars[x].preComputeRecurringEvents(lastDay);
+ }
+ }
+
+ if(this.firstSaturday.date < 7) {
+ // this means there are days to show from the previous month
+ var dayInWeek = 6;
+ for (var i=this.firstSaturday.date; i>0; i--) {
+ currentCalendarNode = calendarNodes.item(dayInWeek);
+ this.createDayContents(currentCalendarNode, nextDate);
+
+ dojo.html.setClass(currentCalendarNode, this.getDateClassName(nextDate, "current"));
+ dayInWeek--;
+ previousDate = nextDate;
+ nextDate = this.incrementDate(nextDate, false);
+ }
+ for(var i=dayInWeek; i>-1; i--) {
+ currentCalendarNode = calendarNodes.item(i);
+
+ this.createDayContents(currentCalendarNode, nextDate);
+
+ dojo.html.setClass(currentCalendarNode, this.getDateClassName(nextDate, "previous"));
+ previousDate = nextDate;
+ nextDate = this.incrementDate(nextDate, false);
+ }
+ } else {
+ nextDate.setDate(1);
+ for(var i=0; i<7; i++) {
+ currentCalendarNode = calendarNodes.item(i);
+ this.createDayContents(currentCalendarNode, nextDate);
+ dojo.html.setClass(currentCalendarNode, this.getDateClassName(nextDate, "current"));
+ previousDate = nextDate;
+ nextDate = this.incrementDate(nextDate, true);
+ }
+ }
+ previousDate.setDate(this.firstSaturday.date);
+ previousDate.setMonth(this.firstSaturday.month);
+ previousDate.setFullYear(this.firstSaturday.year);
+ nextDate = this.incrementDate(previousDate, true);
+ var count = 7;
+ currentCalendarNode = calendarNodes.item(count);
+ while((nextDate.getMonth() == previousDate.getMonth()) && (count<42)) {
+ this.createDayContents(currentCalendarNode, nextDate);
+ dojo.html.setClass(currentCalendarNode, this.getDateClassName(nextDate, "current"));
+ currentCalendarNode = calendarNodes.item(++count);
+ previousDate = nextDate;
+ nextDate = this.incrementDate(nextDate, true);
+ }
+
+ while(count < 42) {
+ this.createDayContents(currentCalendarNode, nextDate);
+ dojo.html.setClass(currentCalendarNode, this.getDateClassName(nextDate, "next"));
+ currentCalendarNode = calendarNodes.item(++count);
+ previousDate = nextDate;
+ nextDate = this.incrementDate(nextDate, true);
+ }
+ this.setMonthLabel(this.firstSaturday.month);
+ this.setYearLabels(this.firstSaturday.year);
+ }
+ }
+);
+
+dojo.widget.MonthlyCalendar.util= new function() {
+
+ this.toRfcDate = function(jsDate) {
+ if(!jsDate) {
+ jsDate = this.today;
+ }
+ var year = jsDate.getFullYear();
+ var month = jsDate.getMonth() + 1;
+ if (month < 10) {
+ month = "0" + month.toString();
+ }
+ var date = jsDate.getDate();
+ if (date < 10) {
+ date = "0" + date.toString();
+ }
+ // because this is a date picker and not a time picker, we treat time
+ // as zero
+ return year + "-" + month + "-" + date + "T00:00:00+00:00";
+ }
+
+ this.fromRfcDate = function(rfcDate) {
+ var tempDate = rfcDate.split("-");
+ if(tempDate.length < 3) {
+ return new Date();
+ }
+ // fullYear, month, date
+ return new Date(parseInt(tempDate[0]), (parseInt(tempDate[1], 10) - 1), parseInt(tempDate[2].substr(0,2), 10));
+ }
+
+//Note: redundant with dojo.widget.DatePicker.util
+ this.initFirstSaturday = function(month, year) {
+ if(!month) {
+ month = this.date.getMonth();
+ }
+ if(!year) {
+ year = this.date.getFullYear();
+ }
+ var firstOfMonth = new Date(year, month, 1);
+ return {year: year, month: month, date: 7 - firstOfMonth.getDay()};
+ }
+}
Added: struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/PageContainer.js
URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/PageContainer.js?view=auto&rev=514083
==============================================================================
--- struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/PageContainer.js (added)
+++ struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/PageContainer.js Fri Mar 2 21:48:54 2007
@@ -0,0 +1,387 @@
+/*
+ Copyright (c) 2004-2006, The Dojo Foundation
+ All Rights Reserved.
+
+ Licensed under the Academic Free License version 2.1 or above OR the
+ modified BSD license. For more information on Dojo licensing, see:
+
+ http://dojotoolkit.org/community/licensing.shtml
+*/
+
+dojo.provide("dojo.widget.PageContainer");
+
+dojo.require("dojo.lang.func");
+dojo.require("dojo.widget.*");
+dojo.require("dojo.event.*");
+dojo.require("dojo.html.selection");
+
+// A PageContainer is a container that has multiple children, but shows only
+// one child at a time (like looking at the pages in a book one by one).
+//
+// Publishes topics <widgetId>-addChild, <widgetId>-removeChild, and <widgetId>-selectChild
+//
+// Can be base class for container, Wizard, Show, etc.
+dojo.widget.defineWidget("dojo.widget.PageContainer", dojo.widget.HtmlWidget, {
+ isContainer: true,
+
+ // Boolean
+ // if true, change the size of my currently displayed child to match my size
+ doLayout: true,
+
+ templateString: "<div dojoAttachPoint='containerNode'></div>",
+
+ // String
+ // id of the initially shown page
+ selectedChild: "",
+
+ fillInTemplate: function(args, frag) {
+ // Copy style info from input node to output node
+ var source = this.getFragNodeRef(frag);
+ dojo.html.copyStyle(this.domNode, source);
+ dojo.widget.PageContainer.superclass.fillInTemplate.apply(this, arguments);
+ },
+
+ postCreate: function(args, frag) {
+ if(this.children.length){
+ // Setup each page panel
+ dojo.lang.forEach(this.children, this._setupChild, this);
+
+ // Figure out which child to initially display
+ var initialChild;
+ if(this.selectedChild){
+ this.selectChild(this.selectedChild);
+ }else{
+ for(var i=0; i<this.children.length; i++){
+ if(this.children[i].selected){
+ this.selectChild(this.children[i]);
+ break;
+ }
+ }
+ if(!this.selectedChildWidget){
+ this.selectChild(this.children[0]);
+ }
+ }
+ }
+ },
+
+ addChild: function(child){
+ dojo.widget.PageContainer.superclass.addChild.apply(this, arguments);
+ this._setupChild(child);
+
+ // in case the page labels have overflowed from one line to two lines
+ this.onResized();
+
+ // if this is the first child, then select it
+ if(!this.selectedChildWidget){
+ this.selectChild(child);
+ }
+ },
+
+ _setupChild: function(page){
+ // Summary: Add the given child to this page container
+
+ page.hide();
+
+ // publish the addChild event for panes added via addChild(), and the original panes too
+ dojo.event.topic.publish(this.widgetId+"-addChild", page);
+ },
+
+ removeChild: function(/* Widget */page){
+ dojo.widget.PageContainer.superclass.removeChild.apply(this, arguments);
+
+ // If we are being destroyed than don't run the code below (to select another page), because we are deleting
+ // every page one by one
+ if(this._beingDestroyed){ return; }
+
+ // this will notify any tablists to remove a button; do this first because it may affect sizing
+ dojo.event.topic.publish(this.widgetId+"-removeChild", page);
+
+ if (this.selectedChildWidget === page) {
+ this.selectedChildWidget = undefined;
+ if (this.children.length > 0) {
+ this.selectChild(this.children[0], true);
+ }
+ }
+ },
+
+ selectChild: function(/* Widget */ page, /* Widget */ callingWidget){
+ // summary
+ // Show the given widget (which must be one of my children)
+ page = dojo.widget.byId(page);
+ this.correspondingPageButton = callingWidget;
+
+ // Deselect old page and select new one
+ if(this.selectedChildWidget){
+ this._hideChild(this.selectedChildWidget);
+ }
+ this.selectedChildWidget = page;
+ this._showChild(page);
+ page.isFirstChild = (page == this.children[0]);
+ page.isLastChild = (page == this.children[this.children.length-1]);
+ dojo.event.topic.publish(this.widgetId+"-selectChild", page);
+ },
+
+ forward: function(){
+ // Summary: advance to next page
+ var index = dojo.lang.find(this.children, this.selectedChildWidget);
+ this.selectChild(this.children[index+1]);
+ },
+
+ back: function(){
+ // Summary: go back to previous page
+ var index = dojo.lang.find(this.children, this.selectedChildWidget);
+ this.selectChild(this.children[index-1]);
+ },
+
+ onResized: function(){
+ // Summary: called when any page is shown, to make it fit the container correctly
+ if(this.doLayout && this.selectedChildWidget){
+ with(this.selectedChildWidget.domNode.style){
+ top = dojo.html.getPixelValue(this.containerNode, "padding-top", true);
+ left = dojo.html.getPixelValue(this.containerNode, "padding-left", true);
+ }
+ var content = dojo.html.getContentBox(this.containerNode);
+ this.selectedChildWidget.resizeTo(content.width, content.height);
+ }
+ },
+
+ _showChild: function(page) {
+ // size the current page (in case this is the first time it's being shown, or I have been resized)
+ if(this.doLayout){
+ var content = dojo.html.getContentBox(this.containerNode);
+ page.resizeTo(content.width, content.height);
+ }
+
+ page.selected=true;
+ page.show();
+ },
+
+ _hideChild: function(page) {
+ page.selected=false;
+ page.hide();
+ },
+
+ closeChild: function(page) {
+ // summary
+ // callback when user clicks the [X] to remove a page
+ // if onClose() returns true then remove and destroy the childd
+ var remove = page.onClose(this, page);
+ if(remove) {
+ this.removeChild(page);
+ // makes sure we can clean up executeScripts in ContentPane onUnLoad
+ page.destroy();
+ }
+ },
+
+ destroy: function(){
+ this._beingDestroyed = true;
+ dojo.event.topic.destroy(this.widgetId+"-addChild");
+ dojo.event.topic.destroy(this.widgetId+"-removeChild");
+ dojo.event.topic.destroy(this.widgetId+"-selectChild");
+ dojo.widget.PageContainer.superclass.destroy.apply(this, arguments);
+ }
+});
+
+
+// PageController - set of buttons to select the page in a page list
+// When intialized, the PageController monitors the container, and whenever a page is
+// added or deleted updates itself accordingly.
+dojo.widget.defineWidget(
+ "dojo.widget.PageController",
+ dojo.widget.HtmlWidget,
+ {
+ templateString: "<span wairole='tablist' dojoAttachEvent='onKey'></span>",
+ isContainer: true,
+
+ // String
+ // the id of the page container that I point to
+ containerId: "",
+
+ // String
+ // the name of the button widget to create to correspond to each page
+ buttonWidget: "PageButton",
+
+ // String
+ // Class name to apply to the top dom node
+ "class": "dojoPageController",
+
+ fillInTemplate: function() {
+ dojo.html.addClass(this.domNode, this["class"]); // "class" is a reserved word in JS
+ dojo.widget.wai.setAttr(this.domNode, "waiRole", "role", "tablist");
+ },
+
+ postCreate: function(){
+ this.pane2button = {}; // mapping from panes to buttons
+
+ // If children have already been added to the page container then create buttons for them
+ var container = dojo.widget.byId(this.containerId);
+ if(container){
+ dojo.lang.forEach(container.children, this.onAddChild, this);
+ }
+
+ dojo.event.topic.subscribe(this.containerId+"-addChild", this, "onAddChild");
+ dojo.event.topic.subscribe(this.containerId+"-removeChild", this, "onRemoveChild");
+ dojo.event.topic.subscribe(this.containerId+"-selectChild", this, "onSelectChild");
+ },
+
+ destroy: function(){
+ dojo.event.topic.unsubscribe(this.containerId+"-addChild", this, "onAddChild");
+ dojo.event.topic.unsubscribe(this.containerId+"-removeChild", this, "onRemoveChild");
+ dojo.event.topic.unsubscribe(this.containerId+"-selectChild", this, "onSelectChild");
+ dojo.widget.PageController.superclass.destroy.apply(this, arguments);
+ },
+
+ onAddChild: function(/* Widget */ page){
+ // summary
+ // Called whenever a page is added to the container.
+ // Create button corresponding to the page.
+ var button = dojo.widget.createWidget(this.buttonWidget,
+ {
+ label: page.label,
+ closeButton: page.closable
+ });
+ this.addChild(button);
+ this.domNode.appendChild(button.domNode);
+ this.pane2button[page]=button;
+ page.controlButton = button; // this value might be overwritten if two tabs point to same container
+
+ var _this = this;
+ dojo.event.connect(button, "onClick", function(){ _this.onButtonClick(page); });
+ dojo.event.connect(button, "onCloseButtonClick", function(){ _this.onCloseButtonClick(page); });
+ },
+
+ onRemoveChild: function(/* Widget */ page){
+ // summary
+ // Called whenever a page is removed from the container.
+ // Remove the button corresponding to the page.
+ if(this._currentChild == page){ this._currentChild = null; }
+ var button = this.pane2button[page];
+ if(button){
+ button.destroy();
+ }
+ this.pane2button[page] = null;
+ },
+
+ onSelectChild: function(/*Widget*/ page){
+ // Summary
+ // Called when a page has been selected in the PageContainer, either by me or by another PageController
+ if(this._currentChild){
+ var oldButton=this.pane2button[this._currentChild];
+ oldButton.clearSelected();
+ }
+ var newButton=this.pane2button[page];
+ newButton.setSelected();
+ this._currentChild=page;
+ },
+
+ onButtonClick: function(/*Widget*/ page){
+ // summary
+ // Called whenever one of my child buttons is pressed in an attempt to select a page
+ var container = dojo.widget.byId(this.containerId); // TODO: do this via topics?
+ container.selectChild(page, false, this);
+ },
+
+ onCloseButtonClick: function(/*Widget*/ page){
+ // summary
+ // Called whenever one of my child buttons [X] is pressed in an attempt to close a page
+ var container = dojo.widget.byId(this.containerId);
+ container.closeChild(page);
+ },
+
+ onKey: function(evt){
+ // summary:
+ // Handle keystrokes on the page list, for advancing to next/previous button
+
+ if( (evt.keyCode == evt.KEY_RIGHT_ARROW)||
+ (evt.keyCode == evt.KEY_LEFT_ARROW) ){
+ var current = 0;
+ var next = null; // the next button to focus on
+
+ // find currently focused button in children array
+ var current = dojo.lang.find(this.children, this.pane2button[this._currentChild]);
+
+ // pick next button to focus on
+ if(evt.keyCode == evt.KEY_RIGHT_ARROW){
+ next = this.children[ (current+1) % this.children.length ];
+ }else{ // is LEFT_ARROW
+ next = this.children[ (current+ (this.children.length-1)) % this.children.length ];
+ }
+
+ dojo.event.browser.stopEvent(evt);
+ next.onClick();
+ }
+ }
+ }
+);
+
+// PageButton (the thing you click to select or delete a page)
+dojo.widget.defineWidget("dojo.widget.PageButton", dojo.widget.HtmlWidget,
+{
+ templateString: "<span class='item'>" +
+ "<span dojoAttachEvent='onClick' dojoAttachPoint='titleNode' class='selectButton'>${this.label}</span>" +
+ "<span dojoAttachEvent='onClick:onCloseButtonClick' class='closeButton'>[X]</span>" +
+ "</span>",
+
+ // String
+ // Name to print on the button
+ label: "foo",
+
+ // Boolean
+ // true iff we should also print a close icon to destroy corresponding page
+ closeButton: false,
+
+ onClick: function(){
+ // summary
+ // Basically this is the attach point PageController listens to, to select the page
+ this.focus();
+ },
+
+ onCloseButtonMouseOver: function(){
+ // summary
+ // The close button changes color a bit when you mouse over
+ dojo.html.addClass(this.closeButtonNode, "closeHover");
+ },
+
+ onCloseButtonMouseOut: function(){
+ // summary
+ // Revert close button to normal color on mouse out
+ dojo.html.removeClass(this.closeButtonNode, "closeHover");
+ },
+
+ onCloseButtonClick: function(evt){
+ // summary
+ // Handle clicking the close button for this tab
+ },
+
+ setSelected: function(){
+ // summary
+ // This is run whenever the page corresponding to this button has been selected
+ dojo.html.addClass(this.domNode, "current");
+ this.titleNode.setAttribute("tabIndex","0");
+ },
+
+ clearSelected: function(){
+ // summary
+ // This function is run whenever the page corresponding to this button has been deselected (and another page has been shown)
+ dojo.html.removeClass(this.domNode, "current");
+ this.titleNode.setAttribute("tabIndex","-1");
+ },
+
+ focus: function(){
+ // summary
+ // This will focus on the this button (for accessibility you need to do this when the button is selected)
+ if(this.titleNode.focus){ // mozilla 1.7 doesn't have focus() func
+ this.titleNode.focus();
+ }
+ }
+});
+
+// These arguments can be specified for the children of a PageContainer.
+// Since any widget can be specified as a PageContainer child, mix them
+// into the base widget class. (This is a hack, but it's effective.)
+dojo.lang.extend(dojo.widget.Widget, {
+ label: "",
+ selected: false, // is this tab currently selected?
+ closable: false, // true if user can close this tab pane
+ onClose: function(){ return true; } // callback if someone tries to close the child, child will be closed if func returns true
+});