Home progress:

- Fix missing size for new widgets
- Different styling for single entries
- Custom template for single addressbook entries
- Hideable nextmatch header
This commit is contained in:
Nathan Gray 2014-11-18 23:46:58 +00:00
parent ea8ff86854
commit b626fd1a88
9 changed files with 332 additions and 117 deletions

View File

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8"?>
<overlay>
<template id="addressbook.home.link" template="" lang="" group="0" version="1.9.001">
<grid>
<columns>
<column width="70"/>
<column/>
</columns>
<rows>
<row span="all">
<hbox height="100px">
<image src="image" class="photo"/>
<vbox>
<description id="n_fn" class="addressbook_sidebox_name"/>
<description id="org_name" class="addressbook_sidebox_org"/>
<description id="org_unit"/>
<description id="adr_one_locality"/>
</vbox>
</hbox>
</row>
<row>
<description span="2" value="Phone numbers" class="addressbook_sidebox_header"/>
</row>
<row>
<description for="tel_work" value="Business"/>
<url-phone id="tel_work" readonly="true"/>
</row>
<row>
<description for="tel_cell" value="Mobile phone"/>
<url-phone id="tel_cell" readonly="true"/>
</row>
<row>
<description for="tel_home" value="Private"/>
<url-phone id="tel_home" readonly="true"/>
</row>
<row>
<description for="tel_fax" value="Fax"/>
<url-phone id="tel_fax" readonly="true"/>
</row>
<row>
<description span="2" value="EMail &amp; Internet" class="addressbook_sidebox_header"/>
</row>
<row>
<description for="email" value="EMail"/>
<url-email id="email" readonly="true"/>
</row>
<row>
<description for="url" value="URL"/>
<url id="url" readonly="true"/>
</row>
</rows>
</grid>
</template>
</overlay>

View File

