You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tapestry.apache.org by jk...@apache.org on 2006/09/23 01:22:51 UTC
svn commit: r449122 [28/40] - in
/tapestry/tapestry4/trunk/tapestry-framework/src: java/org/apache/tapestry/
java/org/apache/tapestry/dojo/ java/org/apache/tapestry/dojo/form/
java/org/apache/tapestry/dojo/html/ java/org/apache/tapestry/form/
java/org/...
Added: tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/PopupContainer.js
URL: http://svn.apache.org/viewvc/tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/PopupContainer.js?view=auto&rev=449122
==============================================================================
--- tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/PopupContainer.js (added)
+++ tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/PopupContainer.js Fri Sep 22 16:22:30 2006
@@ -0,0 +1,428 @@
+/*
+ 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.PopupContainer");
+
+dojo.require("dojo.html.style");
+dojo.require("dojo.html.layout");
+dojo.require("dojo.html.selection");
+dojo.require("dojo.html.iframe");
+dojo.require("dojo.event.*");
+dojo.require("dojo.widget.*");
+dojo.require("dojo.widget.HtmlWidget");
+
+
+//PopupContainerBase is the mixin class which provide popup behaviors:
+//it can open in a given position x,y or around a given node.
+//In addition, it handles animation and IE bleed through workaround.
+//This class can not be used standalone: it should be mixed-in to a
+//dojo.widget.HtmlWidget. Use PopupContainer instead if you want a
+//a standalone popup widget
+dojo.declare(
+ "dojo.widget.PopupContainerBase",
+ null,
+ function(){
+ this.queueOnAnimationFinish = [];
+ },
+{
+ isContainer: true,
+ templateString: '<div dojoAttachPoint="containerNode" style="display:none;position:absolute;" class="dojoPopupContainer" ></div>',
+
+ isShowingNow: false,
+
+ currentSubpopup: null,
+
+ beginZIndex: 1000,
+
+ parentPopup: null,
+ parent: null, // parent Widget
+ popupIndex: 0,
+
+ aroundBox: dojo.html.boxSizing.BORDER_BOX, //by default, popup around the BORDER box of the aroundNode in open()
+
+ openedForWindow: null, //in which window, the open() is triggered
+
+ processKey: function(evt){
+ return false;
+ },
+
+ //this function should be called in sub class where a custom
+ //templateString/templateStringPath is used (see Tooltip widget)
+ applyPopupBasicStyle: function(){
+ with(this.domNode.style){
+ display = 'none';
+ position = 'absolute';
+ }
+ },
+
+ //connect to this stub to modify the content of the popup
+ aboutToShow: function() {},
+
+ /**
+ * Open the popup at position (x,y), relative to dojo.body()
+ * Or open(node, parent, explodeSrc, aroundOrient) to open
+ * around node
+ */
+ open: function(x, y, parent, explodeSrc, orient, padding){
+ if (this.isShowingNow){ return; }
+
+ this.aboutToShow();
+
+ // if I click right button and menu is opened, then it gets 2 commands: close -> open
+ // so close enables animation and next "open" is put to queue to occur at new location
+ if(this.animationInProgress){
+ this.queueOnAnimationFinish.push(this.open, arguments);
+ return;
+ }
+
+ // save this so that the focus can be returned
+ this.parent = parent;
+
+ var around = false, node, aroundOrient;
+ if(typeof x == 'object'){
+ node = x;
+ aroundOrient = explodeSrc;
+ explodeSrc = parent;
+ parent = y;
+ around = true;
+ }
+
+ // for unknown reasons even if the domNode is attached to the body in postCreate(),
+ // it's not attached here, so have to attach it here.
+ dojo.body().appendChild(this.domNode);
+
+ // if explodeSrc isn't specified then explode from my parent widget
+ explodeSrc = explodeSrc || parent["domNode"] || [];
+
+ //keep track of parent popup to decided whether this is a top level popup
+ var parentPopup = null;
+ this.isTopLevel = true;
+ while(parent){
+ if(parent !== this && (parent.setOpenedSubpopup != undefined && parent.applyPopupBasicStyle != undefined)){
+ parentPopup = parent;
+ this.isTopLevel = false;
+ parentPopup.setOpenedSubpopup(this);
+ break;
+ }
+ parent = parent.parent;
+ }
+
+ this.parentPopup = parentPopup;
+ this.popupIndex = parentPopup ? parentPopup.popupIndex + 1 : 1;
+
+ if(this.isTopLevel){
+ var button = explodeSrc instanceof Array ? null : explodeSrc;
+ dojo.widget.PopupManager.opened(this, button);
+ }
+
+ //Store the current selection and restore it before the action for a menu item
+ //is executed. This is required as clicking on an menu item deselects current selection
+ if(this.isTopLevel && !dojo.withGlobal(this.openedForWindow||dojo.global(), dojo.html.selection.isCollapsed)){
+ this._bookmark = dojo.withGlobal(this.openedForWindow||dojo.global(), dojo.html.selection.getBookmark);
+ }else{
+ this._bookmark = null;
+ }
+
+ //convert explodeSrc from format [x, y] to
+ //{left: x, top: y, width: 0, height: 0} which is the new
+ //format required by dojo.html.toCoordinateObject
+ if(explodeSrc instanceof Array){
+ explodeSrc = {left: explodeSrc[0], top: explodeSrc[1], width: 0, height: 0};
+ }
+
+ // display temporarily, and move into position, then hide again
+ with(this.domNode.style){
+ display="";
+ zIndex = this.beginZIndex + this.popupIndex;
+ }
+
+ if(around){
+ this.move(node, padding, aroundOrient);
+ }else{
+ this.move(x, y, padding, orient);
+ }
+ this.domNode.style.display="none";
+
+ this.explodeSrc = explodeSrc;
+
+ // then use the user defined method to display it
+ this.show();
+
+ this.isShowingNow = true;
+ },
+
+ /* Summery: calculate where to place the popup
+ move(node, padding, aroundOrient) */
+ move: function(x, y, padding, orient){
+ var around = (typeof x == "object");
+ if(around){
+ var aroundOrient=padding;
+ var node=x;
+ padding=y;
+ if(!aroundOrient){ //By default, attempt to open above the aroundNode, or below
+ aroundOrient = {'BL': 'TL', 'TL': 'BL'};
+ }
+ dojo.html.placeOnScreenAroundElement(this.domNode, node, padding, this.aroundBox, aroundOrient);
+ }else{
+ if(!orient){ orient = 'TL,TR,BL,BR';}
+ dojo.html.placeOnScreen(this.domNode, x, y, padding, true, orient);
+ }
+ },
+
+ close: function(force){
+ if(force){
+ this.domNode.style.display="none";
+ }
+
+ // If we are in the process of opening the menu and we are asked to close it
+ if(this.animationInProgress){
+ this.queueOnAnimationFinish.push(this.close, []);
+ return;
+ }
+
+ this.closeSubpopup(force);
+ this.hide();
+ if(this.bgIframe){
+ this.bgIframe.hide();
+ this.bgIframe.size({left: 0, top: 0, width: 0, height: 0});
+ }
+ if(this.isTopLevel){
+ dojo.widget.PopupManager.closed(this);
+ }
+ this.isShowingNow = false;
+ // return focus to the widget that opened the menu
+ try {
+ this.parent.domNode.focus();
+ } catch(e) {}
+
+ //do not need to restore if current selection is not empty
+ //(use keyboard to select a menu item)
+ if(this._bookmark && dojo.withGlobal(this.openedForWindow||dojo.global(), dojo.html.selection.isCollapsed)){
+ if(this.openedForWindow){
+ this.openedForWindow.focus()
+ }
+ dojo.withGlobal(this.openedForWindow||dojo.global(), "moveToBookmark", dojo.html.selection, [this._bookmark]);
+ }
+ this._bookmark = null;
+ },
+
+ closeAll: function(force){
+ if (this.parentPopup){
+ this.parentPopup.closeAll(force);
+ }else{
+ this.close(force);
+ }
+ },
+
+ //call this when a embedded popup is shown
+ setOpenedSubpopup: function(popup) {
+ this.currentSubpopup = popup;
+ },
+
+ closeSubpopup: function(force) {
+ if(this.currentSubpopup == null){ return; }
+
+ this.currentSubpopup.close(force);
+ this.currentSubpopup = null;
+ },
+
+ onShow: function() {
+ this.inherited('onShow');
+ // With some animation (wipe), after close, the size of the domnode is 0
+ // and next time when shown, the open() function can not determine
+ // the correct place to popup, so we store the opened size here and
+ // set it after close (in function onHide())
+ this.openedSize={w: this.domNode.style.width, h: this.domNode.style.height};
+ // prevent IE bleed through
+ if(dojo.render.html.ie){
+ if(!this.bgIframe){
+ this.bgIframe = new dojo.html.BackgroundIframe();
+ this.bgIframe.setZIndex(this.domNode);
+ }
+
+ this.bgIframe.size(this.domNode);
+ this.bgIframe.show();
+ }
+ this.processQueue();
+ },
+
+ // do events from queue
+ processQueue: function() {
+ if (!this.queueOnAnimationFinish.length) return;
+
+ var func = this.queueOnAnimationFinish.shift();
+ var args = this.queueOnAnimationFinish.shift();
+
+ func.apply(this, args);
+ },
+
+ onHide: function() {
+ dojo.widget.HtmlWidget.prototype.onHide.call(this);
+
+ //restore size of the domnode, see comment in
+ //function onShow()
+ if(this.openedSize){
+ with(this.domNode.style){
+ width=this.openedSize.w;
+ height=this.openedSize.h;
+ }
+ }
+
+ this.processQueue();
+ }
+});
+
+dojo.widget.defineWidget(
+ "dojo.widget.PopupContainer",
+ [dojo.widget.HtmlWidget, dojo.widget.PopupContainerBase], {});
+
+
+//
+// the popup manager makes sure we don't have several popups
+// open at once. the root popup in an opening sequence calls
+// opened(). when a root menu closes it calls closed(). then
+// everything works. lovely.
+//
+
+dojo.widget.PopupManager = new function(){
+
+ this.currentMenu = null;
+ this.currentButton = null; // button that opened current menu (if any)
+ this.currentFocusMenu = null; // the (sub)menu which receives key events
+ this.focusNode = null;
+ this.registeredWindows = [];
+
+ this.registerWin = function(win){
+ if(!win.__PopupManagerRegistered)
+ {
+ dojo.event.connect(win.document, 'onmousedown', this, 'onClick');
+ dojo.event.connect(win, "onscroll", this, "onClick");
+ dojo.event.connect(win.document, "onkey", this, 'onKey');
+ win.__PopupManagerRegistered = true;
+ this.registeredWindows.push(win);
+ }
+ };
+
+ /*
+ This function register all the iframes and the top window,
+ so that whereever the user clicks in the page, the popup
+ menu will be closed
+ In case you add an iframe after onload event, please call
+ dojo.widget.PopupManager.registerWin manually
+ */
+ this.registerAllWindows = function(targetWindow){
+ //starting from window.top, clicking everywhere in this page
+ //should close popup menus
+ if(!targetWindow) { //see comment below
+ targetWindow = dojo.html.getDocumentWindow(window.top.document);
+ }
+
+ this.registerWin(targetWindow);
+
+ for (var i = 0; i < targetWindow.frames.length; i++){
+ try{
+ //do not remove dojo.html.getDocumentWindow, see comment in it
+ var win = dojo.html.getDocumentWindow(targetWindow.frames[i].document);
+ if(win){
+ this.registerAllWindows(win);
+ }
+ }catch(e){ /* squelch error for cross domain iframes */ }
+ }
+ };
+
+ this.unRegisterWin = function(win){
+ if(win.__PopupManagerRegistered)
+ {
+ dojo.event.disconnect(win.document, 'onmousedown', this, 'onClick');
+ dojo.event.disconnect(win, "onscroll", this, "onClick");
+ dojo.event.disconnect(win.document, "onkey", this, 'onKey');
+ win.__PopupManagerRegistered = false;
+ }
+ };
+
+ this.unRegisterAllWindows = function(){
+ for(var i=0;i<this.registeredWindows.length;++i){
+ this.unRegisterWin(this.registeredWindows[i]);
+ }
+ this.registeredWindows = [];
+ };
+
+ dojo.addOnLoad(this, "registerAllWindows");
+ dojo.addOnUnload(this, "unRegisterAllWindows");
+
+ this.closed = function(menu){
+ if (this.currentMenu == menu){
+ this.currentMenu = null;
+ this.currentButton = null;
+ this.currentFocusMenu = null;
+ }
+ };
+
+ this.opened = function(menu, button){
+ if (menu == this.currentMenu){ return; }
+
+ if (this.currentMenu){
+ this.currentMenu.close();
+ }
+
+ this.currentMenu = menu;
+ this.currentFocusMenu = menu;
+ this.currentButton = button;
+ };
+
+ this.setFocusedMenu = function(menu){
+ this.currentFocusMenu = menu;
+ };
+
+ this.onKey = function(e){
+ if (!e.key) { return; }
+ if(!this.currentMenu || !this.currentMenu.isShowingNow){ return; }
+
+ var m = this.currentFocusMenu;
+ while (m){
+ if(m.processKey(e)){
+ e.preventDefault();
+ e.stopPropagation();
+ break;
+ }
+ m = m.parentPopup;
+ }
+ },
+
+ this.onClick = function(e){
+ if (!this.currentMenu){ return; }
+
+ var scrolloffset = dojo.html.getScroll().offset;
+
+ // starting from the base menu, perform a hit test
+ // and exit when one succeeds
+
+ var m = this.currentMenu;
+
+ while (m){
+ if(dojo.html.overElement(m.domNode, e) || dojo.html.isDescendantOf(e.target, m.domNode)){
+ return;
+ }
+ m = m.currentSubpopup;
+ }
+
+ // Also, if user clicked the button that opened this menu, then
+ // that button will send the menu a close() command, so this code
+ // shouldn't try to close the menu. Closing twice messes up animation.
+ if (this.currentButton && dojo.html.overElement(this.currentButton, e)){
+ return;
+ }
+
+ // the click didn't fall within the open menu tree
+ // so close it
+
+ this.currentMenu.close();
+ };
+}
\ No newline at end of file
Propchange: tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/PopupContainer.js
------------------------------------------------------------------------------
svn:eol-style = native
Added: tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/RadioGroup.js
URL: http://svn.apache.org/viewvc/tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/RadioGroup.js?view=auto&rev=449122
==============================================================================
--- tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/RadioGroup.js (added)
+++ tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/RadioGroup.js Fri Sep 22 16:22:30 2006
@@ -0,0 +1,194 @@
+/*
+ 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.RadioGroup");
+
+dojo.require("dojo.lang.common");
+dojo.require("dojo.event.browser");
+dojo.require("dojo.html.selection");
+dojo.require("dojo.widget.*");
+dojo.require("dojo.widget.HtmlWidget");
+
+// Widget that provides useful/common functionality that may be desirable
+// when interacting with ul/ol html lists.
+//
+// This widget was mostly developed under supervision/guidance from Tom Trenka.
+dojo.widget.defineWidget(
+ "dojo.widget.RadioGroup",
+ dojo.widget.HtmlWidget,
+ function(){
+ this.selectedItem=null; //currently selected li, if any
+ this.listNode=null; //the containing ul or ol
+ this.items=[]; //the individual li's
+ this.selected=[];
+
+ // CSS classes as attributes.
+ this.groupCssClass="radioGroup";
+ this.selectedCssClass="selected";
+ this.itemContentCssClass="itemContent";
+ },
+ {
+ isContainer:false,
+ templatePath: null,
+ templateCssPath: null,
+
+ // overridden from HtmlWidget
+ postCreate:function(){
+ this.parseStructure();
+ dojo.html.addClass(this.listNode, this.groupCssClass);
+ this.setupChildren();
+ dojo.event.browser.addListener(this.listNode, "onclick", dojo.lang.hitch(this, "onSelect"));
+ if (this.selectedItem){
+ this.selectItem(this.selectedItem);
+ }
+ },
+
+ /*
+ * Sets local radioGroup and items properties, also validates
+ * that domNode contains an expected list.
+ *
+ * exception: If a ul or ol node can't be found in this widgets domNode
+ */
+ parseStructure:function() {
+ var listNode = dojo.dom.firstElement(this.domNode, "ul");
+ if (!listNode) { listNode = dojo.dom.firstElement(this.domNode, "ol");}
+
+ if (listNode) {
+ this.listNode = listNode;
+ this.items=[]; // reset the items.
+ var nl=listNode.getElementsByTagName("li");
+ for (var i=0; i<nl.length; i++){
+ if(nl[i].parentNode==listNode){
+ this.items.push(nl[i]);
+ }
+ }
+ return;
+ }
+ dojo.raise("RadioGroup: Expected ul or ol content.");
+ },
+
+ /*
+ * Allows the app to add a node on the fly, finishing up
+ * the setup so that we don't need to deal with it on a
+ * widget-wide basis.
+ */
+ add:function(node){
+ if(node.parentNode!=this.listNode){
+ this.listNode.appendChild(node);
+ }
+ this.items.push(node);
+ this.setup(node);
+ },
+
+ // removes the specified node from this group, if it exists
+ remove:function(node){
+ var idx=-1;
+ for(var i=0; i<this.items.length; i++){
+ if(this.items[i]==node){
+ idx=i;
+ break;
+ }
+ }
+ if(idx<0) {return;}
+ this.items.splice(idx,1);
+ node.parentNode.removeChild(node);
+ },
+
+ // removes all items in this list
+ clear:function(){
+ for(var i=0; i<this.items.length; i++){
+ this.listNode.removeChild(this.items[i]);
+ }
+ this.items=[];
+ },
+
+ // clears any selected items from being selected
+ clearSelections:function(){
+ for(var i=0; i<this.items.length; i++){
+ dojo.html.removeClass(this.items[i], this.selectedCssClass);
+ }
+ this.selectedItem=null;
+ },
+
+ setup:function(node){
+ var span = document.createElement("span");
+ dojo.html.disableSelection(span);
+ dojo.html.addClass(span, this.itemContentCssClass);
+ dojo.dom.moveChildren(node, span);
+ node.appendChild(span);
+
+ if (this.selected.length > 0) {
+ var uid = dojo.html.getAttribute(node, "id");
+ if (uid && uid == this.selected){
+ this.selectedItem = node;
+ }
+ }
+ dojo.event.browser.addListener(node, "onclick", dojo.lang.hitch(this, "onItemSelect"));
+ if (dojo.html.hasAttribute(node, "onitemselect")) {
+ var tn = dojo.lang.nameAnonFunc(new Function(dojo.html.getAttribute(node, "onitemselect")),
+ this);
+ dojo.event.browser.addListener(node, "onclick", dojo.lang.hitch(this, tn));
+ }
+ },
+
+ /*
+ * Iterates over the items li nodes and manipulates their
+ * dom structures to handle things like span tag insertion
+ * and selecting a default item.
+ */
+ setupChildren:function(){
+ for (var i=0; i<this.items.length; i++){
+ this.setup(this.items[i]);
+ }
+ },
+
+ // Sets the selectedItem to passed in node, applies
+ // css selection class on new item
+ selectItem:function(node, event, nofire){
+ if(this.selectedItem){
+ dojo.html.removeClass(this.selectedItem, this.selectedCssClass);
+ }
+
+ this.selectedItem = node;
+ dojo.html.addClass(this.selectedItem, this.selectedCssClass);
+
+ // if this is the result of an event, stop here.
+ if (!dj_undef("currentTarget", event)){
+ return;
+ }
+
+ // if there's no nofire flag, passed when this is nailed internally.
+ if(!nofire){
+ if(dojo.render.html.ie){
+ this.selectedItem.fireEvent("onclick");
+ }else{
+ var e = document.createEvent("MouseEvents");
+ e.initEvent("click", true, false);
+ this.selectedItem.dispatchEvent(e);
+ }
+ }
+ },
+
+ // gets the currently selected item
+ getValue:function() {
+ return this.selectedItem;
+ },
+
+ // when the ul or ol contained by this widget is selected
+ onSelect:function(e) { },
+
+ // when an individual li is selected
+ onItemSelect:function(e) {
+ if (!dj_undef("currentTarget", e)){
+ this.selectItem(e.currentTarget, e);
+ }
+ }
+ }
+);
Propchange: tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/RadioGroup.js
------------------------------------------------------------------------------
svn:eol-style = native
Added: tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/RemoteTabController.js
URL: http://svn.apache.org/viewvc/tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/RemoteTabController.js?view=auto&rev=449122
==============================================================================
--- tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/RemoteTabController.js (added)
+++ tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/RemoteTabController.js Fri Sep 22 16:22:30 2006
@@ -0,0 +1,39 @@
+/*
+ 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.RemoteTabController");
+
+//Summary
+//Remote Tab Controller widget. Can be located independently of a tab
+//container and control the selection of its tabs
+dojo.require("dojo.widget.*");
+dojo.require("dojo.widget.TabContainer");
+dojo.require("dojo.event.*");
+
+dojo.widget.defineWidget(
+ "dojo.widget.RemoteTabController",
+ dojo.widget.TabList,
+ {
+ templateCssPath: dojo.uri.dojoUri("src/widget/templates/RemoteTabControl.css"),
+ templateString: '<div dojoAttachPoint="domNode" wairole="tablist"></div>',
+
+ "class": "dojoRemoteTabController",
+
+ fillInTemplate: function() {
+ dojo.html.addClass(this.domNode, this["class"]); // "class" is a reserved word in JS
+
+ if (this.tabContainer) {
+ dojo.addOnLoad(dojo.lang.hitch(this, "setupTabs"));
+ }
+
+ dojo.widget.RemoteTabController.superclass.fillInTemplate.apply(this, arguments);
+ }
+ }
+);
Propchange: tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/RemoteTabController.js
------------------------------------------------------------------------------
svn:eol-style = native
Added: tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/ResizableTextarea.js
URL: http://svn.apache.org/viewvc/tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/ResizableTextarea.js?view=auto&rev=449122
==============================================================================
--- tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/ResizableTextarea.js (added)
+++ tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/ResizableTextarea.js Fri Sep 22 16:22:30 2006
@@ -0,0 +1,102 @@
+/*
+ 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.ResizableTextarea");
+dojo.require("dojo.widget.*");
+dojo.require("dojo.widget.LayoutContainer");
+dojo.require("dojo.widget.ResizeHandle");
+
+dojo.widget.defineWidget(
+ "dojo.widget.ResizableTextarea",
+ dojo.widget.HtmlWidget,
+{
+ templatePath: dojo.uri.dojoUri("src/widget/templates/ResizableTextarea.html"),
+ templateCssPath: dojo.uri.dojoUri("src/widget/templates/ResizableTextarea.css"),
+ isContainer: false,
+ textAreaNode: null,
+ textAreaContainer: null,
+ textAreaContainerNode: null,
+ statusBar: null,
+ statusBarContainerNode: null,
+ statusLabelNode: null,
+ statusLabel: null,
+ rootLayoutNode: null,
+ resizeHandleNode: null,
+ resizeHandle: null,
+
+ fillInTemplate: function(args, frag){
+ this.textAreaNode = this.getFragNodeRef(frag).cloneNode(true);
+
+ // FIXME: Safari apparently needs this!
+ dojo.body().appendChild(this.domNode);
+
+ this.rootLayout = dojo.widget.createWidget(
+ "LayoutContainer",
+ {
+ minHeight: 50,
+ minWidth: 100
+ },
+ this.rootLayoutNode
+ );
+
+
+ this.textAreaContainer = dojo.widget.createWidget(
+ "LayoutContainer",
+ { layoutAlign: "client" },
+ this.textAreaContainerNode
+ );
+ this.rootLayout.addChild(this.textAreaContainer);
+
+ this.textAreaContainer.domNode.appendChild(this.textAreaNode);
+ with(this.textAreaNode.style){
+ width="100%";
+ height="100%";
+ }
+
+ this.statusBar = dojo.widget.createWidget(
+ "LayoutContainer",
+ {
+ layoutAlign: "bottom",
+ minHeight: 28
+ },
+ this.statusBarContainerNode
+ );
+ this.rootLayout.addChild(this.statusBar);
+
+ this.statusLabel = dojo.widget.createWidget(
+ "LayoutContainer",
+ {
+ layoutAlign: "client",
+ minWidth: 50
+ },
+ this.statusLabelNode
+ );
+ this.statusBar.addChild(this.statusLabel);
+
+ this.resizeHandle = dojo.widget.createWidget(
+ "ResizeHandle",
+ { targetElmId: this.rootLayout.widgetId },
+ this.resizeHandleNode
+ );
+ this.statusBar.addChild(this.resizeHandle);
+ // dojo.debug(this.rootLayout.widgetId);
+
+ // dojo.event.connect(this.resizeHandle, "beginSizing", this, "hideContent");
+ // dojo.event.connect(this.resizeHandle, "endSizing", this, "showContent");
+ },
+
+ hideContent: function(){
+ this.textAreaNode.style.display = "none";
+ },
+
+ showContent: function(){
+ this.textAreaNode.style.display = "";
+ }
+});
Propchange: tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/ResizableTextarea.js
------------------------------------------------------------------------------
svn:eol-style = native
Added: tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/ResizeHandle.js
URL: http://svn.apache.org/viewvc/tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/ResizeHandle.js?view=auto&rev=449122
==============================================================================
--- tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/ResizeHandle.js (added)
+++ tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/ResizeHandle.js Fri Sep 22 16:22:30 2006
@@ -0,0 +1,103 @@
+/*
+ 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.ResizeHandle");
+
+dojo.require("dojo.widget.*");
+dojo.require("dojo.html.layout");
+dojo.require("dojo.event.*");
+
+dojo.widget.defineWidget(
+ "dojo.widget.ResizeHandle",
+ dojo.widget.HtmlWidget,
+{
+ isSizing: false,
+ startPoint: null,
+ startSize: null,
+ minSize: null,
+
+ targetElmId: '',
+
+ templateCssPath: dojo.uri.dojoUri("src/widget/templates/ResizeHandle.css"),
+ templateString: '<div class="dojoHtmlResizeHandle"><div></div></div>',
+
+ postCreate: function(){
+ dojo.event.connect(this.domNode, "onmousedown", this, "beginSizing");
+ },
+
+ beginSizing: function(e){
+ if (this.isSizing){ return false; }
+
+ // get the target dom node to adjust. targetElmId can refer to either a widget or a simple node
+ this.targetWidget = dojo.widget.byId(this.targetElmId);
+ this.targetDomNode = this.targetWidget ? this.targetWidget.domNode : dojo.byId(this.targetElmId);
+ if (!this.targetDomNode){ return; }
+
+ this.isSizing = true;
+ this.startPoint = {'x':e.clientX, 'y':e.clientY};
+ var mb = dojo.html.getMarginBox(this.targetDomNode);
+ this.startSize = {'w':mb.width, 'h':mb.height};
+
+ dojo.event.kwConnect({
+ srcObj: dojo.body(),
+ srcFunc: "onmousemove",
+ targetObj: this,
+ targetFunc: "changeSizing",
+ rate: 25
+ });
+ dojo.event.connect(dojo.body(), "onmouseup", this, "endSizing");
+
+ e.preventDefault();
+ },
+
+ changeSizing: function(e){
+ // On IE, if you move the mouse above/to the left of the object being resized,
+ // sometimes clientX/Y aren't set, apparently. Just ignore the event.
+ try{
+ if(!e.clientX || !e.clientY){ return; }
+ }catch(e){
+ // sometimes you get an exception accessing above fields...
+ return;
+ }
+ var dx = this.startPoint.x - e.clientX;
+ var dy = this.startPoint.y - e.clientY;
+
+ var newW = this.startSize.w - dx;
+ var newH = this.startSize.h - dy;
+
+ // minimum size check
+ if (this.minSize) {
+ var mb = dojo.html.getMarginBox(this.targetDomNode);
+ if (newW < this.minSize.w) {
+ newW = mb.width;
+ }
+ if (newH < this.minSize.h) {
+ newH = mb.height;
+ }
+ }
+
+ if(this.targetWidget){
+ this.targetWidget.resizeTo(newW, newH);
+ }else{
+ dojo.html.setMarginBox(this.targetDomNode, { width: newW, height: newH});
+ }
+
+ e.preventDefault();
+ },
+
+ endSizing: function(e){
+ dojo.event.disconnect(dojo.body(), "onmousemove", this, "changeSizing");
+ dojo.event.disconnect(dojo.body(), "onmouseup", this, "endSizing");
+
+ this.isSizing = false;
+ }
+
+
+});
Propchange: tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/ResizeHandle.js
------------------------------------------------------------------------------
svn:eol-style = native
Added: tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/RichText.js
URL: http://svn.apache.org/viewvc/tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/RichText.js?view=auto&rev=449122
==============================================================================
--- tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/RichText.js (added)
+++ tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/RichText.js Fri Sep 22 16:22:30 2006
@@ -0,0 +1,1739 @@
+/*
+ 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
+*/
+
+ /* -*- tab-width: 4 -*- */
+dojo.provide("dojo.widget.RichText");
+
+dojo.require("dojo.widget.*");
+dojo.require("dojo.html.*");
+dojo.require("dojo.html.layout");
+dojo.require("dojo.event.*");
+dojo.require("dojo.string.extras");
+
+// used to save content
+if(dojo.hostenv.post_load_){
+ (function(){
+ var savetextarea = dojo.doc().createElement('textarea');
+ savetextarea.id = "dojo.widget.RichText.savedContent";
+ savetextarea.style = "display:none;position:absolute;top:-100px;left:-100px;height:3px;width:3px;overflow:hidden;";
+ dojo.body().appendChild(savetextarea);
+ })();
+}else{
+ //dojo.body() is not available before onLoad is fired
+ try {
+ dojo.doc().write('<textarea id="dojo.widget.RichText.savedContent" ' +
+ 'style="display:none;position:absolute;top:-100px;left:-100px;height:3px;width:3px;overflow:hidden;"></textarea>');
+ }catch(e){ }
+}
+
+// summary:
+// dojo.widget.RichText is the core of the WYSIWYG editor in dojo, which
+// provides the basic editing features. It also encapsulates the differences
+// of different js engines for various browsers
+dojo.widget.defineWidget(
+ "dojo.widget.RichText",
+ dojo.widget.HtmlWidget,
+ {
+ // Boolean:
+ // whether to inherit the parent's width or simply use 100%
+ inheritWidth: false,
+
+ // Boolean:
+ // whether focusing into this instance of richtext when page onload
+ focusOnLoad: true,
+
+ // String:
+ // If a save name is specified the content is saved and restored if the
+ // editor is not properly closed after editing has started.
+ saveName: "",
+
+ // String:
+ // temporary content storage
+ _content: "",
+
+ // String:
+ // set height to fix the editor at a specific height, with scrolling
+ height: "",
+
+ // String:
+ // The minimum height that the editor should have
+ minHeight: "1em",
+
+ // Boolean:
+ isClosed: true,
+ // Boolean:
+ isLoaded: false,
+
+ // Boolean:
+ // whether to use the active-x object in IE
+ useActiveX: false,
+
+ // Boolean:
+ // whether to use relative URLs for images - if this is enabled
+ // images will be given absolute URLs when inside the editor but
+ // will be changed to use relative URLs (to the current page) on save
+ relativeImageUrls: false,
+
+ // String:
+ // used to concat contents from multiple textareas into a single string
+ _SEPARATOR: "@@**%%__RICHTEXTBOUNDRY__%%**@@",
+
+ /* Init
+ *******/
+
+ fillInTemplate: function(){
+ // summary: see dojo.widget.DomWidget
+ dojo.event.topic.publish("dojo.widget.RichText::init", this);
+ this.open();
+
+ // add the formatting functions
+ var funcs = ["queryCommandEnabled", "queryCommandState",
+ "queryCommandValue", "execCommand"];
+ for(var i = 0; i < funcs.length; i++){
+ dojo.event.connect("around", this, funcs[i], this, "_normalizeCommand");
+ }
+
+ // backwards compatibility, needs to be removed
+ dojo.event.connect(this, "onKeyPressed", this, "afterKeyPress");
+ dojo.event.connect(this, "onKeyPress", this, "keyPress");
+ dojo.event.connect(this, "onKeyDown", this, "keyDown");
+ dojo.event.connect(this, "onKeyUp", this, "keyUp");
+
+ // add default some key handlers
+ var ctrl = this.KEY_CTRL;
+ var exec = function (cmd, arg) {
+ return arguments.length == 1 ? function () { this.execCommand(cmd); } :
+ function () { this.execCommand(cmd, arg); }
+ }
+
+ this.addKeyHandler("b", ctrl, exec("bold"));
+ this.addKeyHandler("i", ctrl, exec("italic"));
+ this.addKeyHandler("u", ctrl, exec("underline"));
+ this.addKeyHandler("a", ctrl, exec("selectall"));
+ //this.addKeyHandler("k", ctrl, exec("createlink", ""));
+ //this.addKeyHandler("K", ctrl, exec("unlink"));
+ this.addKeyHandler("s", ctrl, function () { this.save(true); });
+
+ this.addKeyHandler("1", ctrl, exec("formatblock", "h1"));
+ this.addKeyHandler("2", ctrl, exec("formatblock", "h2"));
+ this.addKeyHandler("3", ctrl, exec("formatblock", "h3"));
+ this.addKeyHandler("4", ctrl, exec("formatblock", "h4"));
+
+ this.addKeyHandler("\\", ctrl, exec("insertunorderedlist"));
+ if(!dojo.render.html.ie){
+ this.addKeyHandler("Z", ctrl, exec("redo"));
+ }
+ },
+
+
+ // Array: events which should be connected to the underlying editing area
+ events: ["onBlur", "onFocus", "onKeyPress", "onKeyDown", "onKeyUp", "onClick"],
+
+ /**
+ * Transforms the node referenced in this.domNode into a rich text editing
+ * node. This can result in the creation and replacement with an <iframe> if
+ * designMode is used, an <object> and active-x component if inside of IE or
+ * a reguler element if contentEditable is available.
+ */
+ open: function (/*DomNode, optional*/element) {
+ // summary:
+ // Transforms the node referenced in this.domNode into a rich text editing
+ // node. This can result in the creation and replacement with an <iframe> if
+ // designMode is used, an <object> and active-x component if inside of IE or
+ // a reguler element if contentEditable is available.
+ var h = dojo.render.html;
+ dojo.event.topic.publish("dojo.widget.RichText::open", this);
+
+ if (!this.isClosed) { this.close(); }
+ this._content = "";
+ if((arguments.length == 1)&&(element["nodeName"])){ this.domNode = element; } // else unchanged
+
+ if( (this.domNode["nodeName"])&&
+ (this.domNode.nodeName.toLowerCase() == "textarea")){
+ this.textarea = this.domNode;
+ var html = dojo.string.trim(this.textarea.value);
+ if(html == ""){ html = " "; }
+ this.domNode = dojo.doc().createElement("div");
+ dojo.html.copyStyle(this.domNode, this.textarea);
+ var tmpFunc = dojo.lang.hitch(this, function(){
+ //some browsers refuse to submit display=none textarea, so
+ //move the textarea out of screen instead
+ with(this.textarea.style){
+ display = "block";
+ position = "absolute";
+ left = top = "-1000px";
+
+ if(h.ie){ //nasty IE bug: abnormal formatting if overflow is not hidden
+ this.__overflow = overflow;
+ overflow = "hidden";
+ }
+ }
+ });
+ if(h.ie){
+ setTimeout(tmpFunc, 10);
+ }else{
+ tmpFunc();
+ }
+ if(!h.safari){
+ // FIXME: VERY STRANGE safari 2.0.4 behavior here caused by
+ // moving the textarea. Often crashed the browser!!! Seems
+ // fixed on webkit nightlies.
+ dojo.html.insertBefore(this.domNode, this.textarea);
+ }
+ // this.domNode.innerHTML = html;
+
+ if(this.textarea.form){
+ dojo.event.connect(this.textarea.form, "onsubmit",
+ // FIXME: should we be calling close() here instead?
+ dojo.lang.hitch(this, function(){
+ this.textarea.value = this.getEditorContent();
+ })
+ );
+ }
+
+ // dojo plucks our original domNode from the document so we need
+ // to go back and put ourselves back in
+ var editor = this;
+ dojo.event.connect(this, "postCreate", function (){
+ dojo.html.insertAfter(editor.textarea, editor.domNode);
+ });
+ }else{
+ var html = this._preFilterContent(dojo.string.trim(this.domNode.innerHTML));
+ if(html == ""){ html = " "; }
+ }
+
+ var content = dojo.html.getContentBox(this.domNode);
+ this._oldHeight = content.height;
+ this._oldWidth = content.width;
+
+ this._firstChildContributingMargin = this._getContributingMargin(this.domNode, "top");
+ this._lastChildContributingMargin = this._getContributingMargin(this.domNode, "bottom");
+
+ this.savedContent = dojo.doc().createElement("div");
+ while (this.domNode.hasChildNodes()) {
+ this.savedContent.appendChild(this.domNode.firstChild);
+ }
+
+ this.editingArea = dojo.doc().createElement("div");
+ this.domNode.appendChild(this.editingArea);
+
+ // If we're a list item we have to put in a blank line to force the
+ // bullet to nicely align at the top of text
+ if( (this.domNode["nodeName"])&&
+ (this.domNode.nodeName == "LI")){
+ this.domNode.innerHTML = " <br>";
+ }
+
+ if(this.saveName != ""){
+ var saveTextarea = dojo.doc().getElementById("dojo.widget.RichText.savedContent");
+ if (saveTextarea.value != "") {
+ var datas = saveTextarea.value.split(this._SEPARATOR);
+ for (var i = 0; i < datas.length; i++) {
+ var data = datas[i].split(":");
+ if (data[0] == this.saveName) {
+ html = data[1];
+ datas.splice(i, 1);
+ break;
+ }
+ }
+ }
+ dojo.event.connect("before", window, "onunload", this, "_saveContent");
+ // dojo.event.connect(window, "onunload", this, "_saveContent");
+ }
+
+ if(h.ie70 && this.useActiveX){
+ dojo.debug("activeX in ie70 is not currently supported, useActiveX is ignored for now.");
+ this.useActiveX = false;
+ }
+ // Safari's selections go all out of whack if we do it inline,
+ // so for now IE is our only hero
+ //if (typeof document.body.contentEditable != "undefined") {
+ if (this.useActiveX && h.ie) { // active-x
+ var self = this;
+ //if call _drawObject directly here, textarea replacement
+ //won't work: no content is shown. However, add a delay
+ //can workaround this. No clue why.
+ setTimeout(function(){self._drawObject(html);}, 0);
+ } else if (h.ie) { // contentEditable, easy
+ this.iframe = dojo.doc().createElement( 'iframe' ) ;
+ this.iframe.src = 'javascript:void(0)';
+ this.editorObject = this.iframe;
+ with(this.iframe.style){
+ border = '0';
+ width = "100%";
+ }
+ this.iframe.frameBorder = 0;
+ this.editingArea.appendChild(this.iframe)
+ this.window = this.iframe.contentWindow;
+ this.document = this.window.document;
+ this.document.open();
+ this.document.write("<html><head></head><body style='margin: 0; padding: 0;border: 0; overflow: hidden;'><div></div></body></html>");
+ this.document.close();
+ this.editNode = this.document.body.firstChild;//document.createElement("div");
+ this.editNode.contentEditable = true;
+ with (this.iframe.style) {
+ if(h.ie70){
+ if(this.height){
+ height = this.height;
+ }
+ if(this.minHeight){
+ minHeight = this.minHeight;
+ }
+ }else{
+ height = this.height ? this.height : this.minHeight;
+ }
+ }
+
+ // FIXME: setting contentEditable on switches this element to
+ // IE's hasLayout mode, triggering weird margin collapsing
+ // behavior. It's particularly bad if the element you're editing
+ // contains childnodes that don't have margin: defined in local
+ // css rules. It would be nice if it was possible to hack around
+ // this. Sadly _firstChildContributingMargin and
+ // _lastChildContributingMargin don't work on IE unless all
+ // elements have margins set in CSS :-(
+
+ //if the normal way fails, we try the hard way to get the list
+ if(!this._cacheLocalBlockFormatNames()){
+ //in the array below, ul can not come directly after ol, otherwise the queryCommandValue returns Normal for it
+ var formats = ['p', 'pre', 'address', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ol', 'div', 'ul'];
+ var localhtml = "";
+ for(var i in formats){
+ if(formats[i].charAt(1) != 'l'){
+ localhtml += "<"+formats[i]+"><span>content</span></"+formats[i]+">";
+ }else{
+ localhtml += "<"+formats[i]+"><li>content</li></"+formats[i]+">";
+ }
+ }
+ //queryCommandValue returns empty if we hide editNode, so move it out of screen temporary
+ with(this.editNode.style){
+ position = "absolute";
+ left = "-2000px";
+ top = "-2000px";
+ }
+ this.editNode.innerHTML = localhtml;
+ var node = this.editNode.firstChild;
+ while(node){
+ dojo.withGlobal(this.window, "selectElement", dojo.html.selection, [node.firstChild]);
+ var nativename = node.tagName.toLowerCase();
+ this._local2NativeFormatNames[nativename] = this.queryCommandValue("formatblock");
+// dojo.debug([nativename,this._local2NativeFormatNames[nativename]]);
+ this._native2LocalFormatNames[this._local2NativeFormatNames[nativename]] = nativename;
+ node = node.nextSibling;
+ }
+ with(this.editNode.style){
+ position = "";
+ left = "";
+ top = "";
+ }
+ }
+
+ this.editNode.innerHTML = html;
+ if(this.height){ this.editNode.style.overflowY="scroll"; }
+
+ dojo.lang.forEach(this.events, function(e){
+ dojo.event.connect(this.editNode, e.toLowerCase(), this, e);
+ }, this);
+
+ this.onLoad();
+ } else { // designMode in iframe
+ this._drawIframe(html);
+ this.editorObject = this.iframe;
+ }
+
+ // TODO: this is a guess at the default line-height, kinda works
+ if (this.domNode.nodeName == "LI") { this.domNode.lastChild.style.marginTop = "-1.2em"; }
+ dojo.html.addClass(this.domNode, "RichTextEditable");
+
+ this.isClosed = false;
+ },
+
+ _hasCollapseableMargin: function(/*DomNode*/element, /*String*/side) {
+ // summary:
+ // check if an element has padding or borders on the given side
+ // which would prevent it from collapsing margins
+ if (dojo.html.getPixelValue(element,
+ 'border-'+side+'-width',
+ false)) {
+ return false;
+ } else if (dojo.html.getPixelValue(element,
+ 'padding-'+side,
+ false)) {
+ return false;
+ } else {
+ return true;
+ }
+ },
+
+ _getContributingMargin: function(/*DomNode*/element, /*String*/topOrBottom) {
+ // summary:
+ // calculate how much margin this element and its first or last
+ // child are contributing to the total margin between this element
+ // and the adjacent node. CSS border collapsing makes this
+ // necessary.
+
+ if (topOrBottom == "top") {
+ var siblingAttr = "previousSibling";
+ var childSiblingAttr = "nextSibling";
+ var childAttr = "firstChild";
+ var marginProp = "margin-top";
+ var siblingMarginProp = "margin-bottom";
+ } else {
+ var siblingAttr = "nextSibling";
+ var childSiblingAttr = "previousSibling";
+ var childAttr = "lastChild";
+ var marginProp = "margin-bottom";
+ var siblingMarginProp = "margin-top";
+ }
+
+ var elementMargin = dojo.html.getPixelValue(element, marginProp, false);
+
+ function isSignificantNode(element) {
+ // see if an node is significant in the current context
+ // for calulating margins
+ return !(element.nodeType==3 && dojo.string.isBlank(element.data))
+ && dojo.html.getStyle(element, "display") != "none"
+ && !dojo.html.isPositionAbsolute(element);
+ }
+
+ // walk throuh first/last children to find total collapsed margin size
+ var childMargin = 0;
+ var child = element[childAttr];
+ while (child) {
+ // skip over insignificant elements (whitespace, etc)
+ while ((!isSignificantNode(child)) && child[childSiblingAttr]) {
+ child = child[childSiblingAttr];
+ }
+
+ childMargin = Math.max(childMargin, dojo.html.getPixelValue(child, marginProp, false));
+ // stop if we hit a bordered/padded element
+ if (!this._hasCollapseableMargin(child, topOrBottom)) break;
+ child = child[childAttr];
+ }
+
+ // if this element has a border, return full child margin immediately
+ // as there won't be any margin collapsing
+ if (!this._hasCollapseableMargin(element, topOrBottom)){ return parseInt(childMargin); }
+
+ // find margin supplied by nearest sibling
+ var contextMargin = 0;
+ var sibling = element[siblingAttr];
+ while (sibling) {
+ if (isSignificantNode(sibling)) {
+ contextMargin = dojo.html.getPixelValue(sibling,
+ siblingMarginProp,
+ false);
+ break;
+ }
+ sibling = sibling[siblingAttr];
+ }
+ if (!sibling) { // no sibling, look at parent's margin instead
+ contextMargin = dojo.html.getPixelValue(element.parentNode,
+ marginProp, false);
+ }
+
+ if (childMargin > elementMargin) {
+ return parseInt(Math.max((childMargin-elementMargin)-contextMargin, 0));
+ } else {
+ return 0;
+ }
+
+ },
+
+ _drawIframe: function (/*String*/html) {
+ // summary:
+ // Draws an iFrame using the existing one if one exists.
+ // Used by Mozilla, Safari, and Opera
+
+ // detect firefox < 1.5, which has some iframe loading issues
+ var oldMoz = Boolean(dojo.render.html.moz && (
+ typeof window.XML == 'undefined'))
+
+ if (!this.iframe) {
+ var currentDomain = (new dojo.uri.Uri(dojo.doc().location)).host;
+ this.iframe = dojo.doc().createElement("iframe");
+ with (this.iframe) {
+ scrolling = this.height ? "auto" : "no";
+ style.border = "none";
+ style.lineHeight = "0"; // squash line height
+ style.verticalAlign = "bottom";
+ }
+ }
+ // opera likes this to be outside the with block
+ this.iframe.src = dojo.uri.dojoUri("src/widget/templates/richtextframe.html") + "#" + ((dojo.doc().domain != currentDomain) ? dojo.doc().domain : "");
+ this.iframe.width = this.inheritWidth ? this._oldWidth : "100%";
+ if (this.height) {
+ this.iframe.style.height = this.height;
+ } else {
+ var height = this._oldHeight;
+ if (this._hasCollapseableMargin(this.domNode, 'top')) {
+ height += this._firstChildContributingMargin;
+ }
+ if (this._hasCollapseableMargin(this.domNode, 'bottom')) {
+ height += this._lastChildContributingMargin;
+ }
+ this.iframe.height = height;
+ }
+
+ var tmpContent = dojo.doc().createElement('div');
+ tmpContent.innerHTML = html;
+
+ // make relative image urls absolute
+ if (this.relativeImageUrls) {
+ var imgs = tmpContent.getElementsByTagName('img');
+ for (var i=0; i<imgs.length; i++) {
+ imgs[i].src = (new dojo.uri.Uri(dojo.global().location, imgs[i].src)).toString();
+ }
+ html = tmpContent.innerHTML;
+ }
+
+ // fix margins on tmpContent
+ var firstChild = dojo.html.firstElement(tmpContent);
+ var lastChild = dojo.html.lastElement(tmpContent);
+ if(firstChild){
+ firstChild.style.marginTop = this._firstChildContributingMargin+"px";
+ }
+ if(lastChild){
+ lastChild.style.marginBottom = this._lastChildContributingMargin+"px";
+ }
+
+ // show existing content behind iframe for now
+ tmpContent.style.position = "absolute";
+ this.editingArea.appendChild(tmpContent);
+ this.editingArea.appendChild(this.iframe);
+
+ var _iframeInitialized = false;
+
+ // now we wait for onload. Janky hack!
+ var ifrFunc = dojo.lang.hitch(this, function(){
+ if(!_iframeInitialized){
+ _iframeInitialized = true;
+ }else{ return; }
+ if(!this.editNode){
+ if(this.iframe.contentWindow){
+ this.window = this.iframe.contentWindow;
+ }else{
+ // for opera
+ this.window = this.iframe.contentDocument.window;
+ }
+ if(dojo.render.html.moz){
+ this.document = this.iframe.contentWindow.document
+ }else{
+ this.document = this.iframe.contentDocument;
+ }
+
+ // curry the getStyle function
+ var getStyle = (function (domNode) { return function (style) {
+ return dojo.html.getStyle(domNode, style);
+ }; })(this.domNode);
+
+ var font =
+ getStyle('font-weight') + " " +
+ getStyle('font-size') + " " +
+ getStyle('font-family');
+
+ // line height is tricky - applying a units value will mess things up.
+ // if we can't get a non-units value, bail out.
+ var lineHeight = "1.0";
+ var lineHeightStyle = dojo.html.getUnitValue(this.domNode, 'line-height');
+ if (lineHeightStyle.value && lineHeightStyle.units=="") {
+ lineHeight = lineHeightStyle.value;
+ }
+
+ dojo.html.insertCssText(
+ ' body,html { background: transparent; padding: 0; margin: 0; }\n' +
+ // TODO: left positioning will case contents to disappear out of view
+ // if it gets too wide for the visible area
+ ' body { top: 0; left: 0; right: 0;' +
+ (((this.height)||(dojo.render.html.opera)) ? '' : ' position: fixed; ') +
+ ' font: ' + font + ';\n' +
+ ' min-height: ' + this.minHeight + '; \n' +
+ ' line-height: ' + lineHeight + '} \n' +
+ ' p { margin: 1em 0 !important; }\n' +
+ ' body > *:first-child { padding-top: 0 !important; margin-top: ' + this._firstChildContributingMargin + 'px !important; }\n' + // FIXME: test firstChild nodeType
+ ' body > *:last-child { padding-bottom: 0 !important; margin-bottom: ' + this._lastChildContributingMargin + 'px !important; }\n' +
+ ' li > ul:-moz-first-node, li > ol:-moz-first-node { padding-top: 1.2em; }\n' +
+ ' li { min-height: 1.2em; }\n' +
+ //' p,ul,li { padding-top: 0; padding-bottom: 0; margin-top:0; margin-bottom: 0; }\n' +
+ '', this.document);
+
+ tmpContent.parentNode.removeChild(tmpContent);
+ this.document.body.innerHTML = html;
+ if(oldMoz){
+ this.document.designMode = "on";
+ }
+ this.onLoad();
+ }else{
+ tmpContent.parentNode.removeChild(tmpContent);
+ this.editNode.innerHTML = html;
+ this.onDisplayChanged();
+ }
+ });
+
+ if(this.editNode){
+ ifrFunc(); // iframe already exists, just set content
+ }else if(dojo.render.html.moz){
+ // FIXME: if we put this on a delay, we get a height of 20px.
+ // Otherwise we get the correctly specified minHeight value.
+ this.iframe.onload = function(){
+ setTimeout(ifrFunc, 250);
+ }
+ }else{ // new mozillas, opera, safari
+ this.iframe.onload = ifrFunc;
+ }
+ },
+
+ _applyEditingAreaStyleSheets: function(){
+ // summary:
+ // apply the specified css files in styleSheets
+ var files = [];
+ if(this.styleSheets){
+ files = this.styleSheets.split(';');
+ }
+
+ //empty this.editingAreaStyleSheets here, as it will be filled in addStyleSheet
+ files = files.concat(this.editingAreaStyleSheets);
+ this.editingAreaStyleSheets = [];
+
+ if(files.length>0){
+ for(var i=0;i<files.length;i++){
+ var url = files[i];
+ if(url){
+ this.addStyleSheet(new dojo.uri.Uri(dojo.global().location, url));
+ }
+ }
+ }
+ },
+
+ addStyleSheet: function(/*dojo.uri.Uri*/uri) {
+ // summary:
+ // add an external stylesheet for the editing area
+ // uri: a dojo.uri.Uri pointing to the url of the external css file
+ var url=uri.toString();
+ if(dojo.lang.find(this.editingAreaStyleSheets, url) > -1){
+ dojo.debug("dojo.widget.RichText.addStyleSheet: Style sheet "+url+" is already applied to the editing area!");
+ return;
+ }
+ this.editingAreaStyleSheets.push(url);
+ if(this.document.createStyleSheet){ //IE
+ this.document.createStyleSheet(url);
+ }else{ //other browser
+ var head = this.document.getElementsByTagName("head")[0];
+ var stylesheet = this.document.createElement("link");
+ with(stylesheet){
+ rel="stylesheet";
+ type="text/css";
+ href=url;
+ }
+ head.appendChild(stylesheet);
+ }
+ },
+
+ removeStyleSheet: function (/*dojo.uri.Uri*/uri) {
+ // summary:
+ // remove an external stylesheet for the editing area
+ var url=uri.toString();
+ var index = dojo.lang.find(this.editingAreaStyleSheets, url);
+ if(index == -1){
+ dojo.debug("dojo.widget.RichText.removeStyleSheet: Style sheet "+url+" is not applied to the editing area so it can not be removed!");
+ return;
+ }
+ delete this.editingAreaStyleSheets[index];
+
+ var links = this.document.getElementsByTagName("link");
+ for(var i=0;i<links.length;i++){
+ if(links[i].href == url){
+ if(dojo.render.html.ie){//we need to empty the href first, to get IE to remove the rendered styles
+ links[i].href="";
+ }
+ dojo.html.removeNode(links[i]);
+ break;
+ }
+ }
+ },
+
+ _drawObject: function (/*String*/html) {
+ // summary:
+ // Draws an active x object, used by IE
+ this.object = dojo.html.createExternalElement(dojo.doc(), "object");
+
+ with (this.object) {
+ classid = "clsid:2D360201-FFF5-11D1-8D03-00A0C959BC0A";
+ width = this.inheritWidth ? this._oldWidth : "100%";
+ style.height = this.height ? this.height : (this._oldHeight+"px");
+ Scrollbars = this.height ? true : false;
+ Appearance = this._activeX.appearance.flat;
+ }
+ this.editorObject = this.object;
+ this.editingArea.appendChild(this.object);
+
+ this.object.attachEvent("DocumentComplete", dojo.lang.hitch(this, "onLoad"));
+ //DisplayChanged is fired too often even no change is made, so we ignore it
+ //and call onDisplayChanged manually in execCommand instead
+// this.object.attachEvent("DisplayChanged", dojo.lang.hitch(this, "onDisplayChanged"));
+
+ dojo.lang.forEach(this.events, function(e){
+ this.object.attachEvent(e.toLowerCase(), dojo.lang.hitch(this, e));
+ }, this);
+
+ this.object.DocumentHTML = '<!doctype HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">' +
+ '<html><title></title>' +
+ '<style type="text/css">' +
+ ' body,html { padding: 0; margin: 0; }' + //font: ' + font + '; }' +
+ (this.height ? '' : ' body, { overflow: hidden; }') +
+ '</style>' +
+ //'<base href="' + dojo.global().location + '">' +
+ '<body><div>' + html + '<div></body></html>';
+
+ this._cacheLocalBlockFormatNames();
+ },
+
+ //static cache variables shared among all instance of this class
+ _local2NativeFormatNames: {},
+ _native2LocalFormatNames: {},
+ //in IE, names for blockformat is locale dependent, so we cache the values here
+ //we use activeX to obtain the list, if success or the names are already cached,
+ //return true
+ _cacheLocalBlockFormatNames: function(){
+ if(!this._native2LocalFormatNames['p']){
+ var obj = this.object;
+ if(!obj){
+ //create obj temporarily
+ try{
+ obj = dojo.html.createExternalElement(dojo.doc(), "object");
+ obj.classid = "clsid:2D360201-FFF5-11D1-8D03-00A0C959BC0A";
+ dojo.body().appendChild(obj);
+ obj.DocumentHTML = "<html><head></head><body></body></html>";
+ }catch(e){
+ return false;
+ }
+ }
+ var oNamesParm = new ActiveXObject("DEGetBlockFmtNamesParam.DEGetBlockFmtNamesParam");
+ obj.ExecCommand(this._activeX.command['getblockformatnames'], 0, oNamesParm);
+ var vbNamesArray = new VBArray(oNamesParm.Names);
+ var localFormats = vbNamesArray.toArray();
+ var nativeFormats = ['p', 'pre', 'address', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ol', 'ul', '', '', '','','div'];
+ for(var i=0;i<nativeFormats.length;++i){
+ if(nativeFormats[i].length>0){
+ this._local2NativeFormatNames[localFormats[i]] = nativeFormats[i];
+ this._native2LocalFormatNames[nativeFormats[i]] = localFormats[i];
+ }
+ }
+ if(!this.object){
+ //delete the temporary obj
+ dojo.body().removeChild(obj);
+ }
+ }
+ return true;
+ },
+ /* Event handlers
+ *****************/
+
+ _isResized: function(){ return false; },
+
+ onLoad: function(e){
+ this.isLoaded = true;
+ if (this.object){
+ this.document = this.object.DOM;
+ this.window = this.document.parentWindow;
+ this.editNode = this.document.body.firstChild;
+ this.editingArea.style.height = this.height ? this.height : this.minHeight;
+ this.connect(this, "onDisplayChanged", "_updateHeight");
+ //pretend the object as an iframe, so that the context menu for the
+ //editor can be placed correctly when shown
+ this.window._frameElement = this.object;
+ }else if (this.iframe && !dojo.render.html.ie){
+ this.editNode = this.document.body;
+ this.connect(this, "onDisplayChanged", "_updateHeight");
+
+ try { // sanity check for Mozilla
+ this.document.execCommand("useCSS", false, true); // old moz call
+ this.document.execCommand("styleWithCSS", false, false); // new moz call
+ //this.document.execCommand("insertBrOnReturn", false, false); // new moz call
+ }catch(e2){ }
+
+ if (dojo.render.html.safari) {
+ /*
+ this.iframe.style.visiblity = "visible";
+ this.iframe.style.border = "1px solid black";
+ this.editNode.style.visiblity = "visible";
+ this.editNode.style.border = "1px solid black";
+ */
+ // this.onDisplayChanged();
+ this.connect(this.editNode, "onblur", "onBlur");
+ this.connect(this.editNode, "onfocus", "onFocus");
+ this.connect(this.editNode, "onclick", "onFocus");
+
+ this.interval = setInterval(dojo.lang.hitch(this, "onDisplayChanged"), 750);
+ // dojo.raise("onload");
+ // dojo.debug(this.editNode.parentNode.parentNode.parentNode.nodeName);
+ } else if (dojo.render.html.mozilla || dojo.render.html.opera) {
+
+ // We need to unhook the blur event listener on close as we
+ // can encounter a garunteed crash in FF if another event is
+ // also fired
+ var doc = this.document;
+ var blurfp = dojo.event.browser.addListener(this.document, "blur", dojo.lang.hitch(this, "onBlur"));
+ var unBlur = { unBlur: function(e){
+ dojo.event.browser.removeListener(doc, "blur", blurfp);
+ } };
+ dojo.event.connect("before", this, "close", unBlur, "unBlur");
+ dojo.event.browser.addListener(this.document, "focus", dojo.lang.hitch(this, "onFocus"));
+
+ var addListener = dojo.event.browser.addListener;
+ addListener(this.document, "keypress", dojo.lang.hitch(this, "onKeyPress"));
+ addListener(this.document, "keydown", dojo.lang.hitch(this, "onKeyDown"));
+ addListener(this.document, "keyup", dojo.lang.hitch(this, "onKeyUp"));
+ addListener(this.document, "click", dojo.lang.hitch(this, "onClick"));
+ }
+ // FIXME: when scrollbars appear/disappear this needs to be fired
+ }else if(dojo.render.html.ie){
+ // IE contentEditable
+ this.connect(this, "onDisplayChanged", "_updateHeight");
+ this.editNode.style.zoom = 1.0;
+ }
+
+ this._applyEditingAreaStyleSheets();
+
+ if(this.focusOnLoad){
+ this.focus();
+ }
+ this.onDisplayChanged(e);
+ },
+
+ /** Fired on keydown */
+ onKeyDown: function(e){
+ if((!e)&&(this.object)){
+ e = dojo.event.browser.fixEvent(this.window.event);
+ }
+ // dojo.debug("onkeydown:", e.keyCode);
+ // we need this event at the moment to get the events from control keys
+ // such as the backspace. It might be possible to add this to Dojo, so that
+ // keyPress events can be emulated by the keyDown and keyUp detection.
+ if((dojo.render.html.ie)&&(e.keyCode == e.KEY_TAB)){
+ e.preventDefault();
+ e.stopPropagation();
+ // FIXME: this is a poor-man's indent/outdent. It would be
+ // better if it added 4 " " chars in an undoable way.
+ // Unfortuantly pasteHTML does not prove to be undoable
+ this.execCommand((e.shiftKey ? "outdent" : "indent"));
+ }else if(dojo.render.html.ie){
+ if((65 <= e.keyCode)&&(e.keyCode <= 90)){
+ e.charCode = e.keyCode;
+ this.onKeyPress(e);
+ }
+ // dojo.debug(e.ctrlKey);
+ // dojo.debug(e.keyCode);
+ // dojo.debug(e.charCode);
+ // this.onKeyPress(e);
+ }
+ },
+
+ /** Fired on keyup */
+ onKeyUp: function(e){
+ return;
+ },
+
+ KEY_CTRL: 1,
+
+ /** Fired on keypress. */
+ onKeyPress: function(e){
+ if((!e)&&(this.object)){
+ e = dojo.event.browser.fixEvent(this.window.event);
+ }
+ // handle the various key events
+
+ var character = e.charCode > 0 ? String.fromCharCode(e.charCode) : null;
+ var code = e.keyCode;
+
+ var modifiers = e.ctrlKey ? this.KEY_CTRL : 0;
+
+ if (this._keyHandlers[character]) {
+ // dojo.debug("char:", character);
+ var handlers = this._keyHandlers[character], i = 0, handler;
+ while (handler = handlers[i++]) {
+ if (modifiers == handler.modifiers) {
+ handler.handler.call(this);
+ e.preventDefault();
+ break;
+ }
+ }
+ }
+
+ /*
+ // define some key combos
+ if (e.ctrlKey || e.metaKey) { // modifier pressed
+ switch (character) {
+ case "b": this.execCommand("bold"); break;
+ case "i": this.execCommand("italic"); break;
+ case "u": this.execCommand("underline"); break;
+ //case "a": this.execCommand("selectall"); break;
+ //case "k": this.execCommand("createlink", ""); break;
+ //case "K": this.execCommand("unlink"); break;
+ case "Z": this.execCommand("redo"); break;
+ case "s": this.close(true); break; // saves
+
+ case "1": this.execCommand("formatblock", "h1"); break;
+ case "2": this.execCommand("formatblock", "h2"); break;
+ case "3": this.execCommand("formatblock", "h3"); break;
+ case "4": this.execCommand("formatblock", "h4"); break;
+
+ case "\\": this.execCommand("insertunorderedlist"); break;
+
+ default: switch (code) {
+ case e.KEY_LEFT_ARROW:
+ case e.KEY_RIGHT_ARROW:
+ //break; // preventDefault stops the browser
+ // going through its history
+ default:
+ preventDefault = false; break; // didn't handle here
+ }
+ }
+ } else {
+ switch (code) {
+ case e.KEY_TAB:
+ // commenting out bcs it's crashing FF
+ // this.execCommand(e.shiftKey ? "unindent" : "indent");
+ // break;
+ default:
+ preventDefault = false; break; // didn't handle here
+ }
+ }
+
+ if (preventDefault) { e.preventDefault(); }
+ */
+
+ // function call after the character has been inserted
+ dojo.lang.setTimeout(this, this.onKeyPressed, 1, e);
+ },
+
+ addKeyHandler: function (key, modifiers, handler) {
+ if (!(this._keyHandlers[key] instanceof Array)) { this._keyHandlers[key] = []; }
+ this._keyHandlers[key].push({
+ modifiers: modifiers || 0,
+ handler: handler
+ });
+ },
+
+
+
+ /**
+ * Fired after a keypress event has occured and it's action taken. This
+ * is useful if action needs to be taken after text operations have
+ * finished
+ */
+ onKeyPressed: function (e) {
+ // Mozilla adds a single <p> with an embedded <br> when you hit enter once:
+ // <p><br>\n</p>
+ // when you hit enter again it adds another <br> inside your enter
+ // <p><br>\n<br>\n</p>
+ // and if you hit enter again it splits the <br>s over 2 <p>s
+ // <p><br>\n</p>\n<p><br>\n</p>
+ // now this assumes that <p>s have double the line-height of <br>s to work
+ // and so we need to remove the <p>s to ensure the position of the cursor
+ // changes from the users perspective when they hit enter, as the second two
+ // html snippets render the same when margins are set to 0.
+
+ // TODO: doesn't really work; is this really needed?
+ //if (dojo.render.html.moz) {
+ // for (var i = 0; i < this.document.getElementsByTagName("p").length; i++) {
+ // var p = this.document.getElementsByTagName("p")[i];
+ // if (p.innerHTML.match(/^<br>\s$/m)) {
+ // while (p.hasChildNodes()) { p.parentNode.insertBefore(p.firstChild, p); }
+ // p.parentNode.removeChild(p);
+ // }
+ // }
+ //}
+ this.onDisplayChanged(/*e*/); // can't pass in e
+ },
+
+ onClick: function(e){ this.onDisplayChanged(e); },
+ onBlur: function(e){ },
+ _initialFocus: true,
+ onFocus: function(e){
+ if( (dojo.render.html.mozilla)&&(this._initialFocus) ){
+ this._initialFocus = false;
+ if(dojo.string.trim(this.editNode.innerHTML) == " "){
+ this.execCommand("selectall");
+ this.window.getSelection().collapseToStart();
+ }
+ }
+ },
+
+ blur: function () {
+ if(this.iframe) { this.window.blur(); }
+ else if(this.object) { this.document.body.blur(); }
+ else if(this.editNode) { this.editNode.blur(); }
+ },
+
+ focus: function () {
+ if(this.iframe && !dojo.render.html.ie) { this.window.focus(); }
+ else if(this.object) { this.document.focus(); }
+ // editNode may be hidden in display:none div, lets just punt in this case
+ else if(this.editNode && dojo.lang.isFunction(this.editNode.focus)) { this.editNode.focus(); }
+ },
+
+ /** this event will be fired everytime the display context changes and the
+ result needs to be reflected in the UI */
+ onDisplayChanged: function (e){ },
+
+
+ /* Formatting commands
+ **********************/
+
+ /** IE's Active X codes: see http://www.computerbytesman.com/js/activex/dhtmledit.htm */
+ _activeX: {
+ command: {
+ bold: 5000,
+ italic: 5023,
+ underline: 5048,
+
+ justifycenter: 5024,
+ justifyleft: 5025,
+ justifyright: 5026,
+
+ cut: 5003,
+ copy: 5002,
+ paste: 5032,
+ "delete": 5004,
+
+ undo: 5049,
+ redo: 5033,
+
+ removeformat: 5034,
+ selectall: 5035,
+ unlink: 5050,
+
+ indent: 5018,
+ outdent: 5031,
+
+ insertorderedlist: 5030,
+ insertunorderedlist: 5051,
+
+ // table commands
+ inserttable: 5022,
+ insertcell: 5019,
+ insertcol: 5020,
+ insertrow: 5021,
+ deletecells: 5005,
+ deletecols: 5006,
+ deleterows: 5007,
+ mergecells: 5029,
+ splitcell: 5047,
+
+ // the command need mapping, they don't translate directly
+ // to the contentEditable commands
+ setblockformat: 5043,
+ getblockformat: 5011,
+ getblockformatnames: 5012,
+ setfontname: 5044,
+ getfontname: 5013,
+ setfontsize: 5045,
+ getfontsize: 5014,
+ setbackcolor: 5042,
+ getbackcolor: 5010,
+ setforecolor: 5046,
+ getforecolor: 5015,
+
+ findtext: 5008,
+ font: 5009,
+ hyperlink: 5016,
+ image: 5017,
+
+ lockelement: 5027,
+ makeabsolute: 5028,
+ sendbackward: 5036,
+ bringforward: 5037,
+ sendbelowtext: 5038,
+ bringabovetext: 5039,
+ sendtoback: 5040,
+ bringtofront: 5041,
+
+ properties: 5052
+ },
+
+ ui: {
+ "default": 0,
+ prompt: 1,
+ noprompt: 2
+ },
+
+ status: {
+ notsupported: 0,
+ disabled: 1,
+ enabled: 3,
+ latched: 7,
+ ninched: 11
+ },
+
+ appearance: {
+ flat: 0,
+ inset: 1
+ },
+
+ state: {
+ unchecked: 0,
+ checked: 1,
+ gray: 2
+ }
+ },
+
+ /**
+ * Used as the advice function by dojo.event.connect to map our
+ * normalized set of commands to those supported by the target
+ * browser
+ *
+ * @param arugments The arguments Array, containing at least one
+ * item, the command and an optional second item,
+ * an argument.
+ */
+ _normalizeCommand: function (joinObject){
+ var drh = dojo.render.html;
+
+ var command = joinObject.args[0].toLowerCase();
+ if(command == "formatblock"){
+ if(drh.safari){ command = "heading"; }
+ if(this.object && joinObject.args[1]){ //IE activeX mode
+ joinObject.args[1] = this._native2LocalFormatNames[joinObject.args[1]];
+ }
+ else if(drh.ie){ joinObject.args[1] = "<"+joinObject.args[1]+">"; }
+ }else if(this.object){
+ switch(command){
+ case "createlink":
+ command = "hyperlink";
+ break;
+ case "insertimage":
+ command = "image";
+ break;
+ }
+ }else if(command == "hilitecolor" && !drh.mozilla){
+ command = "backcolor";
+ }
+
+ joinObject.args[0] = command;
+
+ if (joinObject.args.length > 1) { // a command was specified
+ var argument = joinObject.args[1];
+ if (command == "heading") { throw new Error("unimplemented"); }
+ joinObject.args[1] = argument;
+ }
+
+ return joinObject.proceed();
+ },
+
+ /**
+ * Tests whether a command is supported by the host. Clients SHOULD check
+ * whether a command is supported before attempting to use it, behaviour
+ * for unsupported commands is undefined.
+ *
+ * @param command The command to test for
+ * @return true if the command is supported, false otherwise
+ */
+ queryCommandAvailable: function (command) {
+ var ie = 1;
+ var mozilla = 1 << 1;
+ var safari = 1 << 2;
+ var opera = 1 << 3;
+ var safari420 = 1 << 4;
+
+ var gt420 = false;
+ if(dojo.render.html.safari){
+ var tmp = dojo.render.html.UA.split("AppleWebKit/")[1];
+ var ver = parseFloat(tmp.split(" ")[0]);
+ if(ver >= 420){ gt420 = true; }
+ }
+
+ function isSupportedBy (browsers) {
+ return {
+ ie: Boolean(browsers & ie),
+ mozilla: Boolean(browsers & mozilla),
+ safari: Boolean(browsers & safari),
+ safari420: Boolean(browsers & safari420),
+ opera: Boolean(browsers & opera)
+ }
+ }
+
+ var supportedBy = null;
+
+ switch (command.toLowerCase()) {
+ case "bold": case "italic": case "underline":
+ case "subscript": case "superscript":
+ case "fontname": case "fontsize":
+ case "forecolor": case "hilitecolor":
+ case "justifycenter": case "justifyfull": case "justifyleft":
+ case "justifyright": case "delete": case "selectall":
+ supportedBy = isSupportedBy(mozilla | ie | safari | opera);
+ break;
+
+ case "createlink": case "unlink": case "removeformat":
+ case "inserthorizontalrule": case "insertimage":
+ case "insertorderedlist": case "insertunorderedlist":
+ case "indent": case "outdent": case "formatblock":
+ case "inserthtml": case "undo": case "redo": case "strikethrough":
+ supportedBy = isSupportedBy(mozilla | ie | opera | safari420);
+ break;
+
+ case "blockdirltr": case "blockdirrtl":
+ case "dirltr": case "dirrtl":
+ case "inlinedirltr": case "inlinedirrtl":
+ supportedBy = isSupportedBy(ie);
+ break;
+ case "cut": case "copy": case "paste":
+ supportedBy = isSupportedBy( ie | mozilla | safari420);
+ break;
+
+ case "inserttable":
+ supportedBy = isSupportedBy(mozilla | (this.object ? ie : 0));
+ break;
+
+ case "insertcell": case "insertcol": case "insertrow":
+ case "deletecells": case "deletecols": case "deleterows":
+ case "mergecells": case "splitcell":
+ supportedBy = isSupportedBy(this.object ? ie : 0);
+ break;
+
+ default: return false;
+ }
+
+ return (dojo.render.html.ie && supportedBy.ie) ||
+ (dojo.render.html.mozilla && supportedBy.mozilla) ||
+ (dojo.render.html.safari && supportedBy.safari) ||
+ (gt420 && supportedBy.safari420) ||
+ (dojo.render.html.opera && supportedBy.opera);
+ },
+
+ /**
+ * Executes a command in the Rich Text area
+ *
+ * @param command The command to execute
+ * @param argument An optional argument to the command
+ */
+ execCommand: function (command, argument){
+ var returnValue;
+
+ //focus() is required for IE (none-activeX mode) to work
+ //In addition, focus() makes sure after the execution of
+ //the command, the editor receives the focus as expected
+ this.focus();
+
+ if(this.object){
+ switch (command) {
+ case "hilitecolor":
+ command = "setbackcolor";
+ break;
+ case "forecolor":
+ case "backcolor":
+ case "fontsize":
+ case "fontname":
+ command = "set" + command;
+ break;
+ case "formatblock":
+ command = "setblockformat";
+ }
+
+ if(command == "strikethrough"){
+ command = "inserthtml";
+ var range = this.document.selection.createRange();
+ if(!range.htmlText){
+ return;
+ }
+ argument=range.htmlText.strike();
+ }else if(command == "inserthorizontalrule"){
+ command = "inserthtml";
+ argument="<hr>";
+ }
+
+ if(command == "inserthtml"){
+ var range = this.document.selection.createRange();
+ if(this.document.selection.type.toUpperCase() == "CONTROL"){
+ //if selection is controlrange, no pasteHTML is available,
+ //we replace the outerHTML directly
+ for(var i=0;i<range.length;i++){
+ range.item(i).outerHTML = argument;
+ }
+//
+// range.collapse(true);
+// range.select();
+ }else{
+ range.select();
+ range.pasteHTML(argument);
+ range.collapse(true);
+ }
+ returnValue = true;
+ }else if(arguments.length == 1){
+ returnValue = this.object.ExecCommand(this._activeX.command[command],
+ this._activeX.ui.noprompt);
+ }else{
+ returnValue = this.object.ExecCommand(this._activeX.command[command],
+ this._activeX.ui.noprompt, argument);
+ }
+ }else if(command == "inserthtml"){
+ // on IE, we can use the pasteHTML method of the textRange object
+ // to get an undo-able innerHTML modification
+ if(dojo.render.html.ie){
+ dojo.debug("inserthtml breaks the undo stack when not using the ActiveX version of the control!");
+ var insertRange = this.document.selection.createRange();
+ insertRange.select();
+ insertRange.pasteHTML(argument);
+ insertRange.collapse(true);
+ return true;
+ }else{
+ return this.document.execCommand(command, false, argument);
+ }
+ /* */
+ // fix up unlink in Mozilla to unlink the link and not just the selection
+ }else if((command == "unlink")&&
+ (this.queryCommandEnabled("unlink"))&&
+ (dojo.render.html.mozilla)){
+ // grab selection
+ // Mozilla gets upset if we just store the range so we have to
+ // get the basic properties and recreate to save the selection
+ var selection = this.window.getSelection();
+ var selectionRange = selection.getRangeAt(0);
+ var selectionStartContainer = selectionRange.startContainer;
+ var selectionStartOffset = selectionRange.startOffset;
+ var selectionEndContainer = selectionRange.endContainer;
+ var selectionEndOffset = selectionRange.endOffset;
+
+ // select our link and unlink
+ var a = dojo.withGlobal(this.window, "getAncestorElement", dojo.html.selection, ['a']);
+ dojo.withGlobal(this.window, "selectElement", dojo.html.selection, [a]);
+
+ returnValue = this.document.execCommand("unlink", false, null);
+
+ // restore original selection
+ var selectionRange = this.document.createRange();
+ selectionRange.setStart(selectionStartContainer, selectionStartOffset);
+ selectionRange.setEnd(selectionEndContainer, selectionEndOffset);
+ selection.removeAllRanges();
+ selection.addRange(selectionRange);
+
+ return returnValue;
+ }else if((command == "hilitecolor")&&(dojo.render.html.mozilla)){
+ // mozilla doesn't support hilitecolor properly when useCSS is
+ // set to false (bugzilla #279330)
+
+ this.document.execCommand("useCSS", false, false);
+ returnValue = this.document.execCommand(command, false, argument);
+ this.document.execCommand("useCSS", false, true);
+
+ }else if((dojo.render.html.ie)&&( (command == "backcolor")||(command == "forecolor") )){
+ // Tested under IE 6 XP2, no problem here, comment out
+ // IE weirdly collapses ranges when we exec these commands, so prevent it
+// var tr = this.document.selection.createRange();
+ argument = arguments.length > 1 ? argument : null;
+ returnValue = this.document.execCommand(command, false, argument);
+
+ // timeout is workaround for weird IE behavior were the text
+ // selection gets correctly re-created, but subsequent input
+ // apparently isn't bound to it
+// setTimeout(function(){tr.select();}, 1);
+ }else{
+ // dojo.debug("command:", command, "arg:", argument);
+
+ argument = arguments.length > 1 ? argument : null;
+// if(dojo.render.html.moz){
+// this.document = this.iframe.contentWindow.document
+// }
+
+ if(argument || command!="createlink") {
+ returnValue = this.document.execCommand(command, false, argument);
+ }
+ }
+
+ this.onDisplayChanged();
+ return returnValue;
+ },
+
+ queryCommandEnabled: function(command, argument){
+ if(this.object){
+ switch (command) {
+ case "hilitecolor":
+ command = "setbackcolor";
+ break;
+ case "forecolor":
+ case "backcolor":
+ case "fontsize":
+ case "fontname":
+ command = "set" + command;
+ break;
+ case "formatblock":
+ command = "setblockformat";
+ break;
+ //below are not natively supported commands, we fake them
+ case "strikethrough":
+ command = "bold"; //whenever bold is enabled, strikethrough should be so as well
+ break;
+ case "inserthorizontalrule":
+ return true;
+ }
+
+ if(typeof this._activeX.command[command] == "undefined"){ return false; }
+ var status = this.object.QueryStatus(this._activeX.command[command]);
+ return ((status != this._activeX.status.notsupported)&&
+ (status != this._activeX.status.disabled));
+ }else{
+ // mozilla returns true always
+ if(command == "unlink" && dojo.render.html.mozilla){
+ return dojo.withGlobal(this.window, "hasAncestorElement", dojo.html.selection, ['a']);
+ } else if (command == "inserttable" && dojo.render.html.mozilla) {
+ return true;
+ }
+
+ // return this.document.queryCommandEnabled(command);
+ var elem = (dojo.render.html.ie) ? this.document.selection.createRange() : this.document;
+ return elem.queryCommandEnabled(command);
+ }
+ },
+
+ queryCommandState: function(command, argument){
+ if(this.object){
+ if(command == "forecolor"){
+ command = "setforecolor";
+ }else if(command == "backcolor"){
+ command = "setbackcolor";
+ }else if(command == "strikethrough"){
+ //check whether we are under a <strike>
+ return dojo.withGlobal(this.window, "hasAncestorElement", dojo.html.selection, ['strike']);
+ }else if(command == "inserthorizontalrule"){
+ return false;
+ }
+
+ if(typeof this._activeX.command[command] == "undefined"){ return null; }
+ var status = this.object.QueryStatus(this._activeX.command[command]);
+ return ((status == this._activeX.status.latched)||
+ (status == this._activeX.status.ninched));
+ }else{
+ return this.document.queryCommandState(command);
+ }
+ },
+
+ queryCommandValue: function (command, argument) {
+ if (this.object) {
+ switch (command) {
+ case "forecolor":
+ case "backcolor":
+ case "fontsize":
+ case "fontname":
+ command = "get" + command;
+ return this.object.execCommand(
+ this._activeX.command[command],
+ this._activeX.ui.noprompt);
+ case "formatblock":
+ var retvalue = this.object.execCommand(
+ this._activeX.command["getblockformat"],
+ this._activeX.ui.noprompt);
+ if(retvalue){
+ return this._local2NativeFormatNames[retvalue];
+ }
+ }
+ } else {
+ if(dojo.render.html.ie && command == "formatblock"){
+ return this._local2NativeFormatNames[this.document.queryCommandValue(command)] || this.document.queryCommandValue(command);
+ }
+ return this.document.queryCommandValue(command);
+ }
+ },
+
+
+ /* Misc.
+ ********/
+
+ placeCursorAtStart: function(){
+ // summary:
+ // place the cursor at the start of the editing area
+ this.focus();
+ //see comments in placeCursorAtEnd
+ if(dojo.render.html.moz && this.editNode.firstChild &&
+ this.editNode.firstChild.nodeType != dojo.dom.TEXT_NODE){
+ dojo.withGlobal(this.window, "selectElementChildren", dojo.html.selection, [this.editNode.firstChild]);
+ }else{
+ dojo.withGlobal(this.window, "selectElementChildren", dojo.html.selection, [this.editNode]);
+ }
+ dojo.withGlobal(this.window, "collapse", dojo.html.selection, [true]);
+ },
+
+ placeCursorAtEnd: function(){
+ // summary:
+ // place the cursor at the end of the editing area
+ this.focus();
+ //In mozilla, if last child is not a text node, we have to use selectElementChildren on this.editNode.lastChild
+ //otherwise the cursor would be placed at the end of the closing tag of this.editNode.lastChild
+ if(dojo.render.html.moz && this.editNode.lastChild &&
+ this.editNode.lastChild.nodeType != dojo.dom.TEXT_NODE){
+ dojo.withGlobal(this.window, "selectElementChildren", dojo.html.selection, [this.editNode.lastChild]);
+ }else{
+ dojo.withGlobal(this.window, "selectElementChildren", dojo.html.selection, [this.editNode]);
+ }
+ dojo.withGlobal(this.window, "collapse", dojo.html.selection, [false]);
+ },
+
+ replaceEditorContent: function(/*String*/html){
+ // summary:
+ // this function set the content while trying to maintain the undo stack
+ html = this._preFilterContent(html);
+ if(this.isClosed){
+ this.domNode.innerHTML = html;
+ }else if(this.window.getSelection && !dojo.render.html.moz){ // Safari
+ // look ma! it's a totally f'd browser!
+ this.editNode.innerHTML = html;
+ }else if(this.window.getSelection || this.document.selection){ // Moz/IE
+ this.execCommand("selectall");
+ this.execCommand("inserthtml", html);
+ }
+ },
+
+ _preFilterContent: function(/*String*/html){
+ // summary:
+ // filter the input before setting the content of the editing area
+ var ec = html;
+ dojo.lang.forEach(this.contentPreFilters, function(ef){
+ ec = ef(ec);
+ });
+ if(this.contentDomPreFilters.length>0){
+ var dom = dojo.doc().createElement('div');
+ dom.style.display = "none";
+ dojo.body().appendChild(dom);
+ dom.innerHTML = ec;
+ dojo.lang.forEach(this.contentDomPreFilters, function(ef){
+ dom = ef(dom);
+ });
+ ec = dom.innerHTML;
+ dojo.body().removeChild(dom);
+ }
+ return ec;
+ },
+ _postFilterContent: function(/*String*/html){
+ // summary:
+ // filter the output after getting the content of the editing area
+ var ec = html;
+ if(this.contentDomPostFilters.length>0){
+ var dom = this.document.createElement('div');
+ dom.innerHTML = ec;
+ dojo.lang.forEach(this.contentDomPostFilters, function(ef){
+ dom = ef(dom);
+ });
+ ec = dom.innerHTML;
+ }
+ dojo.lang.forEach(this.contentPostFilters, function(ef){
+ ec = ef(ec);
+ });
+ return ec;
+ },
+
+ //Int: stored last time height
+ _lastHeight: 0,
+
+ _updateHeight: function(){
+ // summary:
+ // Updates the height of the editor area to fit the contents.
+ if(!this.isLoaded){ return; }
+ if(this.height){ return; }
+
+ var height = dojo.html.getBorderBox(this.editNode).height;
+ //height maybe zero in some cases even though the content is not empty,
+ //we try the height of body instead
+ if(!height){
+ height = dojo.html.getBorderBox(this.document.body).height;
+ }
+ if(height == 0){
+ dojo.debug("Can not figure out the height of the editing area!");
+ return; //prevent setting height to 0
+ }
+ this._lastHeight = height;
+ this.editorObject.style.height = this._lastHeight + "px";
+ this.window.scrollTo(0, 0);
+ },
+
+ _saveContent: function(e){
+ // summary:
+ // Saves the content in an onunload event if the editor has not been closed
+ var saveTextarea = dojo.doc().getElementById("dojo.widget.RichText.savedContent");
+ saveTextarea.value += this._SEPARATOR + this.saveName + ":" + this.getEditorContent();
+ },
+
+ getEditorContent: function(){
+ // summary:
+ // return the current content of the editing area (post filters are applied)
+ var ec = "";
+ try{
+ ec = (this._content.length > 0) ? this._content : this.editNode.innerHTML;
+ if(dojo.string.trim(ec) == " "){ ec = ""; }
+ }catch(e){ /* squelch */ }
+
+ if(dojo.render.html.ie && !this.object){
+ //removing appended <P> </P> for IE in none-activeX mode
+ var re = new RegExp("(?:<p> </p>[\n\r]*)+$", "i");
+ ec = ec.replace(re,"");
+ }
+
+ ec = this._postFilterContent(ec);
+
+ if (this.relativeImageUrls) {
+ // why use a regexp instead of dom? because IE is stupid
+ // and won't let us set img.src to a relative URL
+ // this comes after contentPostFilters because once content
+ // gets innerHTML'd img urls will be fully qualified
+ var siteBase = dojo.global().location.protocol + "//" + dojo.global().location.host;
+ var pathBase = dojo.global().location.pathname;
+ if (pathBase.match(/\/$/)) {
+ // ends with slash, match full path
+ } else {
+ // match parent path to find siblings
+ var pathParts = pathBase.split("/");
+ if (pathParts.length) {
+ pathParts.pop();
+ }
+ pathBase = pathParts.join("/") + "/";
+
+ }
+
+ var sameSite = new RegExp("(<img[^>]*\ src=[\"'])("+siteBase+"("+pathBase+")?)", "ig");
+ ec = ec.replace(sameSite, "$1");
+ }
+ return ec;
+ },
+
+ close: function(/*Boolean*/save, /*Boolean*/force){
+ // summery:
+ // Kills the editor and optionally writes back the modified contents to the
+ // element from which it originated.
+ // save:
+ // Whether or not to save the changes. If false, the changes are discarded.
+ // force:
+ if(this.isClosed){return false; }
+
+ if (arguments.length == 0) { save = true; }
+ this._content = this._postFilterContent(this.editNode.innerHTML);
+ var changed = (this.savedContent.innerHTML != this._content);
+
+ // line height is squashed for iframes
+ // FIXME: why was this here? if (this.iframe){ this.domNode.style.lineHeight = null; }
+
+ if(this.interval){ clearInterval(this.interval); }
+
+ if(dojo.render.html.ie && !this.object){
+ dojo.event.browser.clean(this.editNode);
+ }
+
+ if (this.iframe) {
+ // FIXME: should keep iframe around for later re-use
+ delete this.iframe;
+ }
+
+ if(this.textarea){
+ with(this.textarea.style){
+ position = "";
+ left = top = "";
+ if(dojo.render.html.ie){
+ overflow = this.__overflow;
+ this.__overflow = null;
+ }
+ }
+
+ this.domNode.parentNode.removeChild(this.domNode);
+ this.domNode = this.textarea;
+ }else{
+ this.domNode.innerHTML = "";
+ }
+
+ if(save){
+ // kill listeners on the saved content
+ dojo.event.browser.clean(this.savedContent);
+ if(dojo.render.html.moz){
+ var nc = dojo.doc().createElement("span");
+ this.domNode.appendChild(nc);
+ nc.innerHTML = this.editNode.innerHTML;
+ }else{
+ this.domNode.innerHTML = this._content;
+ }
+ } else {
+ while (this.savedContent.hasChildNodes()) {
+ this.domNode.appendChild(this.savedContent.firstChild);
+ }
+ }
+ delete this.savedContent;
+
+ dojo.html.removeClass(this.domNode, "RichTextEditable");
+ this.isClosed = true;
+ this.isLoaded = false;
+ // FIXME: is this always the right thing to do?
+ delete this.editNode;
+
+ if(this.window._frameElement){
+ this.window._frameElement = null;
+ }
+
+ this.window = null;
+ this.document = null;
+ this.object = null;
+ this.editingArea = null;
+ this.editorObject = null;
+
+ return changed; // Boolean: whether the content has been modified
+ },
+
+ destroyRendering: function(){}, // stub!
+
+ destroy: function (){
+ this.destroyRendering();
+ if(!this.isClosed){ this.close(false); }
+
+ dojo.widget.RichText.superclass.destroy.call(this);
+ },
+
+ connect: function (targetObj, targetFunc, thisFunc) {
+ // summary: convenient method for dojo.event.connect
+ dojo.event.connect(targetObj, targetFunc, this, thisFunc);
+ },
+
+ disconnect: function (targetObj, targetFunc, thisFunc) {
+ // summary: convenient method for dojo.event.disconnect
+ dojo.event.disconnect(targetObj, targetFunc, this, thisFunc);
+ },
+
+ disconnectAllWithRoot: function (targetObj) {
+ dojo.deprecated("disconnectAllWithRoot", "is deprecated. No need to disconnect manually", "0.5");
+ },
+
+ _fixContentForMoz: function(html){
+ // summary:
+ // Moz can not handle strong/em tags correctly, correct them here
+ html = html.replace(/<strong([ \>])/gi, '<b$1' );
+ html = html.replace(/<\/strong>/gi, '<\/b>' );
+ html = html.replace(/<em([ \>])/gi, '<i$1' );
+ html = html.replace(/<\/em>/gi, '<\/i>' );
+ return html;
+ }
+ },
+ "html",
+ function(){
+ // summary:
+ // Constructor for this widget, initialize per-instance variables
+
+ // Array: pre content filter function register array
+ this.contentPreFilters = [];
+ // Array: post content filter function register array
+ this.contentPostFilters = [];
+ // Array: pre content dom filter function register array
+ this.contentDomPreFilters = [];
+ // Array: post content dom filter function register array
+ this.contentDomPostFilters = [];
+ // String: semicolon (";") separated list of css files for the editing area
+ this.styleSheets = "";
+ // Array: array to store all the stylesheets applied to the editing area
+ this.editingAreaStyleSheets=[];
+ if(dojo.render.html.moz){
+ this.contentPreFilters.push(this._fixContentForMoz);
+ }
+
+ this._keyHandlers = {};
+ }
+);
Propchange: tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/RichText.js
------------------------------------------------------------------------------
svn:eol-style = native