add drag-n-drop portal code from #6102
authorChuck Hagenbuch <chuck@horde.org>
Thu, 4 Dec 2008 03:35:56 +0000 (22:35 -0500)
committerChuck Hagenbuch <chuck@horde.org>
Thu, 4 Dec 2008 03:35:56 +0000 (22:35 -0500)
23 files changed:
drag_n_drop_portal/block.php [new file with mode: 0644]
drag_n_drop_portal/index.php [new file with mode: 0644]
drag_n_drop_portal/js/portal.js [new file with mode: 0644]
drag_n_drop_portal/js/portal_edit.js [new file with mode: 0644]
drag_n_drop_portal/js/src/portal.js [new file with mode: 0644]
drag_n_drop_portal/js/src/portal_edit.js [new file with mode: 0644]
drag_n_drop_portal/lib/Block/Layout/View/js.php [new file with mode: 0644]
drag_n_drop_portal/params.php [new file with mode: 0644]
drag_n_drop_portal/save.php [new file with mode: 0644]
drag_n_drop_portal/select.php [new file with mode: 0644]
drag_n_drop_portal/templates/portal/params.php [new file with mode: 0644]
drag_n_drop_portal/themes/graphics/bottom_left.gif [new file with mode: 0644]
drag_n_drop_portal/themes/graphics/bottom_right.gif [new file with mode: 0644]
drag_n_drop_portal/themes/graphics/delete.png [new file with mode: 0644]
drag_n_drop_portal/themes/graphics/edit.png [new file with mode: 0644]
drag_n_drop_portal/themes/graphics/minus.png [new file with mode: 0644]
drag_n_drop_portal/themes/graphics/plus.png [new file with mode: 0644]
drag_n_drop_portal/themes/graphics/redbox_spinner.gif [new file with mode: 0644]
drag_n_drop_portal/themes/graphics/reload.png [new file with mode: 0644]
drag_n_drop_portal/themes/graphics/tooltip_bg.png [new file with mode: 0644]
drag_n_drop_portal/themes/graphics/top_left.gif [new file with mode: 0644]
drag_n_drop_portal/themes/graphics/top_right.gif [new file with mode: 0644]
drag_n_drop_portal/themes/screen.css [new file with mode: 0644]