@ -110,6 +110,7 @@ var et2_portlet = et2_valueWidget.extend(
// Create DOM nodes // Create DOM nodes
this.div = $j(document.createElement("div")) this.div = $j(document.createElement("div"))
.addClass(this.options.class)
.addClass("ui-widget ui-widget-content ui-corner-all") .addClass("ui-widget ui-widget-content ui-corner-all")
.addClass("et2_portlet") .addClass("et2_portlet")
/* Gridster */ /* Gridster */

View File

@ -11,6 +11,9 @@
* @version $Id$ * @version $Id$
*/ */
/**
* A single entry is displayed with its application icon and title
*/
class home_link_portlet extends home_portlet class home_link_portlet extends home_portlet
{ {
@ -24,6 +27,12 @@ class home_link_portlet extends home_portlet
*/ */
protected $title = 'Link'; protected $title = 'Link';
/**
* Image shown. Leave at false to have it automatically set an icon based
* on the entry or customize it for the context.
*/
protected $image = false;
/** /**
* Base name for template * Base name for template
* @var string * @var string
@ -48,6 +57,8 @@ class home_link_portlet extends home_portlet
{ {
$this->title = $context['entry']['title'] = egw_link::title($context['entry']['app'], $context['entry']['id']); $this->title = $context['entry']['title'] = egw_link::title($context['entry']['app'], $context['entry']['id']);
// Reload to get the latest title
// TODO: This is a performance hit, it would be good to do this less
$need_reload |= (boolean)$context['entry']['id']; $need_reload |= (boolean)$context['entry']['id'];
} }
$this->context = $context; $this->context = $context;
@ -82,27 +93,79 @@ class home_link_portlet extends home_portlet
public function exec($id = null, etemplate_new &$etemplate = null) public function exec($id = null, etemplate_new &$etemplate = null)
{ {
// Check for custom template for app // Check for custom template for app
$custom_template = false;
if($this->context && $this->context['entry'] && $this->context['entry']['app'] && if($this->context && $this->context['entry'] && $this->context['entry']['app'] &&
$etemplate->read($this->context['entry']['app'] . '.' . $this->template_name)) $etemplate->read($this->context['entry']['app'] . '.' . $this->template_name))
{ {
// No action needed, custom template loaded as side-effect // No action needed, custom template loaded as side-effect
$custom_template = true;
} }
else else
{ {
$etemplate->read($this->template_name); $etemplate->read($this->template_name);
} }
$etemplate->set_dom_id($id);
$content = array(
'image' => $this->image
);
// Try to load entry
if($this->context['entry'] && $this->context['entry']['app'])
{
try
{
$classname = $this->context['entry']['app'] . '_egw_record';
if(class_exists($classname))
{
$record = new $classname($this->context['entry']['id']);
if($record)
{
// If there's a custom template, send the full record
if($custom_template)
{
$content += $record->get_record_array();
}
if($content['image'] == false)
{
$content['image'] = $record->get_icon();
}
}
}
}
catch(Exception $e)
{
error_log("Problem loading record " . array2string($this->context['entry']));
throw $e;
}
// Set a fallback image
if($content['image'] == false)
{
if($this->context['entry'] && $this->context['entry']['app'])
{
$content['image'] = $this->context['entry']['app'] . '/navbar';
}
else
{
$content['image'] = 'home';
}
}
}
// Filemanager support - links need app = 'file' and type set // Filemanager support - links need app = 'file' and type set
if($this->context && $this->context['entry'] && $this->context['entry']['app'] == 'filemanager') if($this->context && $this->context['entry'] && $this->context['entry']['app'] == 'filemanager')
{ {
$this->context['entry']['app'] = 'file'; $this->context['entry']['app'] = 'file';
$this->context['entry']['path'] = $this->context['entry']['title'] = $this->context['entry']['id']; $this->context['entry']['path'] = $this->context['entry']['title'] = $this->context['entry']['id'];
$this->context['entry']['type'] = egw_vfs::mime_content_type($this->context['entry']['id']); $this->context['entry']['type'] = egw_vfs::mime_content_type($this->context['entry']['id']);
$content['image'] = egw_framework::link('/etemplate/thumbnail.php',array('path' => $this->context['entry']['id']));
} }
$etemplate->set_dom_id($id); $content += $this->context;
$content = $this->context;
if(!is_array($content['entry'])) if(!is_array($content['entry']))
{ {
$content['entry'] = null; $content['entry'] = null;

View File

@ -171,7 +171,13 @@ app.classes.home = AppJS.extend(
* Add a new portlet from the context menu * Add a new portlet from the context menu
*/ */
add: function(action, source) { add: function(action, source) {
var attrs = {id: this._create_id(), row: 1, col: 1}; // Basic portlet attributes
var attrs = {
id: this._create_id(),
class: action.data.class,
width: this.DEFAULT.WIDTH,
height: this.DEFAULT.HEIGHT
};
// Try to put it about where the menu was opened // Try to put it about where the menu was opened
if(action.menu_context) if(action.menu_context)
@ -187,7 +193,7 @@ app.classes.home = AppJS.extend(
portlet.loadingFinished(); portlet.loadingFinished();
// Get actual attributes & settings, since they're not available client side yet // Get actual attributes & settings, since they're not available client side yet
portlet._process_edit(et2_dialog.OK_BUTTON, {class: action.id}); portlet._process_edit(et2_dialog.OK_BUTTON, attrs);
// Set up sorting/grid of new portlet // Set up sorting/grid of new portlet
var $portlet_container = $j(this.portlet_container.getDOMNode()); var $portlet_container = $j(this.portlet_container.getDOMNode());
@ -212,7 +218,12 @@ app.classes.home = AppJS.extend(
var $portlet_container = $j(this.portlet_container.getDOMNode()); var $portlet_container = $j(this.portlet_container.getDOMNode());
// Basic portlet attributes // Basic portlet attributes
var attrs = {id: this._create_id()}; var attrs = {
id: this._create_id(),
class: action.data.class || action.id.substr(5),
width: this.DEFAULT.WIDTH,
height: this.DEFAULT.HEIGHT
};
// Try to find where the drop was // Try to find where the drop was
if(action != null && action.ui && action.ui.position) if(action != null && action.ui && action.ui.position)
@ -239,7 +250,7 @@ app.classes.home = AppJS.extend(
drop_data.push(source[i].data); drop_data.push(source[i].data);
} }
} }
portlet._process_edit(et2_dialog.OK_BUTTON, {dropped_data: drop_data, class: action.data.class || action.id.substr(5)}); portlet._process_edit(et2_dialog.OK_BUTTON, jQuery.extend({dropped_data: drop_data},attrs));
// Set up sorting/grid of new portlet // Set up sorting/grid of new portlet
$portlet_container.data("gridster").add_widget( $portlet_container.data("gridster").add_widget(
@ -287,14 +298,6 @@ app.classes.home = AppJS.extend(
egw().open(widget.options.settings.entry, "", 'edit'); 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 * Set up the drag / drop / re-order of portlets
*/ */
@ -395,118 +398,119 @@ app.classes.home = AppJS.extend(
/** /**
* Functions for the list portlet * Functions for the list portlet
*/ */
List: /**
{ * For list_portlet - opens a dialog to add a new entry to the list
/** *
* For list_portlet - opens a dialog to add a new entry to the list * @param {egwAction} action Drop or add action
*/ * @param {egwActionObject[]} Selected entries
add_link: function(action, source, target_action) { * @param {egwActionObject} target_action Drop target
// Actions got confused drop vs popup */
if(source[0].id == 'portlets') add_link: function(action, source, target_action) {
{ // Actions got confused drop vs popup
return this.add_link(action); if(source[0].id == 'portlets')
} {
return this.add_link(action);
}
// Get widget // Get widget
var widget = null; var widget = null;
while(action.parent != null) while(action.parent != null)
{
if(action.data && action.data.widget)
{ {
if(action.data && action.data.widget) widget = action.data.widget;
{ break;
widget = action.data.widget;
break;
}
action = action.parent;
} }
if(target_action == null) action = action.parent;
{ }
// use template base url from initial template, to continue using webdav, if that was loaded via webdav if(target_action == null)
var splitted = 'home.edit'.split('.'); {
var path = app.home.portlet_container.getRoot()._inst.template_base_url + splitted.shift() + "/templates/default/" + // use template base url from initial template, to continue using webdav, if that was loaded via webdav
splitted.join('.')+ ".xet"; var splitted = 'home.edit'.split('.');
var dialog = et2_createWidget("dialog",{ var path = app.home.portlet_container.getRoot()._inst.template_base_url + splitted.shift() + "/templates/default/" +
callback: function(button_id, value) { splitted.join('.')+ ".xet";
if(button_id == et2_dialog.CANCEL_BUTTON) return; var dialog = et2_createWidget("dialog",{
var new_list = widget.options.settings.list || []; callback: function(button_id, value) {
for(var i = 0; i < new_list.length; i++) if(button_id == et2_dialog.CANCEL_BUTTON) return;
{ var new_list = widget.options.settings.list || [];
if(new_list[i].app == value.add.app && new_list[i].id == value.add.id) for(var i = 0; i < new_list.length; i++)
{
// Duplicate - skip it
return;
}
}
value.add.link_id = value.add.app + ':' + value.add.id;
// Update server side
new_list.push(value.add);
widget._process_edit(button_id,{list: new_list});
// Update client side
widget.getWidgetById('list').set_value(new_list);
},
buttons: et2_dialog.BUTTONS_OK_CANCEL,
title: app.home.egw.lang('add'),
template:path,
value: { content: [{label: app.home.egw.lang('add'),type: 'link-entry',name: 'add',size:''}]}
});
}
else
{
// Drag'n'dropped something on the list - just send action IDs
var new_list = widget.options.settings.list || [];
var changed = false;
for(var i = 0; i < new_list.length; i++)
{
// Avoid duplicates
for(var j = 0; j < source.length; j++)
{ {
if(!source[j].id || new_list[i].app+"::"+new_list[i].id == source[j].id) if(new_list[i].app == value.add.app && new_list[i].id == value.add.id)
{ {
// Duplicate - skip it // Duplicate - skip it
source.splice(j,1); return;
} }
} }
} value.add.link_id = value.add.app + ':' + value.add.id;
for(var i = 0; i < source.length; i++) // Update server side
new_list.push(value.add);
widget._process_edit(button_id,{list: new_list});
// Update client side
widget.getWidgetById('list').set_value(new_list);
},
buttons: et2_dialog.BUTTONS_OK_CANCEL,
title: app.home.egw.lang('add'),
template:path,
value: { content: [{label: app.home.egw.lang('add'),type: 'link-entry',name: 'add',size:''}]}
});
}
else
{
// Drag'n'dropped something on the list - just send action IDs
var new_list = widget.options.settings.list || [];
var changed = false;
for(var i = 0; i < new_list.length; i++)
{
// Avoid duplicates
for(var j = 0; j < source.length; j++)
{ {
var explode = source[i].id.split('::'); if(!source[j].id || new_list[i].app+"::"+new_list[i].id == source[j].id)
new_list.push({app: explode[0],id: explode[1], link_id: explode.join(':')});
changed = true;
}
if(changed)
{
widget._process_edit(et2_dialog.OK_BUTTON,{
list: new_list || {}
});
}
// Filemanager support - links need app = 'file' and type set
for(var i = 0; i < new_list.length; i++)
{
if(new_list[i]['app'] == 'filemanager')
{ {
new_list[i]['app'] = 'file'; // Duplicate - skip it
new_list[i]['path'] = new_list[i]['title'] = new_list[i]['icon'] = new_list[i]['id']; source.splice(j,1);
} }
} }
widget.getWidgetById('list').set_value(new_list);
} }
}, for(var i = 0; i < source.length; i++)
{
var explode = source[i].id.split('::');
new_list.push({app: explode[0],id: explode[1], link_id: explode.join(':')});
changed = true;
}
if(changed)
{
widget._process_edit(et2_dialog.OK_BUTTON,{
list: new_list || {}
});
}
// Filemanager support - links need app = 'file' and type set
for(var i = 0; i < new_list.length; i++)
{
if(new_list[i]['app'] == 'filemanager')
{
new_list[i]['app'] = 'file';
new_list[i]['path'] = new_list[i]['title'] = new_list[i]['icon'] = new_list[i]['id'];
}
}
/** widget.getWidgetById('list').set_value(new_list);
* Remove a link from the list
*/
link_change: function(list, link_id, row) {
// Quick response client side
row.slideUp(row.remove);
// Actual removal
var portlet = list._parent._parent;
portlet.options.settings.list.splice(row.index(), 1);
portlet._process_edit(et2_dialog.OK_BUTTON,{list: portlet.options.settings.list || {}});
} }
}, },
/**
* Remove a link from the list
*/
link_change: function(list, link_id, row) {
// Quick response client side
row.slideUp(row.remove);
// Actual removal
var portlet = list._parent._parent;
portlet.options.settings.list.splice(row.index(), 1);
portlet._process_edit(et2_dialog.OK_BUTTON,{list: portlet.options.settings.list || {}});
},
/** /**
* Functions for the note portlet * Functions for the note portlet
*/ */
@ -543,5 +547,33 @@ app.classes.home = AppJS.extend(
id: id, id: id,
height: window_height - 70 height: window_height - 70
}),'home_'+id, window_width+'x'+window_height,'home'); }),'home_'+id, window_width+'x'+window_height,'home');
},
/**
* Favorites / nextmatch
*/
/**
* Toggle the nextmatch header shown / hidden
*
* @param {Event} event
* @param {et2_button} widget
*/
nextmatch_toggle_header: function(event, widget) {
widget.set_image(widget.options.image == 'arrow_down' ? 'arrow_left' : 'arrow_down');
// We operate on the DOM here, nm should be unaware of our fiddling
var nm = widget.getParent().getWidgetById('nm');
if(!nm) return;
var header = nm.header;
var header_height = header.div.innerHeight();
// Hide header
nm.div.toggleClass('header_hidden');
header_height -= header.div.height();
// Grow row space - I have no idea why it needs to be 25 pixels instead of header_height
var scroll_height = $j('.egwGridView_scrollarea',nm.getDOMNode()).height();
$j('.egwGridView_scrollarea',nm.getDOMNode()).height(scroll_height + (header_height > 0 ? 25 : -25));
nm.resize();
} }
}); });

