[gen] Ergonomic improvements: added a breadcrumb, more compact design.

This commit is contained in:
Gaetan Delannay 2012-11-29 20:45:21 +01:00
parent 3ec1270fc2
commit 387fbaea7c
11 changed files with 137 additions and 99 deletions

View file

@ -1172,9 +1172,8 @@ class String(Type):
# Default width, height and maxChars vary according to String format # Default width, height and maxChars vary according to String format
if width == None: if width == None:
if format == String.TEXT: self.width = 60 if format == String.TEXT: self.width = 60
# This width corresponds to the standard width of an Appy page, # This width corresponds to the standard width of an Appy page.
# minus the portlet. if format == String.XHTML: self.width = 870
if format == String.XHTML: self.width = 750
else: self.width = 30 else: self.width = 30
if height == None: if height == None:
if format == String.TEXT: self.height = 5 if format == String.TEXT: self.height = 5

View file

@ -217,13 +217,13 @@ class Table(LayoutElement):
# Some base layouts to use, for fields and pages ------------------------------- # Some base layouts to use, for fields and pages -------------------------------
# The default layouts for pages # The default layouts for pages
defaultPageLayouts = { defaultPageLayouts = {
'view': Table('n!-w|-b|', align="center"), 'view': Table('w|-b|', align="center"),
'edit': Table('w|-b|', width=None)} 'edit': Table('w|-b|', width=None)}
# A layout for pages, containing the page summary. # A layout for pages, containing the page summary.
summaryPageLayouts = {'view': Table('s-n!-w|-b|', align="center"), summaryPageLayouts = {'view': Table('s-w|-b|', align="center"),
'edit': Table('w|-b|', width=None)} 'edit': Table('w|-b|', width=None)}
widePageLayouts = { widePageLayouts = {
'view': Table('n!-w|-b|', align="center"), 'view': Table('w|-b|', align="center"),
'edit': Table('w|-b|') 'edit': Table('w|-b|')
} }
# The default layout for fields. Alternative layouts may exist and are declared # The default layout for fields. Alternative layouts may exist and are declared

View file

@ -245,7 +245,10 @@ class ToolMixin(BaseMixin):
elems = sortMethod(elems) elems = sortMethod(elems)
return [importParams['headers'], elems] return [importParams['headers'], elems]
def showPortlet(self, context): def showPortlet(self, context, layoutType):
'''When must the portlet be shown?'''
# Not on 'edit' pages.
if layoutType == 'edit': return
if context.id == 'ui': context = context.getParentNode() if context.id == 'ui': context = context.getParentNode()
res = True res = True
if hasattr(context.aq_base, 'appy'): if hasattr(context.aq_base, 'appy'):
@ -424,17 +427,29 @@ class ToolMixin(BaseMixin):
return '<acronym title="%s">%s</acronym>' % \ return '<acronym title="%s">%s</acronym>' % \
(text, uText[:width].encode('utf-8') + '...') (text, uText[:width].encode('utf-8') + '...')
def getPublishedObject(self): def getLayoutType(self):
'''Guess the current layout type, according to actual URL.'''
actualUrl = self.REQUEST['ACTUAL_URL']
res = ''
if actualUrl.endswith('/ui/view'):
res = 'view'
elif actualUrl.endswith('/ui/edit') or actualUrl.endswith('/do'):
res = 'edit'
return res
def getPublishedObject(self, layoutType):
'''Gets the currently published object, if its meta_class is among '''Gets the currently published object, if its meta_class is among
application classes.''' application classes.'''
req = self.REQUEST # In some situations (ie, we are querying objects), the published object
# If we are querying object, there is no published object (the truth is: # according to Zope is the tool, but we don't want to consider it that
# the tool is the currently published object but we don't want to # way.
# consider it this way). if layoutType not in ('edit', 'view'): return
if not req['ACTUAL_URL'].endswith('/ui/view'): return
obj = self.REQUEST['PUBLISHED'] obj = self.REQUEST['PUBLISHED']
parent = obj.getParentNode() # If URL is a /do, published object is the "do" method.
if parent.id == 'ui': obj = parent.getParentNode() if type(obj) == types.MethodType: obj = obj.im_self
else:
parent = obj.getParentNode()
if parent.id == 'ui': obj = parent.getParentNode()
if obj.meta_type in self.getProductConfig().attributes: return obj if obj.meta_type in self.getProductConfig().attributes: return obj
def getZopeClass(self, name): def getZopeClass(self, name):

