diff --git a/home/inc/class.home_link_portlet.inc.php b/home/inc/class.home_link_portlet.inc.php
index ab48f42bda..5c0da52539 100644
--- a/home/inc/class.home_link_portlet.inc.php
+++ b/home/inc/class.home_link_portlet.inc.php
@@ -66,11 +66,11 @@ class home_link_portlet extends home_portlet
/**
* Get a fragment of HTML for display
*
- * @param content Array Values returned from a submit, if any
- * @param context Settings for customizing the portlet
+ * @param id String unique ID, provided to the portlet so it can make sure content is
+ * unique, if needed.
* @return string HTML fragment for display
*/
- public function get_content()
+ public function get_content($id = null)
{
return $this->title;
}
@@ -124,6 +124,7 @@ class home_link_portlet extends home_portlet
)
);
$actions['view']['enabled'] = (bool)$this->context['entry'];
+
return $actions;
}
}
diff --git a/home/inc/class.home_list_portlet.inc.php b/home/inc/class.home_list_portlet.inc.php
new file mode 100644
index 0000000000..e7dc057e02
--- /dev/null
+++ b/home/inc/class.home_list_portlet.inc.php
@@ -0,0 +1,178 @@
+title = $context['title'];
+ }
+ // Add a new entry to the list
+ if($context['add'])
+ {
+ $context['list'][] = $context['add'];
+ unset($context['add']);
+ }
+
+ $this->context = $context;
+ }
+
+ /**
+ * Some descriptive information about the portlet, so that users can decide if
+ * they want it or not, and for inclusion in lists, hover text, etc.
+ *
+ * These should be already translated, no further translation will be done.
+ *
+ * @return Array with keys
+ * - displayName: Used in lists
+ * - title: Put in the portlet header
+ * - description: A short description of what this portlet does or displays
+ */
+ public function get_description()
+ {
+ return array(
+ 'displayName'=> 'List of entries',
+ 'title'=> $this->title,
+ 'description'=> lang('Show a list of entries')
+ );
+ }
+
+ /**
+ * Get a fragment of HTML for display
+ *
+ * @param id String unique ID, provided to the portlet so it can make sure content is
+ * unique, if needed.
+ * @return string HTML fragment for display
+ */
+ public function get_content($id = null)
+ {
+ $list = array();
+ foreach($this->context['list'] as $link_id => $link)
+ {
+ $list[] = $link + array(
+ 'title' => egw_link::title($link['app'], $link['id']),
+ 'icon' => egw_link::get_registry($link['app'], 'icon')
+ );
+ }
+
+ // Find the portlet widget, and add a link-list to it
+ return "";
+ }
+
+ /**
+ * Return a list of settings to customize the portlet.
+ *
+ * Settings should be in the same style as for preferences. It is OK to return an empty array
+ * for no customizable settings.
+ *
+ * These should be already translated, no further translation will be done.
+ *
+ * @see preferences/inc/class.preferences_settings.inc.php
+ * @return Array of settings. Each setting should have the following keys:
+ * - name: Internal reference
+ * - type: Widget type for editing
+ * - label: Human name
+ * - help: Description of the setting, and what it does
+ * - default: Default value, for when it's not set yet
+ */
+ public function get_properties()
+ {
+ return array(
+ array(
+ 'name' => 'title',
+ 'type' => 'textbox',
+ 'label' => lang('Title'),
+ ),
+ // Internal
+ array(
+ 'name' => 'list'
+ )
+ ) + parent::get_properties();
+ }
+
+ /**
+ * Return a list of allowable actions for the portlet.
+ *
+ * These actions will be merged with the default porlet actions.
+ * We add an 'edit' action as default so double-clicking the widget
+ * opens the entry
+ */
+ public function get_actions()
+ {
+ $actions = array(
+ 'add' => array(
+ 'icon' => 'add',
+ 'caption' => lang('add'),
+ 'hideOnDisabled' => false,
+ 'onExecute' => 'javaScript:app.home.add_link',
+ ),
+ 'add_drop' => array(
+ 'type' => 'drop',
+ 'caption' => lang('add'),
+ 'onExecute' => 'javaScript:app.home.add_link',
+ 'acceptedTypes' => array('file') + array_keys($GLOBALS['egw_info']['apps']),
+ )
+ );
+ return $actions;
+ }
+
+ /**
+ * List portlet displays multiple entries, so it makes sense to accept multiple dropped entries
+ */
+ public function accept_multiple()
+ {
+ return true;
+ }
+}
diff --git a/home/inc/class.home_portlet.inc.php b/home/inc/class.home_portlet.inc.php
index 3d77eb397f..1ac535daa7 100644
--- a/home/inc/class.home_portlet.inc.php
+++ b/home/inc/class.home_portlet.inc.php
@@ -49,11 +49,11 @@ abstract class home_portlet
/**
* Get a fragment of HTML for display
*
- * @param content Array Values returned from a submit, if any
- * @param context Settings for customizing the portlet
+ * @param id String unique ID, provided to the portlet so it can make sure content is
+ * unique, if needed.
* @return string HTML fragment for display
*/
- public abstract function get_content();
+ public abstract function get_content($id = null);
/**
* Return a list of settings to customize the portlet.
@@ -71,7 +71,8 @@ abstract class home_portlet
* - help: Description of the setting, and what it does
* - default: Default value, for when it's not set yet
*/
- public function get_properties() {
+ public function get_properties()
+ {
// Include the common attributes, or they won't get saved
$properties = array();
foreach(self::$common_attributes as $prop)
@@ -84,8 +85,18 @@ abstract class home_portlet
/**
* Return a list of allowable actions for the portlet.
*
- * These actions will be merged with the default porlet actions. Use the
+ * These actions will be merged with the default portlet actions. Use the
* same id / key to override the default action.
*/
public abstract function get_actions();
+
+ /**
+ * If this portlet can accept, display, or otherwise handle multiple
+ * EgroupWare entries. Used for drag and drop processing. How the entries
+ * are handled are up to the portlet.
+ */
+ public function accept_multiple()
+ {
+ return false;
+ }
}
diff --git a/home/inc/class.home_ui.inc.php b/home/inc/class.home_ui.inc.php
index f19d8f6081..1daa07f4a1 100644
--- a/home/inc/class.home_ui.inc.php
+++ b/home/inc/class.home_ui.inc.php
@@ -65,31 +65,29 @@ class home_ui
'caption' => 'Add',
'onExecute' => 'javaScript:app.home.add',
'children' => $portlets
- ),
- 'drop_create' => array(
- 'caption' => 'Add',
- 'type' => 'drop',
- 'acceptedTypes' => array('file') + array_keys($GLOBALS['egw_info']['apps']),
- 'onExecute' => 'javaScript:app.home.add_from_drop',
)
);
+ // Add all known portlets as drop actions too. If there are multiple matches, there will be a menu
+ $drop_execute = 'javaScript:app.home.add_from_drop';
foreach($portlets as $app => $children)
{
- // Home portlets
+ // Home portlets - uses link system, so all apps that support that are accepted
if(!$children['children'])
{
- $children['onExecute'] = $actions['drop_create']['onExecute'];
- $children['acceptedTypes'] = egw_link::app_list();
- $actions[$app] = $children;
+ $children['onExecute'] = $drop_execute;
+ $children['acceptedTypes'] = array_keys(egw_link::app_list());
+ $children['type'] = 'drop';
+ $actions["drop_$app"] = $children;
}
else
{
foreach($children as $portlet => $app_portlets)
{
- $app_portlets['onExecute'] = $actions['drop_create']['onExecute'];
+ $app_portlets['onExecute'] = $drop_execute;
$app_portlet['acceptedTypes'] = $app;
- $actions[$portlet] = $app_portlets;
+ $app_portlet['type'] = 'drop';
+ $actions["drop_$portlet"] = $app_portlets;
}
}
}
@@ -117,7 +115,7 @@ class home_ui
{
$content = '';
$attrs = array();
- $this->get_portlet($context, $content, $attrs);
+ $this->get_portlet($id, $context, $content, $attrs);
$portlets[$id] = $content;
$attributes[$id] = $attrs;
@@ -138,7 +136,7 @@ class home_ui
* @param attributes Array Settings that can be customized on a per-portlet basis - will be set
* @return home_portlet The portlet object that created the content
*/
- protected function get_portlet(&$context, &$content, &$attributes)
+ protected function get_portlet($id, &$context, &$content, &$attributes)
{
if(!$context['class']) $context['class'] = 'home_link_portlet';
@@ -146,14 +144,20 @@ class home_ui
$portlet = new $classname($context);
$desc = $portlet->get_description();
- $content = $portlet->get_content();
+ $content = $portlet->get_content($id);
- // Exclude common attributes changed through UI
- $settings = $portlet->get_properties() + $context;
+ // Exclude common attributes changed through UI and settings lacking a type
+ $settings = $portlet->get_properties();
+ foreach($settings as $key => $setting)
+ {
+ if(is_array($setting) && !array_key_exists('type',$setting)) unset($settings[$key]);
+ }
+ $settings += $context;
foreach(home_portlet::$common_attributes as $attr)
{
unset($settings[$attr]);
}
+
$attributes = array(
'title' => $desc['title'],
'settings' => $settings,
@@ -223,7 +227,8 @@ class home_ui
'id' => $portlet,
'caption' => $desc['displayName'],
'hint' => $desc['description'],
- 'onExecute' => 'javaScript:app.home.add'
+ 'onExecute' => 'javaScript:app.home.add',
+ 'allowOnMultiple' => $instance->accept_multiple()
);
}
}
@@ -259,12 +264,12 @@ class home_ui
}
else
{
-error_log(array2string($attributes));
-error_log(array2string($values));
// Get portlet settings, and merge new with old
$content = '';
- $portlet = $this->get_portlet(array_merge((array)$attributes, $values), $content, $attributes);
- $context = array('class' => get_class($portlet));
+ $context = $values+(array)$portlets[$portlet_id]; //array('class'=>$attributes['class']);
+ $portlet = $this->get_portlet($portlet_id, $context,$content, $attributes);
+
+ $context['class'] = get_class($portlet);
foreach($portlet->get_properties() as $property)
{
if($values[$property['name']])
@@ -277,14 +282,12 @@ error_log(array2string($values));
}
}
+
// Update client side
$update = array('content' => $content, 'attributes' => $attributes);
// New portlet? Flag going straight to edit mode
- if(!array_key_exists($portlet_id,$portlets) && $attributes['settings'])
- {
- $update['edit_settings'] = true;
- }
+ //$update['edit_settings'] = true;
$response->data($update);
// Store for preference update
diff --git a/home/js/app.js b/home/js/app.js
index ba088751b7..06d8b99574 100644
--- a/home/js/app.js
+++ b/home/js/app.js
@@ -1,10 +1,10 @@
/**
- * EGroupware - Filemanager - Javascript UI
+ * EGroupware - Home - Javascript UI
*
* @link http://www.egroupware.org
- * @package filemanager
- * @author Ralf Becker
- * @copyright (c) 2008-13 by Ralf Becker
+ * @package home
+ * @author Nathan Gray
+ * @copyright (c) 2013 Nathan Gray
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @version $Id$
*/
@@ -14,7 +14,6 @@
/*egw:uses
jquery.jquery;
jquery.jquery-ui;
- /phpgwapi/js/jquery/shapeshift/core/jquery.shapeshift.js;
/phpgwapi/js/jquery/gridster/jquery.gridster.js;
*/
@@ -25,7 +24,7 @@
*
*
* Uses Gridster for the grid layout
- * @see https://github.com/dustmoo/gridster.js
+ * @see http://gridster.net
* @augments AppJS
*/
app.home = AppJS.extend(
@@ -35,6 +34,19 @@ app.home = AppJS.extend(
*/
appname: "home",
+ /**
+ * Grid resolution. Must match et2_portlet GRID
+ */
+ GRID: 50,
+
+ /**
+ * Default size for new portlets
+ */
+ DEFAULT: {
+ WIDTH: 2,
+ HEIGHT: 1
+ },
+
/**
* Constructor
*
@@ -93,12 +105,12 @@ app.home = AppJS.extend(
* Add a new portlet from the context menu
*/
add: function(action) {
- var attrs = {id: this._create_id(), class: action.id};
+ var attrs = {id: this._create_id()};
var portlet = et2_createWidget('portlet',attrs, this.portlet_container);
portlet.loadingFinished();
// Get actual attributes & settings, since they're not available client side yet
- portlet._process_edit(et2_dialog.OK_BUTTON, {});
+ portlet._process_edit(et2_dialog.OK_BUTTON, {class: action.id});
// Set up sorting/grid of new portlet
var $portlet_container = $j(this.portlet_container.getDOMNode());
@@ -111,7 +123,25 @@ app.home = AppJS.extend(
* User dropped something on home. Add a new portlet
*/
add_from_drop: function(action,source,target_action) {
- var attrs = {id: this._create_id(), class: action.id};
+
+ // Actions got confused drop vs popup
+ if(source[0].id == 'portlets')
+ {
+ return this.add(action);
+ }
+
+ var $portlet_container = $j(this.portlet_container.getDOMNode());
+
+ // Basic portlet attributes
+ var attrs = {id: this._create_id()};
+
+ // Try to find where the drop was
+ if(action != null && action.ui && action.ui.position)
+ {
+ attrs.row = Math.round((action.ui.offset.top - $portlet_container.offset().top )/ this.GRID);
+ attrs.col = Math.max(0,Math.round((action.ui.offset.left - $portlet_container.offset().left) / this.GRID)-1);
+ }
+
var portlet = et2_createWidget('portlet',attrs, this.portlet_container);
portlet.loadingFinished();
@@ -121,14 +151,14 @@ app.home = AppJS.extend(
{
if(source[i].id) drop_data.push(source[i].id);
}
- portlet._process_edit(et2_dialog.OK_BUTTON, {dropped_data: drop_data});
+ portlet._process_edit(et2_dialog.OK_BUTTON, {dropped_data: drop_data, class: action.id.substr(5)});
// Set up sorting/grid of new portlet
- var $portlet_container = $j(this.portlet_container.getDOMNode());
$portlet_container.data("gridster").add_widget(
- portlet.getDOMNode()
+ portlet.getDOMNode(),
+ this.DEFAULT.WIDTH, this.DEFAULT.HEIGHT,
+ attrs.col, attrs.row
);
- console.log(this,arguments);
},
/**
@@ -156,6 +186,14 @@ app.home = AppJS.extend(
egw().open(widget.options.settings.entry, "", 'edit');
},
+ /**
+ * For list_portlet - adds a new link
+ * This is needed here so action system can find it
+ */
+ add_link: function(action,source,target_action) {
+ this.List.add_link(action, source, target_action);
+ },
+
/**
* Set up the drag / drop / re-order of portlets
*/
@@ -164,13 +202,11 @@ app.home = AppJS.extend(
$portlet_container
.addClass("home ui-helper-clearfix")
.disableSelection()
- /* Shapeshift
- .shapeshift();
- */
/* Gridster */
.gridster({
widget_selector: 'div.et2_portlet',
- widget_base_dimensions: [45, 45],
+ // Dimensions + margins = grid spacing
+ widget_base_dimensions: [this.GRID-5, this.GRID-5],
widget_margins: [5,5],
extra_rows: 1,
extra_cols: 1,
@@ -205,13 +241,13 @@ app.home = AppJS.extend(
var widget = window.app.home.portlet_container.getWidgetById(changed[key].id);
if(!widget || widget == window.app.home.portlet_container) continue;
- egw().json("home.home_ui.ajax_set_properties",[widget.id, widget.options.settings,{
+ egw().jsonq("home.home_ui.ajax_set_properties",[widget.id, widget.options.settings,{
row: changed[key].row,
col: changed[key].col
}],
null,
widget, true, widget
- ).sendRequest();
+ );
}
}
}
@@ -224,8 +260,8 @@ app.home = AppJS.extend(
.on("resizestop", function(event, ui) {
$portlet_container.data("gridster").resize_widget(
ui.element,
- Math.round(ui.size.width / 50),
- Math.round(ui.size.height / 50)
+ Math.round(ui.size.width / this.GRID),
+ Math.round(ui.size.height / this.GRID)
);
});
},
@@ -243,5 +279,109 @@ app.home = AppJS.extend(
}
while(this.portlet_container.getWidgetById(id));
return id;
+ },
+
+ /**
+ * Functions for the list portlet
+ */
+ List:
+ {
+ /**
+ * List uses mostly JS to generate its content, so we just do it on the JS side by
+ * returning a call to this function as the HTML content.
+ *
+ * @param id String The ID of the portlet
+ * @param list_values Array List of information passed to the link widget
+ */
+ set_content: function(id, list_values)
+ {
+ var portlet = app.home.portlet_container.getWidgetById(id);
+ if(portlet != null)
+ {
+ var list = portlet.getWidgetById(id+'-list');
+ if(list)
+ {
+ // List was just rudely pulled from DOM by the call to HTML, put it back
+ portlet.content.append(list.getDOMNode());
+ }
+ else
+ {
+ // Create widget
+ list = et2_createWidget('link-list', {id: id+'-list'}, portlet);
+ list.doLoadingFinished();
+ // Abuse link list by overwriting delete handler
+ list._delete_link = app.home.List.delete_link;
+ }
+ list.set_value(list_values);
+
+ // Disable link list context menu
+ $j('tr',list.list).unbind('contextmenu');
+
+ // Allow scroll bars
+ portlet.content.css('overflow', 'auto');
+ }
+ },
+
+
+ /**
+ * For list_portlet - opens a dialog to add a new entry to the list
+ */
+ add_link: function(action, source, target_action) {
+ // Actions got confused drop vs popup
+ if(source[0].id == 'portlets')
+ {
+ return this.add_link(action);
+ }
+
+ // Get widget
+ var widget = null;
+ while(action.parent != null)
+ {
+ if(action.data && action.data.widget)
+ {
+ widget = action.data.widget;
+ break;
+ }
+ action = action.parent;
+ }
+ if(typeof source == undefined)
+ {
+ var link = et2_createWidget('link-entry', {label: egw.lang('Add')}, this.portlet_container);
+ var dialog = et2_dialog.show_dialog(
+ function(button_id) {
+ widget._process_edit(button_id,{list: widget.options.settings.list || {}, add: link.getValue()});
+ link.destroy();
+ },
+ 'Add',
+ egw.lang('Add'), {},
+ et2_dialog.BUTTONS_OK_CANCEL
+ );
+ dialog.set_message(link.getDOMNode());
+ }
+ else
+ {
+ // Drag'n'dropped something on the list - just send action IDs
+ var drop_data = [];
+ for(var i = 0; i < source.length; i++)
+ {
+ if(source[i].id) drop_data.push(source[i].id);
+ }
+ widget._process_edit(et2_dialog.BUTTONS_OK_CANCEL,{
+ list: widget.options.settings.list || {},
+ dropped_data: drop_data
+ });
+ }
+ },
+
+ /**
+ * Remove a link from the list
+ */
+ delete_link: function(undef, row) {
+ // Quick response
+ row.slideUp(row.remove);
+ // Actual removal
+ this._parent.options.settings.list.splice(row.index(), 1);
+ this._parent._process_edit(et2_dialog.OK_BUTTON,{list: this._parent.options.settings.list || {}});
+ }
}
});
diff --git a/home/templates/default/app.css b/home/templates/default/app.css
index 2d7324a1c3..97da2d2cc6 100644
--- a/home/templates/default/app.css
+++ b/home/templates/default/app.css
@@ -17,6 +17,9 @@
.et2_portlet.ui-widget-content {
overflow: hidden;
}
+.et2_portlet.ui-widget-content > div:last-of-type {
+ height: 100%;
+}
/* Shapeshift
#portlets {