View File

@ -2,6 +2,9 @@
* Home CSS * Home CSS
*/ */
/**
* Basic layout and structural CSS
*/
#home-index_home-index { #home-index_home-index {
height:100%; height:100%;
} }
@ -20,8 +23,8 @@
cursor: pointer; cursor: pointer;
} }
.et2_portlet.ui-widget-content { .et2_portlet.ui-widget-content > div {
overflow: hidden;
} }
.et2_portlet.ui-widget-content > div:last-of-type { .et2_portlet.ui-widget-content > div:last-of-type {
/* Allow space for header, as the whole portlet is sized by auto-generated css */ /* Allow space for header, as the whole portlet is sized by auto-generated css */
@ -29,13 +32,11 @@
bottom: 0px; bottom: 0px;
top: 20px; top: 20px;
width: 100%; width: 100%;
overflow: hidden;
} }
.et2_portlet .et2_container { .et2_portlet .et2_container {
height: 100%; height: 100%;
} }
.et2_portlet.ui-widget-content > div:last-of-type > div {
background: linear-gradient(to bottom, rgba(255,255,255,.9) 10%,rgba(255,255,255,.75) 90%) /* W3C */
}
/* Gridster */ /* Gridster */
#portlets { #portlets {
@ -54,3 +55,65 @@
border: 1px solid silver; border: 1px solid silver;
position: absolute; position: absolute;
} }
/**
* Portlet styling (cosmetic)
*/
.et2_portlet.ui-widget-content > div:last-of-type > div {
background: linear-gradient(to bottom, rgba(255,255,255,.9) 10%,rgba(255,255,255,.75) 90%) /* W3C */
}
/* Single entry */
.et2_portlet.home_link_portlet > .ui-widget-header {
display: none;
position: relative;
top: -20px;
}
.et2_portlet.home_link_portlet:hover > .ui-widget-header {
display: block;
z-index: 90;
}
.et2_portlet.home_link_portlet.ui-widget-content > div:last-of-type {
top: 0px
}
.et2_portlet.home_link_portlet > div:last-of-type > div {
padding: 10px;
}
/* Thumbnail / icon */
.et2_portlet.home_link_portlet > div:last-of-type img:first-of-type {
float: left;
margin-right: 8px;
margin-bottom: 8px;
width: 32px;
height: 32px;
}
/* Favorite / nextmatch */
.et2_portlet .et2_container > div > .et2_button {
float: left;
margin-bottom: -16px;
min-height: 16px;
min-width: 16px;
}
.et2_portlet .et2_nextmatch.header_hidden .nextmatch_header {
min-height: 6px;
}
.et2_portlet .et2_nextmatch.header_hidden .nextmatch_header .ui-helper-reset{
height: 0px;
padding: 0px;
}
.et2_portlet .et2_nextmatch.header_hidden .nextmatch_header div.nextmatch_header_row .header_count {
top: -11px;
height: 14px;
}
.et2_portlet .et2_nextmatch.header_hidden .nextmatch_header div.nextmatch_header_row .header_count span {
top: 0px;
}
.et2_portlet .et2_nextmatch.header_hidden .nextmatch_header div.nextmatch_header_row div.search,
.et2_portlet .et2_nextmatch.header_hidden .nextmatch_header div.nextmatch_header_row label,
.et2_portlet .et2_nextmatch.header_hidden .nextmatch_header div.nextmatch_header_row select {
display:none;
}
.et2_portlet .et2_nextmatch.header_hidden .egwGridView_outer:first-of-type {
position: absolute;
}