diff --git a/drag_n_drop_portal/block.php b/drag_n_drop_portal/block.php
new file mode 100644 (file)
index 0000000..d105d90
--- /dev/null
@@ -0,0 +1,37 @@
+<?php
+/**
+ * $Id: block.php 219 2008-01-11 09:45:33Z duck $
+ *
+ * Copyright Obala d.o.o. (www.obala.si)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author Duck <duck@obala.net>
+ * @package Folks
+ */
+define('HORDE_BASE', dirname(__FILE__) . '/..');
+require_once HORDE_BASE . '/lib/base.php';
+require_once 'Horde/Loader.php';
+
+// Block to load
+$block_id = Util::getFormData('block');
+list($app, $name) = explode(':', $block_id);
+
+$block_data = array();
+$block = Horde_Block_Collection::getBlock($app, $name, Util::getFormData('defaults'));
+if ($block instanceof PEAR_Error) {
+    $block_data['title'] = $block->getMessage();
+    $block_data['content'] = $block->getDebugInfo();
+} else {
+    $block_data['title'] = @$block->getTitle();
+    if ($block_data['title'] instanceof PEAR_Error) {
+        $block_data['title'] = $block_data['title']->getMessage();
+    }
+    $block_data['content'] = @$block->getContent();
+    if ($block_data['content'] instanceof PEAR_Error) {
+        $block_data['content'] = $block_data['content']->getDebugInfo();
+    }
+}
+
+echo Horde_Serialize::serialize($block_data, SERIALIZE_JSON, NLS::getCharset());
diff --git a/drag_n_drop_portal/index.php b/drag_n_drop_portal/index.php
new file mode 100644 (file)
index 0000000..7bfb3de
--- /dev/null
@@ -0,0 +1,69 @@
+<?php
+/**
+ * $Id: index.php 220 2008-01-11 11:46:13Z duck $
+ *
+ * Copyright Obala d.o.o. (www.obala.si)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author Duck <duck@obala.net>
+ * @package Folks
+ */
+define('HORDE_BASE', dirname(__FILE__) . '/..');
+require_once HORDE_BASE . '/lib/base.php';
+require_once 'Horde/Loader.php';
+require_once './lib/Block/Layout/View/js.php';
+
+if (!Auth::isAuthenticated()) {
+    Horde::authenticationFailureRedirect();
+}
+
+// Load layout from preferences.
+$layout_pref = unserialize($prefs->getValue('portal_layout'));
+if (!is_array($layout_pref)) {
+    $layout_pref = array();
+}
+if (!count($layout_pref)) {
+    $layout_pref = Horde_Block_Collection::getFixedBlocks();
+}
+
+// Render layout.
+$view = new Horde_Block_Layout_View_Js($layout_pref);
+$layout_html = $view->toHtml();
+
+$title = _("Edit yout profile page");
+
+Horde::addScriptFile('prototype.js', 'horde', true);
+Horde::addScriptFile('effects.js', 'horde', true);
+Horde::addScriptFile('redbox.js', 'horde', true);
+require HORDE_TEMPLATES . '/common-header.inc';
+require HORDE_TEMPLATES . '/menu/menu.inc';
+?>
+
+<script type="text/javascript" src="js/builder.js"></script>
+<script type="text/javascript" src="js/dragdrop.js"></script>
+<script type="text/javascript" src="js/src/portal.js"></script>
+<script type="text/javascript" src="js/src/portal_edit.js"></script>
+
+<link href="themes/screen.css" rel="stylesheet" type="text/css" />
+</head>
+
+<div id="menuBottom">
+    <a href="#" onclick="listWidgets()"><?php echo _("Add content") ?></a> |
+    <a href="#" onclick="alert('TODO')"><?php echo _("Reset to default") ?></a> |
+    <a href="#" onclick="savePortal()"><?php echo _("Save") ?></a> |
+</div>
+<br class="clear" />
+ <div id="control_buttons" style="display: none">
+   <a href="#" onclick="minimizeWidget(this); return false;" id="minimize_button" title="<?php echo _("Minimize") ?>"></a>
+   <a href="#" onclick="editWidget(this); return false;" id="edit_button" title="<?php echo _("Edit") ?>"></a>
+   <a href="#" onclick="reloadWidget(this); return false;" id="reload_button" title="<?php echo _("Reload") ?>"></a>
+   <a href="#" onclick="removeWidget(this); return false;" id="delete_button" title="<?php echo _("Delete") ?>"></a>
+ </div>
+
+<?php
+
+echo $layout_html;
+
+require $registry->get('templates', 'horde') . '/common-footer.inc';
diff --git a/drag_n_drop_portal/js/portal.js b/drag_n_drop_portal/js/portal.js
new file mode 100644 (file)
index 0000000..2cc5e94
--- /dev/null
@@ -0,0 +1 @@
+if(typeof Draggable=="undefined"){throw("widget.js requires including script.aculo.us' dragdrop.js library")}if(typeof Builder=="undefined"){throw("widget.js requires including script.aculo.us' builder.js library")}if(typeof Xilinus=="undefined"){Xilinus={}}Builder.dump();Xilinus.Widget=Class.create();Xilinus.Widget.lastId=0;Xilinus.Widget.remove=function(B,A){if(A&&A.afterFinish){A.afterFinish.call()}};Object.extend(Xilinus.Widget.prototype,{initialize:function(D,E){D=D||"widget";this._id=E||("widget_"+Xilinus.Widget.lastId++);this._titleDiv=DIV({className:"header",id:this._getId("header")},"");this._contentDiv=DIV({className:"headerbox",id:this._getId("content")},"");this._footerDiv=DIV({className:D+"_statusbar",id:this._getId("footer")},"");var C=DIV({className:"header"},this._titleDiv);var B=DIV({className:"headerbox"},this._contentDiv);var A=DIV({className:D+"_sw"},this._footerDiv);this._div=DIV({className:D+(D!="widget"?" widget":""),id:this._getId()},[C,B,A]);this._div.widget=this;return this},destroy:function(){this._div.remove()},getElement:function(){return $(this._getId())||$(this._div)},setTitle:function(A){$(this._titleDiv).update(A);return this},getTitle:function(A){return $(this._titleDiv)},setFooter:function(A){$(this._footerDiv).update(A);return this},getFooter:function(A){return $(this._footerDiv)},setContent:function(A){$(this._contentDiv).update(A);return this},getContent:function(A){return $(this._contentDiv)},updateHeight:function(){$(this._contentDiv).setStyle({height:null});var A=$(this._contentDiv).getHeight();$(this._contentDiv).setStyle({height:A+"px"})},_getId:function(A){return(A?A+"_":"")+this._id}});Xilinus.Portal=Class.create();Object.extend(Xilinus.Portal.prototype,{lastEvent:null,widgets:null,columns:null,initialize:function(B,A){this.options=Object.extend({url:null,onOverWidget:null,onOutWidget:null,onChange:null,onUpdate:null,removeEffect:Xilinus.Widget.remove},A);this._columns=(typeof B=="string")?$$(B):B;this._widgets=new Array();this._columns.each(function(C){Droppables.add(C,{onHover:this.onHover.bind(this),overlap:"vertical",accept:this.options.accept})}.bind(this));this._outTimer=null;this._columns.invoke("undoPositioned");this._currentOverWidget=null;this._widgetMouseOver=this.widgetMouseOver.bindAsEventListener(this);this._widgetMouseOut=this.widgetMouseOut.bindAsEventListener(this);Draggables.addObserver({onEnd:this.endDrag.bind(this),onStart:this.startDrag.bind(this)})},add:function(C,B,A){A=typeof A=="undefined"?true:A;this._widgets.push(C);if(this.options.accept){C.getElement().addClassName(this.options.accept)}this._columns[B].appendChild(C.getElement());C.updateHeight();if(A){C.draggable=new Draggable(C.getElement(),{handle:C._titleDiv,revert:false});C.getTitle().addClassName("widget_draggable")}this._updateColumnsHeight();if(this.options.onOverWidget){C.getElement().immediateDescendants().invoke("observe","mouseover",this._widgetMouseOver)}if(this.options.onOutWidget){C.getElement().immediateDescendants().invoke("observe","mouseout",this._widgetMouseOut)}},remove:function(A){this._widgets.reject(function(B){return B==A});if(this.options.onOverWidget){A.getElement().immediateDescendants().invoke("stopObserving","mouseover",this._widgetMouseOver)}if(this.options.onOutWidget){A.getElement().immediateDescendants().invoke("stopObserving","mouseout",this._widgetMouseOut)}if(A.draggable){A.draggable.destroy()}this.options.removeEffect(A.getElement(),{afterFinish:function(){A.destroy()}});this._updateColumnsHeight()},serialize:function(){parameters="";this._columns.each(function(A){var B=A.immediateDescendants().collect(function(C){return A.id+"[]="+C.id}).join("&");parameters+=B+"&"});return parameters},addWidgetControls:function(A){$(A).observe("mouseover",this._widgetMouseOver);$(A).observe("mouseout",this._widgetMouseOut)},widgetMouseOver:function(B){this._clearTimer();var A=Event.element(B).up(".widget");if(this._currentOverWidget==null||this._currentOverWidget!=A){if(this._currentOverWidget&&this._currentOverWidget!=A){this.options.onOutWidget(this,this._currentOverWidget.widget)}this._currentOverWidget=A;this.options.onOverWidget(this,A.widget)}},widgetMouseOut:function(B){this._clearTimer();var A=Event.element(B).up(".widget");this._outTimer=setTimeout(this._doWidgetMouseOut.bind(this,A),100)},_doWidgetMouseOut:function(A){this._currentOverWidget=null;this.options.onOutWidget(this,A.widget)},startDrag:function(B,A){var D=A.element;if(!this._widgets.find(function(F){return F==D.widget})){return}var C=D.parentNode;var E=DIV({className:"widget_ghost"},"");$(E).setStyle({height:D.getHeight()+"px"});C.insertBefore(E,D);D.setStyle({width:D.getWidth()+"px"});Position.absolutize(D);document.body.appendChild(D);A.element.ghost=E;this._savePosition=this.serialize()},endDrag:function(B,A){var D=A.element;if(!this._widgets.find(function(E){return E==D.widget})){return}var C=D.ghost.parentNode;C.insertBefore(A.element,D.ghost);D.ghost.remove();if(Prototype.Browser.Opera){D.setStyle({top:0,left:0,width:"100%",height:D._originalHeight,zIndex:null,opacity:null,position:"relative"})}else{D.setStyle({top:null,left:null,width:null,height:D._originalHeight,zIndex:null,opacity:null,position:"relative"})}D.ghost=null;D.widget.updateHeight();this._updateColumnsHeight();if(this._savePosition!=this.serialize()){if(this.options.url){new Ajax.Request(this.options.url,{parameters:this.serialize()})}if(this.options.onUpdate){this.options.onUpdate(this)}}},onHover:function(D,H,I){var A=Position.cumulativeOffset(H);var J=A[0]+10;var F=A[1]+(1-I)*H.getHeight();if(Position.within(D.ghost,J,F)){return}var M=false;var G=false;for(var C=0,B=this._widgets.length;C<B;++C){var K=this._widgets[C].getElement();if(K==D||K.parentNode!=H){continue}if(Position.within(K,J,F)){var I=Position.overlap("vertical",K);if(I<0.5){if(K.next()!=D.ghost){K.parentNode.insertBefore(D.ghost,K.next());G=true}}else{if(K.previous()!=D.ghost){K.parentNode.insertBefore(D.ghost,K);G=true}}M=true;break}}if(!M){if(D.ghost.parentNode!=H){var L=H.immediateDescendants().last();var E=L?Position.cumulativeOffset(L)[1]+L.getHeight():0;if(F>E&&L!=D.ghost){H.appendChild(D.ghost);G=true}}}if(G&&this.options.onChange){this.options.onChange(this)}this._updateColumnsHeight()},_updateColumnsHeight:function(){var A=0;this._columns.each(function(B){A=Math.max(A,B.immediateDescendants().inject(0,function(D,C){return D+C.getHeight()}))});this._columns.invoke("setStyle",{height:A+"px"})},_clearTimer:function(){if(this._outTimer){clearTimeout(this._outTimer);this._outTimer=null}}});
\ No newline at end of file
diff --git a/drag_n_drop_portal/js/portal_edit.js b/drag_n_drop_portal/js/portal_edit.js
new file mode 100644 (file)
index 0000000..03f7f3b
--- /dev/null
@@ -0,0 +1 @@
+var _widgets_blocks=new Array();var _layout_params=new Array();function onOverWidget(A,B){B.getElement().insertBefore($("control_buttons"),B.getElement().firstChild);$("control_buttons").show()}function onOutWidget(A,B){$("control_buttons").hide()}function minimizeWidget(A){var B=$(A).up(".widget").widget;id=B._getId().substr(7);if($("content_widget_"+id).style.display=="none"){$("content_widget_"+id).style.display="block";A.id="minimize_button"}else{$("content_widget_"+id).style.display="none";A.id="maximize_button"}}function removeWidget(A){var B=$(A).up(".widget").widget;if(confirm(confirm_remove)){document.body.appendChild($("control_buttons").hide());portal.remove(B)}}function listWidgets(){RedBox.loading();new Ajax.Request(list_url,{method:"get",onSuccess:function(A){RedBox.showHtml('<div id="RB_info">'+A.responseText+"</div>")},onFailure:function(A){RedBox.close()}})}function addWidget(){select=$("block_selection");title=select.options[select.selectedIndex].text;widget=new Xilinus.Widget();widget.setTitle(title);widget.setContent(title);portal.add(widget,parseInt($F("block_column")));_widgets_blocks[_widgets_blocks.length]=select.value;_layout_params[_widgets_blocks.length]=new Array();editWidget(widget);cancelRedBox()}function reloadWidget(A){if($(A).id=="reload_button"){var B=$(A).up(".widget").widget}else{var B=A}var C=B._getId().substr(7);new Ajax.Request(load_url,{parameters:getAjaxParameters(A),method:"get",onSuccess:function(D){block_data=D.responseText.evalJSON(true);B.setTitle(block_data.title);B.setContent(block_data.content);_widgets_blocks[B._getId().substr(7)]=block_used},onFailure:function(D){alert("Someting gone wrong.")}})}function editWidget(A){if($(A).id=="reload_button"){var B=$(A).up(".widget").widget}else{var B=A}new Ajax.Request(edit_url,{parameters:getAjaxParameters(A),method:"get",onSuccess:function(C){RedBox.showHtml('<div id="RB_info">'+C.responseText+"</div>")},onFailure:function(C){RedBox.close()}})}function getAjaxParameters(A){if($(A).id=="reload_button"||$(A).id=="edit_button"){var B=$(A).up(".widget").widget}else{var B=A}var C=B._getId().substr(7);parameters="block="+_widgets_blocks[C];parameters=parameters+"&widget="+B._getId();p=_layout_params[C];for(a in p){if(typeof(p[a])!="string"){break}parameters=parameters+"&defaults["+a+"]="+p[a]}return parameters}function setParams(){widget_name="";params=new Array();inputs=$("blockform").getElements();inputs.each(function(B){name=B.name.substr(0,6);if(name=="params"){pos=B.name.indexOf("]",7);param_name=B.name.substr(7,pos-7);if(B.type=="checkbox"){params[param_name]=B.checked}else{params[param_name]=B.value}}if(name=="widget"){widget_name=B.value}});_layout_params[widget_name.substr(7)]=params;var A=$(widget_name).widget;reloadWidget(A);cancelRedBox()}function noParams(B,C){var A=$(B).widget;reloadWidget(A);cancelRedBox()}function cancelRedBox(){RedBox.close();return false}function savePortal(){parameters=portal.serialize();for(var A=0;A<_layout_params.length;A++){parameters=parameters+"&params["+A+"][type]="+_widgets_blocks[A];p=_layout_params[A];for(a in p){if(typeof(p[a])!="string"){break}parameters=parameters+"&params["+A+"]["+a+"]="+p[a]}}new Ajax.Request(save_url,{parameters:parameters,method:"post"})};
\ No newline at end of file
diff --git a/drag_n_drop_portal/js/src/portal.js b/drag_n_drop_portal/js/src/portal.js
new file mode 100644 (file)
index 0000000..a22b33b
--- /dev/null
@@ -0,0 +1,366 @@
+// Copyright (c) 2006 Sébastien Gruhier (http://xilinus.com, http://itseb.com)
+// 
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+// VERSION 1.1-trunk
+
+if(typeof Draggable == 'undefined')
+  throw("widget.js requires including script.aculo.us' dragdrop.js library");
+
+if(typeof Builder == 'undefined')
+  throw("widget.js requires including script.aculo.us' builder.js library");
+
+// Xilinus namespace
+if(typeof Xilinus == 'undefined')
+  Xilinus = {}
+  
+Builder.dump();
+
+
+Xilinus.Widget = Class.create();  
+Xilinus.Widget.lastId = 0;
+Xilinus.Widget.remove = function(element, options) {    
+  if (options && options.afterFinish)
+    options.afterFinish.call();
+} 
+
+Object.extend(Xilinus.Widget.prototype, {
+  initialize: function(className, id) {
+    className = className || "widget";  
+    this._id   = id || ("widget_" + Xilinus.Widget.lastId++);
+    
+    this._titleDiv   = DIV({className: 'header',     id: this._getId("header")},  "");  
+    this._contentDiv = DIV({className: 'headerbox',   id: this._getId("content")}, "");
+    this._footerDiv  = DIV({className: className + '_statusbar', id: this._getId("footer")},  "");
+    
+    var divHeader  = DIV({className: 'header' }, this._titleDiv);
+    var divContent = DIV({className: 'headerbox' },  this._contentDiv); 
+    var divFooter  = DIV({className: className + '_sw' }, this._footerDiv);  
+    
+    this._div = DIV({className: className + (className != "widget" ? " widget" : ""), id: this._getId()}, [divHeader, divContent, divFooter]);     
+    this._div.widget = this;
+        
+    return this;
+  },    
+  
+  destroy: function() {
+    this._div.remove();
+  },
+  
+  getElement: function() {
+    return $(this._getId()) || $(this._div);
+  },
+  
+  setTitle: function(title) {
+    $(this._titleDiv).update(title);
+    return this;
+  },
+
+  getTitle: function(title) {
+    return $(this._titleDiv)
+  },
+  
+  setFooter: function(title) {
+    $(this._footerDiv).update(title);
+    return this;
+  },
+  
+  getFooter: function(title) {
+    return $(this._footerDiv)
+  },
+  
+  setContent: function(title) {
+    $(this._contentDiv).update(title);  
+    return this;
+  },
+   
+  getContent: function(title) {
+    return $(this._contentDiv)
+  },
+  
+  updateHeight: function() {       
+    $(this._contentDiv).setStyle({height: null})
+      
+    var h = $(this._contentDiv).getHeight();
+    $(this._contentDiv).setStyle({height: h + "px"})
+  },
+  
+  // PRIVATE FUNCTIONS
+  _getId: function(prefix) { 
+      return (prefix ? prefix + "_" : "") + this._id;
+  }
+});
+
+
+Xilinus.Portal = Class.create()
+Object.extend(Xilinus.Portal.prototype, {
+  lastEvent: null,   
+  widgets:   null,
+  columns:   null, 
+  
+  initialize: function(columns, options) {   
+    this.options = Object.extend({                  
+                     url:          null,                 // Url called by Ajax.Request after a drop
+                     onOverWidget: null,                 // Called when the mouse goes over a widget
+                     onOutWidget:  null,                 // Called when the mouse goes out of a widget                                           
+                     onChange:     null,                 // Called a widget has been move during drag and drop 
+                     onUpdate:     null,                 // Called a widget has been move after drag and drop
+                     removeEffect: Xilinus.Widget.remove // Remove effect (by default no effect), you can set it to Effect.SwitchOff for example
+                   }, options)
+    this._columns = (typeof columns == "string") ? $$(columns) : columns;
+    this._widgets = new Array();          
+    this._columns.each(function(element) {Droppables.add(element, {onHover: this.onHover.bind(this), 
+                                                                   overlap: "vertical", 
+                                                                   accept: this.options.accept})}.bind(this));  
+    this._outTimer  = null;
+    
+    // Draggable calls makePositioned for IE fix (??), I had to remove it for all browsers fix :) to handle properly zIndex
+    this._columns.invoke("undoPositioned");    
+    
+    this._currentOverWidget = null; 
+    this._widgetMouseOver = this.widgetMouseOver.bindAsEventListener(this);
+    this._widgetMouseOut  = this.widgetMouseOut.bindAsEventListener(this);
+    
+    Draggables.addObserver({ onEnd: this.endDrag.bind(this), onStart: this.startDrag.bind(this) }); 
+  },
+  
+  add: function(widget, columnIndex, draggable) {   
+    draggable = typeof draggable == "undefined" ? true : draggable
+    // Add to widgets list
+    this._widgets.push(widget);
+    if (this.options.accept)
+      widget.getElement().addClassName(this.options.accept)
+    // Add element to column
+    this._columns[columnIndex].appendChild(widget.getElement());
+    widget.updateHeight();
+    
+    // Make header draggable   
+    if (draggable) {
+      widget.draggable = new Draggable(widget.getElement(),{ handle: widget._titleDiv, revert: false});     
+      widget.getTitle().addClassName("widget_draggable");   
+    }
+    
+    // Update columns heights
+    this._updateColumnsHeight();  
+
+    // Add mouse observers  
+    if (this.options.onOverWidget)
+      widget.getElement().immediateDescendants().invoke("observe", "mouseover", this._widgetMouseOver);
+    if (this.options.onOutWidget)
+      widget.getElement().immediateDescendants().invoke("observe", "mouseout",  this._widgetMouseOut);
+  },  
+                
+  remove: function(widget) {
+    // Remove from the list
+    this._widgets.reject(function(w) { return w == widget});
+
+    // Remove observers
+    if (this.options.onOverWidget)
+      widget.getElement().immediateDescendants().invoke("stopObserving", "mouseover", this._widgetMouseOver);
+    if (this.options.onOutWidget)
+      widget.getElement().immediateDescendants().invoke("stopObserving", "mouseout",  this._widgetMouseOut);
+
+    // Remove draggable
+    if (widget.draggable)
+      widget.draggable.destroy();
+      
+    // Remove from the dom
+    this.options.removeEffect(widget.getElement(), {afterFinish: function() {widget.destroy();}});
+    
+    // Update columns heights
+    this._updateColumnsHeight();  
+  },
+    
+  serialize: function() {
+    parameters = ""
+    this._columns.each(function(column) {   
+      var p = column.immediateDescendants().collect(function(element) {
+        return column.id + "[]=" + element.id
+      }).join("&") 
+      parameters += p + "&"
+    });               
+    
+    return parameters;                      
+  },     
+  
+  addWidgetControls: function(element) {
+    $(element).observe("mouseover", this._widgetMouseOver); 
+    $(element).observe("mouseout", this._widgetMouseOut); 
+  },
+  
+  // EVENTS CALLBACKS
+  widgetMouseOver: function(event) {   
+    this._clearTimer();
+      
+    var element =  Event.element(event).up(".widget");
+    if (this._currentOverWidget == null || this._currentOverWidget != element) {
+      if (this._currentOverWidget && this._currentOverWidget != element)
+        this.options.onOutWidget(this, this._currentOverWidget.widget)    
+        
+      this._currentOverWidget = element;
+      this.options.onOverWidget(this, element.widget)
+    }
+  },
+
+  widgetMouseOut: function(event) {    
+    this._clearTimer();
+    var element =  Event.element(event).up(".widget"); 
+    this._outTimer = setTimeout(this._doWidgetMouseOut.bind(this, element), 100);
+  },
+  
+  _doWidgetMouseOut: function(element) {
+    this._currentOverWidget = null;
+    this.options.onOutWidget(this, element.widget)    
+  },                                               
+  
+  // DRAGGABLE OBSERVER CALLBACKS
+  startDrag: function(eventName, draggable) { 
+    var widget = draggable.element;
+    
+    if (!this._widgets.find(function(w) {return w == widget.widget}))
+      return;
+
+    var column = widget.parentNode;
+    
+    // Create and insert ghost widget
+    var ghost = DIV({className: 'widget_ghost'}, ""); 
+    $(ghost).setStyle({height: widget.getHeight()  + 'px'})
+
+    column.insertBefore(ghost, widget);  
+
+    // IE Does not absolutize properly the widget, needs to set width before
+    widget.setStyle({width: widget.getWidth() + "px"});
+    
+    // Absolutize and move widget on body
+    Position.absolutize(widget);  
+    document.body.appendChild(widget);   
+    
+    // Store ghost to drag widget for later use
+    draggable.element.ghost = ghost; 
+    
+    // Store current position
+    this._savePosition = this.serialize();    
+  },   
+
+  endDrag: function(eventName, draggable) {
+    var widget = draggable.element;      
+    if (!this._widgets.find(function(w) {return w == widget.widget}))
+      return;
+    
+    var column = widget.ghost.parentNode;
+    
+    column.insertBefore(draggable.element, widget.ghost); 
+    widget.ghost.remove();   
+    
+    if (Prototype.Browser.Opera)     
+      widget.setStyle({top: 0, left: 0, width: "100%", height: widget._originalHeight, zIndex: null, opacity: null, position: "relative"})
+    else
+      widget.setStyle({top: null, left: null, width: null, height: widget._originalHeight, zIndex: null, opacity: null, position: "relative"})
+    
+    widget.ghost = null;    
+    widget.widget.updateHeight();
+    this._updateColumnsHeight();
+    
+    // Fire events if changed
+    if (this._savePosition != this.serialize()) {
+      if (this.options.url)  
+        new Ajax.Request(this.options.url, {parameters: this.serialize()});
+      
+      if (this.options.onUpdate)
+        this.options.onUpdate(this);
+    }
+  },
+
+  onHover: function(dragWidget, dropon, overlap) {  
+    var offset = Position.cumulativeOffset(dropon);
+    var x = offset[0] + 10;
+    var y = offset[1] + (1 - overlap) * dropon.getHeight();
+
+    // Check over ghost widget
+    if (Position.within(dragWidget.ghost, x, y))
+      return;
+      
+    // Find if it's overlapping a widget
+    var found = false;
+    var moved = false;
+    for (var index = 0, len = this._widgets.length; index < len; ++index) {
+      var w = this._widgets[index].getElement();
+      if (w ==  dragWidget || w.parentNode != dropon)   
+        continue;        
+
+      if (Position.within(w, x, y)) {    
+        var overlap = Position.overlap( 'vertical', w);     
+        // Bottom of the widget
+        if (overlap < 0.5) {         
+          // Check if the ghost widget is not already below this widget
+          if (w.next() != dragWidget.ghost) {
+            w.parentNode.insertBefore(dragWidget.ghost, w.next());   
+            moved = true;
+          }
+        } 
+        // Top of the widget
+        else {       
+          // Check if the ghost widget is not already above this widget
+          if (w.previous() != dragWidget.ghost) {      
+            w.parentNode.insertBefore(dragWidget.ghost, w);   
+            moved = true;
+          }
+        }
+        found = true;
+        break;
+      }
+    }
+    // Not found a widget
+    if (! found) {        
+      // Check if dropon has ghost widget
+      if (dragWidget.ghost.parentNode != dropon) {
+        // Get last widget bottom value
+        var last = dropon.immediateDescendants().last();
+        var yLast = last ? Position.cumulativeOffset(last)[1] + last.getHeight() : 0; 
+        if (y > yLast && last != dragWidget.ghost) {
+          dropon.appendChild(dragWidget.ghost);
+          moved = true;
+        }
+      }
+    }  
+    if (moved && this.options.onChange) 
+      this.options.onChange(this)            
+
+    this._updateColumnsHeight();
+  },                
+  
+  // PRIVATE FUNCTIONS
+  _updateColumnsHeight: function() {      
+    var h = 0;
+    this._columns.each(function(col) {
+      h = Math.max(h, col.immediateDescendants().inject(0, function(sum, element) { 
+        return sum + element.getHeight(); 
+      }));
+    })
+    this._columns.invoke("setStyle", {height: h + 'px'})
+  },
+  
+  _clearTimer: function() {
+    if (this._outTimer) {
+      clearTimeout(this._outTimer);
+      this._outTimer = null;
+    }                        
+  }
+});     
diff --git a/drag_n_drop_portal/js/src/portal_edit.js b/drag_n_drop_portal/js/src/portal_edit.js
new file mode 100644 (file)
index 0000000..f664f2f
--- /dev/null
@@ -0,0 +1,204 @@
+
+var _widgets_blocks = new Array();
+var _layout_params = new Array();
+
+function onOverWidget(portal, widget) {
+    widget.getElement().insertBefore($('control_buttons'), widget.getElement().firstChild);
+    $('control_buttons').show();
+}
+
+function onOutWidget(portal, widget) {
+    $('control_buttons').hide();
+}
+
+function minimizeWidget(element) {
+    var widget = $(element).up(".widget").widget;
+    id = widget._getId().substr(7);
+    if ($('content_widget_' + id).style.display == 'none') {
+        $('content_widget_' + id).style.display = 'block';
+        element.id = 'minimize_button';
+    } else {
+        $('content_widget_' + id).style.display = 'none';
+        element.id = 'maximize_button';
+    }
+}
+
+function removeWidget(element) {
+    var widget = $(element).up(".widget").widget;
+
+    if (confirm(confirm_remove)) {
+        document.body.appendChild($('control_buttons').hide())
+        portal.remove(widget);
+    }
+}
+
+function listWidgets() {
+
+    RedBox.loading();
+
+    // load edit options
+    new Ajax.Request(list_url, {
+                method: 'get',
+                onSuccess: function(transport) {
+                    RedBox.showHtml('<div id="RB_info">' + transport.responseText + '</div>');
+                },
+                onFailure: function(transport) {
+                    RedBox.close();
+                }
+            });
+}
+
+function addWidget() {
+
+    // Add widget
+    select = $('block_selection');
+    title = select.options[select.selectedIndex].text;
+
+    widget = new Xilinus.Widget();
+    widget.setTitle(title);
+    widget.setContent(title);
+    portal.add(widget, parseInt($F('block_column')));
+
+    _widgets_blocks[_widgets_blocks.length] = select.value;
+    _layout_params[_widgets_blocks.length] = new Array();
+
+    // Edit wiget
+    editWidget(widget);
+
+    cancelRedBox();
+}
+
+function reloadWidget(element) {
+
+    if ($(element).id == 'reload_button') {
+        var widget = $(element).up(".widget").widget;
+    } else {
+        var widget = element;
+    }
+
+    var id = widget._getId().substr(7);
+
+    new Ajax.Request(load_url, {
+                parameters: getAjaxParameters(element),
+                method: 'get',
+                onSuccess: function(transport) {
+                    block_data = transport.responseText.evalJSON(true);
+                    widget.setTitle(block_data['title']);
+                    widget.setContent(block_data['content']);
+                    _widgets_blocks[widget._getId().substr(7)] = block_used;
+                },
+                onFailure: function(transport) {
+                    alert('Someting gone wrong.');
+                }
+            });
+}
+
+function editWidget(element) {
+
+    if ($(element).id == 'reload_button') {
+        var widget = $(element).up(".widget").widget;
+    } else {
+        var widget = element;
+    }
+
+    new Ajax.Request(edit_url, {
+                parameters: getAjaxParameters(element),
+                method: 'get',
+                onSuccess: function(transport) {
+                    RedBox.showHtml('<div id="RB_info">' + transport.responseText + '</div>');
+                },
+                onFailure: function(transport) {
+                    RedBox.close();
+                }
+            });
+
+}
+
+function getAjaxParameters(element) {
+
+    if ($(element).id == 'reload_button' || $(element).id == 'edit_button') {
+        var widget = $(element).up(".widget").widget;
+    } else {
+        var widget = element;
+    }
+
+    var id = widget._getId().substr(7);
+
+    parameters = 'block=' + _widgets_blocks[id];
+    parameters = parameters + '&widget=' + widget._getId();
+
+    p = _layout_params[id];
+    for (a in p) {
+        if (typeof(p[a]) != 'string') {
+            break;
+        }
+        parameters = parameters + '&defaults[' + a + ']=' + p[a];
+    }
+
+    return parameters;
+}
+
+function setParams() {
+
+    widget_name = '';
+    params = new Array();
+    inputs = $('blockform').getElements();
+    inputs.each(function(item) {
+        name = item.name.substr(0, 6);
+        if (name == 'params') {
+            pos = item.name.indexOf(']', 7);
+            param_name = item.name.substr(7, pos - 7);
+            if (item.type == 'checkbox') {
+                params[param_name] = item.checked;
+            } else {
+                params[param_name] = item.value;
+            }
+        }
+        if (name == 'widget') {
+            widget_name = item.value;
+        }
+    });
+
+    _layout_params[widget_name.substr(7)] = params;
+
+    var widget = $(widget_name).widget;
+    reloadWidget(widget);
+
+    cancelRedBox();
+}
+
+function noParams(widget_name, msg) {
+
+    // alert(msg);
+
+    var widget = $(widget_name).widget;
+    reloadWidget(widget);
+
+    cancelRedBox();
+}
+
+function cancelRedBox() {
+    RedBox.close();
+    return false;
+}
+
+function savePortal() {
+
+    parameters = portal.serialize();
+
+    for (var i = 0; i < _layout_params.length; i++) {
+        parameters = parameters + '&params[' + i + '][type]=' + _widgets_blocks[i];
+        p = _layout_params[i];
+        for (a in p) {
+            if (typeof(p[a]) != 'string') {
+                break;
+            }
+            parameters = parameters + '&params[' + i + '][' + a + ']=' + p[a];
+        }
+    }
+
+    new Ajax.Request(save_url, {
+                parameters: parameters,
+                method: 'post'
+            });
+}
diff --git a/drag_n_drop_portal/lib/Block/Layout/View/js.php b/drag_n_drop_portal/lib/Block/Layout/View/js.php
new file mode 100644 (file)
index 0000000..2e5cd23
--- /dev/null
@@ -0,0 +1,113 @@
+<?php
+/**
+ * The Horde_Block_Layout_View class represents the user defined portal layout.
+ *
+ * $Horde: framework/Block/Block/Layout/View.php,v 1.17 2007/11/22 00:28:06 jan Exp $
+ *
+ * Copyright 2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author  Duck <duck@obala.net>
+ * @since   Horde 4
+ * @package Horde_Block
+ */
+class Horde_Block_Layout_View_Js extends Horde_Block_Layout_View {
+
+    /**
+     * Render the current layout as HTML.
+     *
+     * @return string HTML layout.
+     */
+    function toHtml()
+    {
+        $html = '<div id="page">';
+        $js = '<script type="text/javascript">' . "\n";
+        $js .= 'var confirm_remove = "' . _("Are sure to remove this block?") . '";' . "\n";
+        $js .= 'var edit_url = "' . Horde::applicationUrl('dragdrop/params.php') . '";' . "\n";
+        $js .= 'var load_url = "' . Horde::applicationUrl('dragdrop/block.php') . '";' . "\n";
+        $js .= 'var list_url = "' . Horde::applicationUrl('dragdrop/select.php') . '";' . "\n";
+        $js .= 'var save_url = "' . Horde::applicationUrl('dragdrop/save.php') . '";' . "\n";
+
+        $js_init = '<script type="text/javascript">'
+                . ' function init() {'
+                . ' portal = new Xilinus.Portal("#page div", {onOverWidget: onOverWidget, onOutWidget: onOutWidget});';
+
+        $js_id = 0;
+        $widget_col = 0;
+        $columns = 0;
+        foreach ($this->_layout as $row_num => $row) {
+            foreach ($row as $col_num => $item) {
+                if ($col_num > $columns) {
+                    $columns = $col_num;
+                }
+                if (is_array($item)) {
+                    $js .= $this->_serializeBlock($js_id, $item['app'], $item['params']['type'],
+                                                $item['params']['params'], $js_init, $col_num);
+                    $js_id++;
+                }
+            }
+        }
+
+        $columns = max($columns, 2); // FOR TESTING ADD AT KEAST 3 COLUMNS
+        for ($col_num = 0; $col_num <= $columns; $col_num++) {
+            $html .= '<div id="widget_col_' . $col_num . '"></div>';
+        }
+
+        $js .= '</script>';
+
+        $js_init .= 'portal.addWidgetControls("control_buttons");'
+                . '}'
+                . 'document.observe("dom:loaded", init);
+'
+                . '</script>';
+
+        $html .= '</div>' . "\n" . $js . "\n" . $js_init;
+
+        // Strip any CSS <link> tags out of the returned content so
+        // they can be handled seperately.
+        if (preg_match_all('/<link .*?rel="stylesheet".*?\/>/', $html, $links)) {
+            $html = str_replace($links[0], '', $html);
+            $this->_linkTags = $links[0];
+        }
+
+        return $html;
+    }
+
+    function _serializeBlock($js_id, $app, $name, $params, &$js_init, $col_num)
+    {
+        $block = Horde_Block_Collection::getBlock($app, $name, $params);
+        if ($block instanceof PEAR_Error) {
+            $title = $block->getMessage();
+            $content = $block->getDebugInfo();
+            $params = array();
+        } else {
+            $content = @$block->getContent();
+            if ($content instanceof PEAR_Error) {
+                $content = $content->getDebugInfo();
+            }
+            $title = @$block->getTitle();
+            if ($title instanceof PEAR_Error) {
+                $title = $title->getMessage();
+            } else {
+                $title = strip_tags($title);
+            }
+        }
+
+        $content = Horde_Serialize::serialize($content, SERIALIZE_JSON, NLS::getCharset());
+        $title = Horde_Serialize::serialize($title, SERIALIZE_JSON, NLS::getCharset());
+        $params = Horde_Serialize::serialize($params, SERIALIZE_JSON, NLS::getCharset());
+
+        $js_init .= 'portal.add(new Xilinus.Widget().'
+                    . 'setTitle(title_' . $js_id .').'
+                    . ' setContent(content_' . $js_id .'), ' . $col_num . ');'
+                    . '_widgets_blocks[' . $js_id . '] = "' . $app . ':' . $name . '";'
+                    . '_layout_params[' . $js_id . '] = \'' . $params . '\'.evalJSON();'
+                    . 'delete title_' . $js_id .';'
+                    . 'delete content_' . $js_id .';' . "\n";
+
+        return 'var content_' . $js_id . ' = ' . $content . ';' . "\n"
+                . 'var title_' . $js_id . ' = ' . $title . ';' . "\n";
+    }
+}
diff --git a/drag_n_drop_portal/params.php b/drag_n_drop_portal/params.php
new file mode 100644 (file)
index 0000000..92ff914
--- /dev/null
@@ -0,0 +1,42 @@
+<?php
+/**
+ * $Id: params.php 216 2008-01-10 19:47:31Z duck $
+ *
+ * Copyright Obala d.o.o. (www.obala.si)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author Duck <duck@obala.net>
+ * @package Folks
+ */
+define('HORDE_BASE', dirname(__FILE__) . '/..');
+require_once HORDE_BASE . '/lib/base.php';
+require_once 'Horde/Loader.php';
+
+// Block to load
+$block_id = Util::getFormData('block');
+list($app, $name) = explode(':', $block_id);
+
+// Load collection
+$blocks = new Horde_Block_Collection(null, array($app));
+
+// Create block params form
+$params = $blocks->getParams($app, $name);
+if (empty($params) ||
+    !$blocks->isEditable($app, $name)) {
+    echo '<script type="text/javascript">noParams("' . Util::getFormData('widget') . '", "' . _("This block has no special parameters.") . '");</script>';
+} else {
+    $block = &$blocks->getBlock($app, $name);
+
+    $defaults = Util::getFormData('defaults');
+    if (empty($defaults)) {
+        foreach ($params as $key => $val) {
+            $defaults[$key] = $val;
+        }
+    }
+    if (!isset($defaults['_refresh_time'])) {
+        $defaults['_refresh_time'] = 0;
+    }
+    require './templates/portal/params.php';
+}
diff --git a/drag_n_drop_portal/save.php b/drag_n_drop_portal/save.php
new file mode 100644 (file)
index 0000000..41057cd
--- /dev/null
@@ -0,0 +1,39 @@
+<?php
+/**
+ * $Id: block.php 219 2008-01-11 09:45:33Z duck $
+ *
+ * Copyright Obala d.o.o. (www.obala.si)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author Duck <duck@obala.net>
+ * @package Folks
+ */
+define('HORDE_BASE', dirname(__FILE__) . '/..');
+require_once HORDE_BASE . '/lib/base.php';
+require_once 'Horde/Loader.php';
+
+if (!Auth::isAuthenticated()) {
+    Horde::authenticationFailureRedirect();
+}
+
+$layout = array();
+$params = Util::getPost('params');
+foreach ($_POST as $column => $rows) {
+    if (substr($column, 0, 11) != 'widget_col_') {
+        continue;
+    }
+    $col = (int)substr($column, 11);
+    foreach ($rows as $row => $widget) {
+        $id = (int)substr($widget, 7);
+        list($app, $name) = explode(':', $params[$id]['type']);
+        $layout[$row][$col] = array('app' => $app,
+                                    'height' => 1,
+                                    'width' => 1,
+                                    'params' => array('type' => $name,
+                                                      'params' => $params[$id]));
+    }
+}
+
+$prefs->setValue('portal_layout', serialize($layout));
diff --git a/drag_n_drop_portal/select.php b/drag_n_drop_portal/select.php
new file mode 100644 (file)
index 0000000..855f696
--- /dev/null
@@ -0,0 +1,36 @@
+<?php
+/**
+ * $Id: select.php 215 2008-01-10 19:32:18Z duck $
+ *
+ * Copyright Obala d.o.o. (www.obala.si)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author Duck <duck@obala.net>
+ * @package Folks
+ */
+define('HORDE_BASE', dirname(__FILE__) . '/..');
+require_once HORDE_BASE . '/lib/base.php';
+require_once 'Horde/Loader.php';
+
+?>
+<form>
+<select id="block_column">
+<option value="0"><?php echo _("First column") ?></option>
+<option value="1"><?php echo _("Second column") ?></option>
+<option value="2"><?php echo _("Third column") ?></option>
+</select>
+<br />
+<select id="block_selection" size="7">
+<?php
+$collection = Horde_Block_Collection::singleton();
+foreach ($collection->getBlocksList() as $id => $name) {
+    echo '<option value="' . $id  .'">' . $name . '</option>';
+}
+?>
+</select>
+<br />
+<input type="button" class="button" value="<?php echo _("Add") ?>" onclick="return addWidget()" />
+<input type="button" class="button" value="<?php echo _("Cancel") ?>" onclick="return cancelRedBox()" />
+</form>
diff --git a/drag_n_drop_portal/templates/portal/params.php b/drag_n_drop_portal/templates/portal/params.php
new file mode 100644 (file)
index 0000000..a917a10
--- /dev/null
@@ -0,0 +1,37 @@
+<h1 class="header"><?php echo htmlspecialchars($blocks->getName($app, $name)) ?></h1>
+<form action="<?php echo Horde::selfUrl() ?>#block" method="post" id="blockform" name="blockform">
+<?php Util::pformInput() ?>
+<input type="hidden" name="action" value="save" />
+<input type="hidden" name="block" value="<?php echo Util::getFormData('block') ?>" />
+<input type="hidden" name="widget" value="<?php echo Util::getFormData('widget') ?>" />
+<table style="width: 100%; border-collapse: collapse;">
+<?php if ($block->updateable): ?>
+<tr>
+    <td class="text rightAlign" valign="top"><?php echo Horde::label('params_refresh_time', _("Refresh rate:")) ?>&nbsp;</td>
+    <td class="text" valign="top">
+    <select id="params_refresh_time" name="params[_refresh_time]">
+        <option<?php if ($defaults['_refresh_time'] == 0) echo ' selected="selected"' ?> value="0"><?php echo _("Never") ?></option>
+        <option<?php if ($defaults['_refresh_time'] == 30) echo ' selected="selected"' ?> value="30"><?php echo _("Every 30 seconds") ?></option>
+        <option<?php if ($defaults['_refresh_time'] == 60) echo ' selected="selected"' ?> value="60"><?php echo _("Every minute") ?></option>
+        <option<?php if ($defaults['_refresh_time'] == 300) echo ' selected="selected"' ?> value="300"><?php echo _("Every 5 minutes") ?></option>
+        <option<?php if ($defaults['_refresh_time'] == 900) echo ' selected="selected"' ?> value="900"><?php echo _("Every 15 minutes") ?></option>
+        <option<?php if ($defaults['_refresh_time'] == 1800) echo ' selected="selected"' ?> value="1800"><?php echo _("Every half hour") ?></option>
+        <option<?php if ($defaults['_refresh_time'] == 3600) echo ' selected="selected"' ?> value="3600"><?php echo _("Every hour") ?></option>
+    </select>
+    </td>
+</tr>
+<?php endif; ?>
+<?php $i = 0; foreach ($params as $id): $i++; ?>
+    <tr>
+        <td class="<?php echo ($i % 2) ? 'text' : 'item0' ?> rightAlign" valign="top"><?php echo $blocks->getParamName($app, $name, $id) ?>:&nbsp;</td>
+        <td class="<?php echo ($i % 2) ? 'text' : 'item0' ?>" valign="top"><?php echo $blocks->getOptionsWidget($app, $name, $id, $defaults) ?></td>
+    </tr>
+<?php endforeach; ?>
+<tr>
+<td class="control" colspan="2" style="text-align: center;">
+<input type="button" class="button" value="<?php echo _("Save") ?>" onclick="return setParams()" />
+<input type="button" class="button" value="<?php echo _("Cancel") ?>" onclick="return cancelRedBox()" />
+</td>
+</tr>
+</table>
+</form>
diff --git a/drag_n_drop_portal/themes/graphics/bottom_left.gif b/drag_n_drop_portal/themes/graphics/bottom_left.gif
new file mode 100644 (file)
index 0000000..d324686
Binary files /dev/null and b/drag_n_drop_portal/themes/graphics/bottom_left.gif differ
diff --git a/drag_n_drop_portal/themes/graphics/bottom_right.gif b/drag_n_drop_portal/themes/graphics/bottom_right.gif
new file mode 100644 (file)
index 0000000..930a8cf
Binary files /dev/null and b/drag_n_drop_portal/themes/graphics/bottom_right.gif differ
diff --git a/drag_n_drop_portal/themes/graphics/delete.png b/drag_n_drop_portal/themes/graphics/delete.png
new file mode 100644 (file)
index 0000000..5d46660
Binary files /dev/null and b/drag_n_drop_portal/themes/graphics/delete.png differ
diff --git a/drag_n_drop_portal/themes/graphics/edit.png b/drag_n_drop_portal/themes/graphics/edit.png
new file mode 100644 (file)
index 0000000..5ac2275
Binary files /dev/null and b/drag_n_drop_portal/themes/graphics/edit.png differ
diff --git a/drag_n_drop_portal/themes/graphics/minus.png b/drag_n_drop_portal/themes/graphics/minus.png
new file mode 100644 (file)
index 0000000..3217046
Binary files /dev/null and b/drag_n_drop_portal/themes/graphics/minus.png differ
diff --git a/drag_n_drop_portal/themes/graphics/plus.png b/drag_n_drop_portal/themes/graphics/plus.png
new file mode 100644 (file)
index 0000000..263e356
Binary files /dev/null and b/drag_n_drop_portal/themes/graphics/plus.png differ
diff --git a/drag_n_drop_portal/themes/graphics/redbox_spinner.gif b/drag_n_drop_portal/themes/graphics/redbox_spinner.gif
new file mode 100644 (file)
index 0000000..35218b3
Binary files /dev/null and b/drag_n_drop_portal/themes/graphics/redbox_spinner.gif differ
diff --git a/drag_n_drop_portal/themes/graphics/reload.png b/drag_n_drop_portal/themes/graphics/reload.png
new file mode 100644 (file)
index 0000000..dcb0204
Binary files /dev/null and b/drag_n_drop_portal/themes/graphics/reload.png differ
diff --git a/drag_n_drop_portal/themes/graphics/tooltip_bg.png b/drag_n_drop_portal/themes/graphics/tooltip_bg.png
new file mode 100644 (file)
index 0000000..d851211
Binary files /dev/null and b/drag_n_drop_portal/themes/graphics/tooltip_bg.png differ
diff --git a/drag_n_drop_portal/themes/graphics/top_left.gif b/drag_n_drop_portal/themes/graphics/top_left.gif
new file mode 100644 (file)
index 0000000..dd665b9
Binary files /dev/null and b/drag_n_drop_portal/themes/graphics/top_left.gif differ
diff --git a/drag_n_drop_portal/themes/graphics/top_right.gif b/drag_n_drop_portal/themes/graphics/top_right.gif
new file mode 100644 (file)
index 0000000..e46ca39
Binary files /dev/null and b/drag_n_drop_portal/themes/graphics/top_right.gif differ
diff --git a/drag_n_drop_portal/themes/screen.css b/drag_n_drop_portal/themes/screen.css
new file mode 100644 (file)
index 0000000..c4f3274
--- /dev/null
@@ -0,0 +1,217 @@
+
+/* Redbox styles. */
+#RB_overlay {
+    position: absolute;
+    z-index: 100;
+    width: 100%;
+    height: 100%;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    min-height: 100%;
+    background-color: #000;
+    opacity: .6;
+    filter: alpha(opacity=60);
+}
+#RB_loading {
+    z-index: 101;
+    width: 66;
+    margin-left: auto;
+    margin-right: auto;
+    margin-top: 200px;
+    padding-bottom: 66px;
+    text-align: center;
+    background: url("graphics/redbox_spinner.gif") no-repeat bottom center;
+}
+#RB_window {
+    z-index: 102;
+    background-color: #fff;
+    display: block;
+    text-align: left;
+    overflow: hidden;
+    margin: 20px auto 0 auto;
+    position: absolute;
+}
+
+#RB_confirm {
+    width: 20em;
+    padding: 1em;
+    border: 1px solid #ccc;
+    background: #ffc;
+}
+#RB_confirm input {
+    margin: .2em;
+}
+
+#RB_info {
+    width: 30em;
+    padding: 1em;
+    border: 1px solid #ccc;
+    background: #ccf;
+}
+#RB_info input {
+    margin: .2em;
+}
+
+/* Portal editing */
+
+#page {
+  margin: 10px auto;
+}
+
+#page1 {  
+  float: left;
+  width: 45%;
+}
+
+#page2 {
+  float: right;
+  width: 45%;
+}
+
+#widget_col_0 { 
+  float: left;
+  width: 30%;
+  background: #E6E6E6;
+}
+
+#widget_col_1 {
+  width: 50%;
+  float: left;
+  background: #CCC;
+}   
+
+#widget_col_2 { 
+  float: left;
+  width: 20%;
+  background: #B3B3B3;
+}   
+
+#widget_col_3 { 
+  float: left;
+  width: 40%;
+  background: #B3B3B3;
+}   
+
+#widget_col_4 { 
+  float: left;
+  width: 60%;
+  background: #E6E6E6;
+}
+
+#control_buttons {
+  position: absolute;
+  right: 0px;
+  top: 10px;
+  width: 90px;
+}
+
+#reload_button {
+  position: relative;
+  float: left;
+  width: 16px;
+  height: 16px;
+  background: url(graphics/reload.png);
+  behavior: url(png.htc);
+  margin-right: 5px;
+}
+
+#minimize_button {
+  position: relative;
+  float: left;
+  width: 16px;
+  height: 16px;
+  background: url(graphics/minus.png);
+  behavior: url(png.htc);
+  margin-right: 5px;
+}
+
+#maximize_button {
+  position: relative;
+  float: left;
+  width: 16px;
+  height: 16px;
+  background: url(graphics/plus.png);
+  behavior: url(png.htc);
+  margin-right: 5px;
+}
+
+#edit_button {
+  position: relative;
+  float: left;
+  width: 16px;
+  height: 16px;
+  background: url(graphics/edit.png);
+  behavior: url(png.htc);
+  margin-right: 5px;
+}
+
+#delete_button {
+  position: relative;
+  float: left;
+  width: 16px;
+  height: 16px;
+  background: url(graphics/delete.png);
+  behavior: url(png.htc);
+  margin-right: 5px;
+}
+
+/* Sliding doors technique */
+.widget_nw {
+    background: transparent url(graphics/top_left.gif) no-repeat;
+    height: 30px;
+}
+
+.widget_w {
+  border-left: 1px solid #B9B9B9;
+  margin-left: 5px;
+}
+
+.widget_sw {
+    background: transparent url(graphics/bottom_left.gif) no-repeat;
+    height: 15px;
+}
+
+.widget_title {
+    background:  url(graphics/top_right.gif) repeat-x right top;
+    color: #123456;
+    font: bold 14px/25px Tahoma, Arial, sans-serif;
+    height: 26px;
+    margin: 0 0 0 15px;
+    padding: 5px 0 0 0 ;
+    text-align: center;
+    margin-left: 15px;
+}
+
+.widget_content {
+  background-color: #FDFDFD;
+  color: #71777A;
+  font: normal 12px/1em Tahoma, Arial, sans-serif;
+  overflow: hidden;
+  padding: 5px;
+  border-right: 1px solid #B9B9B9;
+  margin-right: 5px;
+}
+
+.widget_statusbar {
+    background: transparent url(graphics/bottom_right.gif) repeat-x right top;
+    font-size: 8px;
+    height: 15px;
+    margin-left: 11px;
+}
+
+.widget_draggable {
+  cursor: move;
+}
+
+/* Ghost */
+.widget_ghost {
+  background: #FFF;
+  opacity: 0.5;
+  filter: alpha(opacity=50);
+  position: relative;
+  border: 3px dashed #F00;
+  margin: 0px;
+  padding: 0;
+}