View file

@ -822,6 +822,11 @@ class BaseMixin:
break break
return phase return phase
else: else:
# Return an empty list if we have a single, link-free page within
# a single phase.
if (len(res) == 1) and (len(res[0]['pages']) == 1) and \
not res[0]['pagesInfo'][res[0]['pages'][0]].get('links'):
return None
return res return res
def getIcons(self): def getIcons(self):
@ -1434,7 +1439,16 @@ class BaseMixin:
# Not-Managers can't navigate back to the tool # Not-Managers can't navigate back to the tool
if (parent.id == 'config') and not self.getUser().has_role('Manager'): if (parent.id == 'config') and not self.getUser().has_role('Manager'):
return False return False
if parent.meta_type != 'Folder': return parent if parent.meta_type not in ('Folder', 'Temporary Folder'): return parent
def getBreadCrumb(self):
'''Gets breadcrumb info about this object and its parents.'''
res = [{'url': self.absolute_url(),
'title': self.getFieldValue('title', layoutType='view')}]
parent = self.getParent()
if parent:
res = parent.getBreadCrumb() + res
return res
def index_html(self): def index_html(self):
'''Redirects to /ui.''' '''Redirects to /ui.'''

View file

@ -57,6 +57,9 @@ img { border: 0; vertical-align: middle}
.userStripText { font-size: 110%; padding: 0 0.3em 0 0.3em; color: white } .userStripText { font-size: 110%; padding: 0 0.3em 0 0.3em; color: white }
.userStrip a { color: #e7e7e7 } .userStrip a { color: #e7e7e7 }
.userStrip a:visited { color: #e7e7e7 } .userStrip a:visited { color: #e7e7e7 }
.navigate { border-bottom: 1px solid #5F7983;
background-color: #dbdde1; font-weight: bold }
.navigate td { padding: 4px 9px }
.login { margin-top: 2px; margin-bottom: 2px; color: black;} .login { margin-top: 2px; margin-bottom: 2px; color: black;}
.buttons { margin-left: 4px;} .buttons { margin-left: 4px;}
.fakeButton { border: 1px solid #D7DEE4; background-color: #fde8e0; .fakeButton { border: 1px solid #D7DEE4; background-color: #fde8e0;
@ -73,7 +76,7 @@ img { border: 0; vertical-align: middle}
.lostPassword a { font-size: 90%; color: white; padding-left: 1em;} .lostPassword a { font-size: 90%; color: white; padding-left: 1em;}
.portlet { width: 150px; border-right: 1px solid #5F7983; .portlet { width: 150px; border-right: 1px solid #5F7983;
background-color: #ededed} background-color: #ededed}
.portletContent { margin: 9px; } .portletContent { margin: 4px 9px }
.portletTitle { font-weight: bold; font-size: 110%; margin-bottom: 4px;} .portletTitle { font-weight: bold; font-size: 110%; margin-bottom: 4px;}
.portletCurrent { font-weight: bold; } .portletCurrent { font-weight: bold; }
.portletSep { border-top: 1px solid #5F7983; margin-top: 2px;} .portletSep { border-top: 1px solid #5F7983; margin-top: 2px;}
@ -113,21 +116,20 @@ img { border: 0; vertical-align: middle}
text-align: center; color: white; } text-align: center; color: white; }
.odd { background-color: #f9f9f9; } .odd { background-color: #f9f9f9; }
.even { background-color: #f4f4f4; } .even { background-color: #f4f4f4; }
.summary {margin-bottom: 5px;} .summary { margin-bottom: 5px; background-color: #e9e9e9;
border: 1px dashed grey }
.objectTitle { font-size: 11pt; border-bottom: 3px solid grey; .objectTitle { font-size: 11pt; border-bottom: 3px solid grey;
font-weight: bold;} font-weight: bold;}
.by { padding-top: 5px; color: grey; font-size: 97% } .by { padding: 5px; color: grey; font-size: 97% }
.underTitle { background-color: #e9e9e9 }
.objectNavigate { margin-top: 3px;}
.underline { border-bottom: 1px dotted grey;} .underline { border-bottom: 1px dotted grey;}
.state { font-weight: bold; border-bottom: 1px dashed grey;} .state { font-weight: bold; border-bottom: 1px dashed grey;}
.historyLabel { font-variant: small-caps; font-weight: bold;} .historyLabel { font-variant: small-caps; font-weight: bold;}
.history td { border-top: 1px solid grey;} .history td { border-top: 1px solid grey; padding: 0 5px 0 5px }
.history th { font-style: italic; text-align; left;} .history th { font-style: italic; text-align: left; padding: 0 5px 0 5px }
.topSpace { margin-top: 15px;} .topSpace { margin-top: 15px;}
.bottomSpace { margin-bottom: 15px;} .bottomSpace { margin-bottom: 15px;}
.discreet { color: grey} .discreet { color: grey}
.pageLink { padding-left: 6px; font-style: italic} .pageLink { padding-left: 8px }
.footer { font-size: 95% } .footer { font-size: 95% }
.footer td { background-color: #CBCBC9; border-top: 1px solid grey; .footer td { background-color: #CBCBC9; border-top: 1px solid grey;
padding: 0.4em 1em 0.5em } padding: 0.4em 1em 0.5em }

View file

@ -1,10 +1,8 @@
<tal:main define="tool context/getTool"> <tal:main define="tool context/getTool">
<html metal:use-macro="context/ui/template/macros/main"> <html metal:use-macro="context/ui/template/macros/main">
<metal:fill fill-slot="content" <metal:fill fill-slot="content"
tal:define="contextObj context/getParentNode; tal:define="dummy python: contextObj.allows('Modify portal content', raiseError=True);
dummy python: contextObj.allows('Modify portal content', raiseError=True);
errors request/errors | python:{}; errors request/errors | python:{};
layoutType python:'edit';
layout python: contextObj.getPageLayout(layoutType); layout python: contextObj.getPageLayout(layoutType);
cssJs python: {}; cssJs python: {};
phaseInfo python: contextObj.getAppyPhases(currentOnly=True, layoutType=layoutType); phaseInfo python: contextObj.getAppyPhases(currentOnly=True, layoutType=layoutType);

View file

@ -46,51 +46,70 @@
</table> </table>
</div> </div>
<div metal:define-macro="objectNavigate" tal:condition="request/nav|nothing"> <tal:comment replace="nothing">
<tal:comment replace="nothing"> Buttons for going to next/previous elements if this one is among bunch of referenced or searched objects.
Buttons for going to next/previous elements if this one is among bunch of referenced or searched objects. currentNumber starts with 1.
currentNumber starts with 1. </tal:comment>
</tal:comment> <metal:nav define-macro="objectNavigate" tal:condition="request/nav|nothing">
<table class="objectNavigate" <div tal:define="navInfo tool/getNavigationInfo;
tal:define="navInfo tool/getNavigationInfo; currentNumber navInfo/currentNumber;
currentNumber navInfo/currentNumber; totalNumber navInfo/totalNumber;
totalNumber navInfo/totalNumber; firstUrl navInfo/firstUrl;
firstUrl navInfo/firstUrl; previousUrl navInfo/previousUrl;
previousUrl navInfo/previousUrl; nextUrl navInfo/nextUrl;
nextUrl navInfo/nextUrl; lastUrl navInfo/lastUrl;
lastUrl navInfo/lastUrl; sourceUrl navInfo/sourceUrl;
sourceUrl navInfo/sourceUrl; backText navInfo/backText">
backText navInfo/backText"> <tal:comment replace="nothing">Go to the source URL (search or referred object)</tal:comment>
<tr valign="middle"> <a tal:condition="sourceUrl" tal:attributes="href sourceUrl"><img
<tal:comment replace="nothing">Go to the source URL (search or referred object)</tal:comment> tal:define="gotoSource python: _('goto_source');
<td tal:condition="sourceUrl"><a tal:attributes="href sourceUrl"><img goBack python: backText and ('%s - %s' % (backText, gotoSource)) or gotoSource"
tal:define="gotoSource python: _('goto_source'); tal:attributes="src string: $appUrl/ui/gotoSource.png; title goBack"/></a>
goBack python: backText and ('%s - %s' % (backText, gotoSource)) or gotoSource" <tal:comment replace="nothing">Go to the first page</tal:comment>
tal:attributes="src string: $appUrl/ui/gotoSource.png; title goBack"/></a></td> <a tal:condition="firstUrl" tal:attributes="href firstUrl"><img
<tal:comment replace="nothing">Go to the first page</tal:comment> tal:attributes="src string: $appUrl/ui/arrowLeftDouble.png;
<td tal:condition="firstUrl"><a tal:attributes="href firstUrl"><img title python: _('goto_first')"/></a>
tal:attributes="src string: $appUrl/ui/arrowLeftDouble.png; <tal:comment replace="nothing">Go to the previous page</tal:comment>
title python: _('goto_first')"/></a></td> <a tal:condition="previousUrl" tal:attributes="href previousUrl"><img
<tal:comment replace="nothing">Go to the previous page</tal:comment> tal:attributes="src string: $appUrl/ui/arrowLeftSimple.png;
<td tal:condition="previousUrl"><a tal:attributes="href previousUrl"><img title python: _('goto_previous')"/></a>
tal:attributes="src string: $appUrl/ui/arrowLeftSimple.png; <tal:comment replace="nothing">Explain which element is currently shown</tal:comment>
title python: _('goto_previous')"/></a></td> <span class="discreet">&nbsp;
<tal:comment replace="nothing">Explain which element is currently shown</tal:comment> <span tal:replace="python: currentNumber"/>&nbsp;<b>//</b>
<td class="discreet">&nbsp; <span tal:replace="python: totalNumber"/>&nbsp;&nbsp;
<span tal:replace="python: currentNumber"/>&nbsp;<b>//</b> </span>
<span tal:replace="python: totalNumber"/>&nbsp;&nbsp; <tal:comment replace="nothing">Go to the next page</tal:comment>
</td> <a tal:condition="python: nextUrl" tal:attributes="href nextUrl"><img
<tal:comment replace="nothing">Go to the next page</tal:comment> tal:attributes="src string: $appUrl/ui/arrowRightSimple.png;
<td tal:condition="python: nextUrl"><a tal:attributes="href nextUrl"><img title python: _('goto_next')"/></a>
tal:attributes="src string: $appUrl/ui/arrowRightSimple.png; <tal:comment replace="nothing">Go to the last page</tal:comment>
title python: _('goto_next')"/></a></td> <a tal:condition="lastUrl" tal:attributes="href lastUrl"><img
<tal:comment replace="nothing">Go to the last page</tal:comment> tal:attributes="src string: $appUrl/ui/arrowRightDouble.png;
<td tal:condition="lastUrl"><a tal:attributes="href lastUrl"><img title python: _('goto_last')"/></a>
tal:attributes="src string: $appUrl/ui/arrowRightDouble.png; </div>
title python: _('goto_last')"/></a></td> </metal:nav>
</tr>
</table> <table metal:define-macro="navigationStrip" tal:condition="python: contextObj"
</div> width="100%" class="navigate">
<tr>
<td tal:define="breadcrumb contextObj/getBreadCrumb">
<tal:bc repeat="bc breadcrumb">
<tal:elem define="nb repeat/bc/number">
<tal:sep condition="python: nb != 1">
<img tal:attributes="src string: $appUrl/ui/to.png"/>
</tal:sep>
<tal:comment replace="nothing">Display only the title of the current object</tal:comment>
<span tal:condition="python: nb == len(breadcrumb)" tal:content="bc/title"></span>
<tal:comment replace="nothing">Display a link for parent objects</tal:comment>
<a tal:condition="python: nb != len(breadcrumb)" tal:attributes="href bc/url" tal:content="bc/title"></a>
</tal:elem>
</tal:bc>
</td>
<td align="right">
<metal:nav use-macro="app/ui/navigate/macros/objectNavigate"/>
</td>
</tr>
</table>
<tal:comment replace="nothing"> <tal:comment replace="nothing">
This macro displays up/down arrows in a table header column for sorting a given column. This macro displays up/down arrows in a table header column for sorting a given column.

View file

@ -183,14 +183,8 @@
<tal:comment replace="nothing">Information that is common to all tabs (object title, state, etc)</tal:comment> <tal:comment replace="nothing">Information that is common to all tabs (object title, state, etc)</tal:comment>
<table width="100%" class="summary"> <table width="100%" class="summary">
<tr> <tr>
<tal:comment replace="nothing">Title</tal:comment>
<td colspan="2" class="objectTitle">
<tal:icons replace="structure contextObj/getIcons"/>
<span tal:content="python: contextObj.getFieldValue('title', layoutType='view')"></span>
</td>
</tr>
<tr class="underTitle">
<td colspan="2" class="by"> <td colspan="2" class="by">
<tal:icons replace="structure contextObj/getIcons"/>
<tal:comment replace="nothing">Creator and last modification date</tal:comment> <tal:comment replace="nothing">Creator and last modification date</tal:comment>
<tal:comment replace="nothing">Plus/minus icon for accessing history</tal:comment> <tal:comment replace="nothing">Plus/minus icon for accessing history</tal:comment>
<tal:accessHistory condition="hasHistory"> <tal:accessHistory condition="hasHistory">
@ -225,7 +219,7 @@
</td> </td>
</tr> </tr>
<tal:comment replace="nothing">Object history</tal:comment> <tal:comment replace="nothing">Object history</tal:comment>
<tr tal:condition="hasHistory" class="underTitle"> <tr tal:condition="hasHistory">
<td colspan="2"> <td colspan="2">
<span id="appyHistory" <span id="appyHistory"
tal:attributes="style python:test(historyExpanded, 'display:block', 'display:none')"> tal:attributes="style python:test(historyExpanded, 'display:block', 'display:none')">

View file

@ -50,25 +50,19 @@
appUrl app/absolute_url; appUrl app/absolute_url;
currentSearch req/search|nothing; currentSearch req/search|nothing;
currentClass req/className|nothing; currentClass req/className|nothing;
contextObj tool/getPublishedObject; rootClasses tool/getRootClasses;
rootClasses tool/getRootClasses"> phases python: contextObj and contextObj.getAppyPhases() or None">
<div class="portletContent" tal:condition="python: contextObj and contextObj.mayNavigate()"> <tal:phases condition="python: contextObj and phases and contextObj.mayNavigate()">
<div class="portletTitle" tal:define="parent contextObj/getParent">
<span tal:replace="contextObj/Title"></span>
<a tal:condition="python: parent" tal:attributes="href parent/absolute_url">
<img tal:attributes="src string: $appUrl/ui/gotoSource.png"/>
</a>
</div>
<metal:phases use-macro="app/ui/portlet/macros/phases"/> <metal:phases use-macro="app/ui/portlet/macros/phases"/>
</div> </tal:phases>
<tal:comment replace="nothing">One section for every searchable root class.</tal:comment> <tal:comment replace="nothing">One section for every searchable root class.</tal:comment>
<tal:section repeat="rootClass python: [rc for rc in rootClasses if tool.userMaySearch(rc)]"> <tal:section repeat="rootClass python: [rc for rc in rootClasses if tool.userMaySearch(rc)]">
<tal:comment replace="nothing">A separator if required</tal:comment> <tal:comment replace="nothing">A separator if required</tal:comment>
<div class="portletSep" tal:define="nb repeat/rootClass/number" <div class="portletSep" tal:define="nb repeat/rootClass/number"
tal:condition="python: (nb == 1 and contextObj) or (nb != 1)"></div> tal:condition="python: (nb != 1) or ((nb == 1) and phases)"></div>
<div class="portletContent" tal:define="searchInfo python: tool.getGroupedSearches(rootClass)"> <div class="portletContent" tal:define="searchInfo python: tool.getGroupedSearches(rootClass)">
<tal:comment replace="nothing">Section title (link triggers the default search), with action icons</tal:comment> <tal:comment replace="nothing">Section title (link triggers the default search), with action icons</tal:comment>
@ -119,9 +113,8 @@
This macro displays, within the portlet, the navigation tree for the This macro displays, within the portlet, the navigation tree for the
currently shown object, made of phases and contained pages. currently shown object, made of phases and contained pages.
</tal:comment> </tal:comment>
<table metal:define-macro="phases" <table metal:define-macro="phases" class="portletContent"
tal:define="phases contextObj/getAppyPhases; tal:define="singlePhase python: phases and (len(phases) == 1);
singlePhase python: len(phases) == 1;
page python: req.get('page', 'main')"> page python: req.get('page', 'main')">
<tal:phase repeat="phase phases"> <tal:phase repeat="phase phases">
<tal:comment replace="nothing">The box containing phase-related information</tal:comment> <tal:comment replace="nothing">The box containing phase-related information</tal:comment>

View file

@ -10,6 +10,8 @@
req python: request; req python: request;
resp req/RESPONSE; resp req/RESPONSE;
lang tool/getUserLanguage; lang tool/getUserLanguage;
layoutType tool/getLayoutType;
contextObj python: tool.getPublishedObject(layoutType);
dir python: tool.getLanguageDirection(lang); dir python: tool.getLanguageDirection(lang);
dleft python: (dir == 'ltr') and 'left' or 'right'; dleft python: (dir == 'ltr') and 'left' or 'right';
dright python: (dir == 'ltr') and 'right' or 'left'; dright python: (dir == 'ltr') and 'right' or 'left';
@ -35,7 +37,7 @@
<tr class="top" metal:define-slot="top"> <tr class="top" metal:define-slot="top">
<td tal:define="bannerName python: (dir == 'ltr') and 'banner' or 'bannerrtl'" <td tal:define="bannerName python: (dir == 'ltr') and 'banner' or 'bannerrtl'"
tal:attributes="style python: 'background-image: url(%s/ui/%s.jpg)' % (appUrl, bannerName)"> tal:attributes="style python: 'background-image: url(%s/ui/%s.jpg)' % (appUrl, bannerName)">
<table width="100%"> <table width="100%" style="margin-top: 4px">
<tr valign="top" tal:define="pages tool/getMainPages"> <tr valign="top" tal:define="pages tool/getMainPages">
<tal:comment replace="nothing">Links to main pages</tal:comment> <tal:comment replace="nothing">Links to main pages</tal:comment>
<td tal:condition="pages"><a tal:repeat="page pages" class="pageLink" <td tal:condition="pages"><a tal:repeat="page pages" class="pageLink"
@ -183,12 +185,16 @@
</table> </table>
</td> </td>
</tr> </tr>
<tal:comment replace="nothing">The navigation strip</tal:comment>
<tr tal:condition="python: contextObj and (layoutType == 'view')">
<td><metal:navigate use-macro="app/ui/navigate/macros/navigationStrip"/></td>
</tr>
<tr> <tr>
<td> <td>
<table width="100%" cellpadding="0" cellspacing="0"> <table width="100%" cellpadding="0" cellspacing="0">
<tr valign="top"> <tr valign="top">
<tal:comment replace="nothing">Portlet</tal:comment> <tal:comment replace="nothing">Portlet</tal:comment>
<td tal:condition="python: tool.showPortlet(context)" class="portlet"> <td tal:condition="python: tool.showPortlet(context, layoutType)" class="portlet">
<metal:portlet use-macro="app/ui/portlet/macros/portlet"/> <metal:portlet use-macro="app/ui/portlet/macros/portlet"/>
</td> </td>
<tal:comment replace="nothing">Page content</tal:comment> <tal:comment replace="nothing">Page content</tal:comment>

View file

@ -1,10 +1,8 @@
<tal:main define="tool context/config"> <tal:main define="tool context/config">
<html metal:use-macro="context/ui/template/macros/main"> <html metal:use-macro="context/ui/template/macros/main">
<metal:fill fill-slot="content" <metal:fill fill-slot="content"
tal:define="contextObj python: context.getParentNode(); tal:define="dummy python: contextObj.allows('View', raiseError=True);
dummy python: contextObj.allows('View', raiseError=True);
errors python: req.get('errors', {}); errors python: req.get('errors', {});
layoutType python:'view';
layout python: contextObj.getPageLayout(layoutType); layout python: contextObj.getPageLayout(layoutType);
phaseInfo python: contextObj.getAppyPhases(currentOnly=True, layoutType='view'); phaseInfo python: contextObj.getAppyPhases(currentOnly=True, layoutType='view');
phase phaseInfo/name; phase phaseInfo/name;