View File

@ -2,6 +2,7 @@
<!-- $Id$ --> <!-- $Id$ -->
<overlay> <overlay>
<template id="home.favorite" template="" lang="" group="0" version="1.9.001"> <template id="home.favorite" template="" lang="" group="0" version="1.9.001">
<nextmatch id="nm"/> <button id="header_toggle" image="arrow_left" onclick="app.home.nextmatch_toggle_header"/>
<nextmatch id="nm" class="header_hidden"/>
</template> </template>
</overlay> </overlay>

View File

@ -13,7 +13,7 @@
<row> <row>
<box id="portlets"> <box id="portlets">
<!-- Box wrapper needed to get box to auto-repeat --> <!-- Box wrapper needed to get box to auto-repeat -->
<box id="${row}"><portlet id="${_cont[id]}" title="${_cont[title]}" color="@color" parent_node="home-index_portlets" settings="@settings" width="@width" height="@height" row="@row" col="@col" value="@content"/></box> <box id="${row}"><portlet id="${_cont[id]}" title="${_cont[title]}" color="@color" parent_node="home-index_portlets" settings="@settings" width="@width" height="@height" row="@row" col="@col" value="@content" class="@class"/></box>
</box> </box>
</row> </row>
</rows> </rows>

View File

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<overlay> <overlay>
<template id="home.link" template="" lang="" group="0" version="1.9.001"> <template id="home.link" template="" lang="" group="0" version="1.9.001">
<image id="image" src="image"/>
<link id="entry"/> <link id="entry"/>
</template> </template>
</overlay> </overlay>

View File

@ -2,6 +2,6 @@
<!-- $Id$ --> <!-- $Id$ -->
<overlay> <overlay>
<template id="home.list" template="" lang="" group="0" version="1.9.001"> <template id="home.list" template="" lang="" group="0" version="1.9.001">
<link-list id="list" onchange="app.home.List.link_change"/> <link-list id="list" onchange="app.home.link_change"/>
</template> </template>
</overlay> </overlay>