appy.gen: added a widget 'List' for rendering grids of data.

This commit is contained in:
Gaetan Delannay 2011-10-19 09:37:44 +02:00
parent f1136eb786
commit c11378c747
11 changed files with 315 additions and 41 deletions

View file

@ -65,6 +65,9 @@ img {border: 0;}
.list td, .list th { border: 1px solid grey;
padding-left: 5px; padding-right: 5px; padding-top: 3px;}
.list th { background-color: #cbcbcb; font-style: italic; font-weight: normal;}
.grid th { font-style: italic; font-weight: normal;
border-bottom: 2px solid grey; padding: 2px 2px;}
.grid td { padding-right: 5px; }
.noStyle { border: 0 !important; padding: 0 !important; margin: 0 !important; }
.noStyle td { border:0 !important; padding:0 !important; margin:0 !important; }
.translationLabel { background-color: #EAEAEA; border-bottom: 1px dashed grey;

View file

@ -492,3 +492,59 @@ function initTab(cookieId, defaultValue) {
if (!toSelect) { showTab(defaultValue) }
else { showTab(toSelect); }
}
// List-related Javascript functions
function updateRowNumber(row, rowIndex, action) {
/* Within p_row, we update every field whose name and id include the row index
with new p_rowIndex. If p_action is 'set', p_rowIndex becomes the new
index. If p_action is 'add', new index becomes:
existing index + p_rowIndex. */
tagTypes = ['input', 'select'];
currentIndex = -1;
for (var i=0; i < tagTypes.length; i++) {
widgets = row.getElementsByTagName(tagTypes[i]);
for (var j=0; j < widgets.length; j++) {
id = widgets[j].id;
name = widgets[j].name;
idNbIndex = id.lastIndexOf('*') + 1;
nameNbIndex = name.lastIndexOf('*') + 1;
// Compute the current row index if not already done.
if (currentIndex == -1) {
currentIndex = parseInt(id.substring(idNbIndex));
}
// Compute the new values for attributes "id" and "name".
newId = id.substring(0, idNbIndex);
newName = id.substring(0, nameNbIndex);
newIndex = rowIndex;
if (action == 'add') newIndex = newIndex + currentIndex;
widgets[j].id = newId + String(newIndex);
widgets[j].name = newName + String(newIndex);
}
}
}
function insertRow(tableId) {
// This function adds a new row in table with ID p_tableId.
table = document.getElementById(tableId);
newRow = table.rows[1].cloneNode(true);
newRow.style.display = 'table-row';
// Within newRow, I must include in field names and ids the row number
updateRowNumber(newRow, table.rows.length-3, 'set');
table.tBodies[0].appendChild(newRow);
}
function deleteRow(tableId, deleteImg) {
row = deleteImg.parentNode.parentNode;
table = document.getElementById(tableId);
allRows = table.rows;
toDeleteIndex = -1; // Will hold the index of the row to delete.
for (var i=0; i < allRows.length; i++) {
if (toDeleteIndex == -1) {
if (row == allRows[i]) toDeleteIndex = i;
}
else {
// Decrement higher row numbers by 1 because of the deletion
updateRowNumber(allRows[i], -1, 'add');
}
}
table.deleteRow(toDeleteIndex);
}

View file

@ -0,0 +1,72 @@
<tal:comment replace="nothing">Single row.</tal:comment>
<tr metal:define-macro="row"
tal:attributes="style python: (rowIndex==-1) and 'display: none' or ''">
<td align="center" tal:repeat="fieldInfo widget/fieldsd">
<tal:show define="widget python: fieldInfo[1];
tagCss python: 'noStyle';
widgetName python: '%s*%d' % (widget['name'], rowIndex)">
<metal:call use-macro="app/skyn/widgets/show/macros/field"/>
</tal:show>
</td>
<tal:comment replace="nothing">Icon for removing the row</tal:comment>
<td align="right" tal:condition="python: layoutType=='edit'">
<img style="cursor:pointer"
tal:attributes="src string:$appUrl/skyn/delete.png;
title python: 'Delete';
onClick python: 'deleteRow(\'list_%s\',this)' % name"/>
</td>
</tr>
<tal:comment replace="nothing">The whole table, edit or view.</tal:comment>
<table metal:define-macro="table" class="grid"
tal:define="isEdit python: layoutType == 'edit'"
tal:condition="python: isEdit or value"
tal:attributes="id python: 'list_%s' % name">
<tal:comment replace="nothing">Header</tal:comment>
<tr>
<th tal:repeat="fieldInfo widget/fieldsd"
tal:content="python: _(fieldInfo[1]['labelId'])">
</th>
<tal:comment replace="nothing">Icon for adding a new row.</tal:comment>
<th tal:condition="isEdit">
<img style="cursor:pointer"
tal:attributes="src string:$appUrl/skyn/plus.png;
title python: _('add_ref');
onClick python: 'insertRow(\'list_%s\')' % name"/>
</th>
</tr>
<tal:comment replace="nothing">Template row (edit only)</tal:comment>
<tal:templateRow define="rowIndex python:-1" condition="isEdit">
<metal:call use-macro="app/skyn/widgets/list/macros/row"/>
</tal:templateRow>
<tr height="7px"><td></td></tr>
<tal:comment replace="nothing">Rows of data</tal:comment>
<tal:rows define="rows python: inRequest and requestValue or value"
repeat="row rows">
<tal:row define="rowIndex repeat/row/index">
<metal:call use-macro="app/skyn/widgets/list/macros/row"/>
</tal:row>
</tal:rows>
</table>
<tal:comment replace="nothing">View</tal:comment>
<metal:view define-macro="view">
<metal:call use-macro="app/skyn/widgets/list/macros/table"/>
</metal:view>
<tal:comment replace="nothing">Edit</tal:comment>
<metal:edit define-macro="edit">
<tal:comment replace="nothing">
The following input makes Appy aware that this field is in the request.
</tal:comment>
<input type="hidden" tal:attributes="name name" value="">
<metal:call use-macro="app/skyn/widgets/list/macros/table"/>
</metal:edit>
<tal:comment replace="nothing">Cell</tal:comment>
<metal:cell define-macro="cell">
<metal:call use-macro="app/skyn/widgets/list/macros/table"/>
</metal:cell>
<tal:comment replace="nothing">Search</tal:comment>
<metal:search define-macro="search"></metal:search>

View file

@ -28,13 +28,13 @@
<tal:comment replace="nothing">Move up</tal:comment>
<img tal:condition="python: objectIndex &gt; 0"
tal:attributes="src string: $appUrl/skyn/arrowUp.png;
title python: tool.translate('move_up');
title python: _('move_up');
onClick python: ajaxBaseCall.replace('**v**', 'up')"
style="cursor:pointer"/>
<tal:comment replace="nothing">Move down</tal:comment>
<img tal:condition="python: objectIndex &lt; (totalNumber-1)"
tal:attributes="src string: $appUrl/skyn/arrowDown.png;
title python: tool.translate('move_down');
title python: _('move_down');
onClick python: ajaxBaseCall.replace('**v**', 'down')"
style="cursor:pointer"/>
</tal:moveRef>
@ -69,7 +69,7 @@
noFormCall python: navBaseCall.replace('**v**', '%d, \'CreateWithoutForm\'' % startNumber);
noFormCall python: test(appyType['addConfirm'], 'askConfirm(\'script\', &quot;%s&quot;, &quot;%s&quot;)' % (noFormCall, addConfirmMsg), noFormCall)"
tal:attributes="src string:$appUrl/skyn/plus.png;
title python: tool.translate('add_ref');
title python: _('add_ref');
onClick python: test(appyType['noForm'], noFormCall, formCall)"/>
</metal:plusIcon>
@ -108,6 +108,7 @@
ajaxHookId python: contextObj.UID()+fieldName;
startNumber python: int(request.get('%s_startNumber' % ajaxHookId, 0));
tool contextObj/getTool;
_ python: tool.translate;
refObjects python:contextObj.getAppyRefs(fieldName, startNumber);
objs refObjects/objects;
totalNumber refObjects/totalNumber;
@ -121,7 +122,7 @@
showPlusIcon python:not appyType['isBack'] and appyType['add'] and not maxReached and user.has_permission(addPermission, folder) and canWrite;
atMostOneRef python: (multiplicity[1] == 1) and (len(objs)&lt;=1);
label python: contextObj.translate('label', field=appyType);
addConfirmMsg python: appyType['addConfirm'] and tool.translate('%s_addConfirm' % appyType['labelId']) or '';
addConfirmMsg python: appyType['addConfirm'] and _('%s_addConfirm' % appyType['labelId']) or '';
navBaseCall python: 'askRefField(\'%s\',\'%s\',\'%s\',\'%s\',**v**)' % (ajaxHookId, contextObj.absolute_url(), fieldName, innerRef)">
<tal:comment replace="nothing">This macro displays the Reference widget on a "view" page.
@ -139,7 +140,7 @@
<tal:comment replace="nothing">If there is no object...</tal:comment>
<tal:noObject condition="not:objs">
<td tal:content="python: tool.translate('no_ref')"></td>
<td tal:content="python: _('no_ref')"></td>
<td><metal:plusIcon use-macro="app/skyn/widgets/ref/macros/plusIcon"/></td>
</tal:noObject>
@ -162,7 +163,7 @@
<tal:comment replace="nothing">The search icon if field is queryable</tal:comment>
<a tal:condition="appyType/queryable"
tal:attributes="href python: '%s/skyn/search?type_name=%s&ref=%s:%s' % (tool.absolute_url(), linkedPortalType, contextObj.UID(), appyType['name'])">
<img src="search.gif" tal:attributes="title python: tool.translate('search_objects')"/></a>
<img src="search.gif" tal:attributes="title python: _('search_objects')"/></a>
</legend>
<tal:comment replace="nothing">Object description</tal:comment>
@ -173,7 +174,7 @@
<metal:nav use-macro="here/skyn/navigate/macros/appyNavigate"/>
<tal:comment replace="nothing">No object is present</tal:comment>
<p tal:condition="not:objs" tal:content="python: tool.translate('no_ref')"></p>
<p tal:condition="not:objs" tal:content="python: _('no_ref')"></p>
<table width="100%" tal:condition="python: objs"
tal:attributes="class python:test(innerRef, 'innerAppyTable', '')">
@ -185,10 +186,10 @@
<tal:widgets define="widgets python: objs[0].getAppyTypesFromNames(appyType['shownInfo'])">
<tr tal:condition="appyType/showHeaders">
<th tal:repeat="widget widgets">
<span tal:content="python: tool.translate(widget['labelId'])"></span>
<span tal:content="python: _(widget['labelId'])"></span>
<metal:sortIcons use-macro="app/skyn/widgets/ref/macros/sortIcons" />
</th>
<th tal:content="python: tool.translate('ref_actions')"></th>
<th tal:content="python: _('ref_actions')"></th>
</tr>
<tal:row repeat="obj objs">
<tr valign="middle" tal:define="odd repeat/obj/odd"
@ -199,7 +200,7 @@
<metal:showObjectTitle use-macro="app/skyn/widgets/ref/macros/objectTitle"/>
</tal:title>
<tal:state condition="python: widget['name'] == 'state'"
content="python: tool.translate(obj.getWorkflowLabel())">
content="python: _(obj.getWorkflowLabel())">
</tal:state>
<tal:other condition="python: widget['name'] not in ('title', 'state')">
<tal:field define="contextObj python:obj;
@ -231,14 +232,13 @@
<tal:comment replace="nothing">Edit macro for an Ref.</tal:comment>
<metal:editRef define-macro="edit"
tal:condition="widget/link"
tal:define="rname python: 'appy_ref_%s' % name;
requestValue python: request.get(rname, []);
inRequest python: request.has_key(rname);
tal:define="requestValue python: request.get(name, []);
inRequest python: request.has_key(name);
allObjects python: contextObj.getSelectableAppyRefs(name);
refUids python: [o.UID() for o in contextObj.getAppyRefs(name)['objects']];
isBeingCreated python: contextObj.isTemporary() or ('/portal_factory/' in contextObj.absolute_url())">
<select tal:attributes="name rname;
<select tal:attributes="name name;
size python: test(isMultiple, widget['height'], '');
multiple python: test(isMultiple, 'multiple', '')">
<option tal:condition="not: isMultiple" i18n:translate="choose_a_value"></option>

View file

@ -9,23 +9,24 @@
contextMacro The base folder containing the macros to call for
rendering the elements within the layout.
Defaults to app.skyn
slaveId The name and id of the main tag for this layout (used
for master/slave relationships).
slaveCss The CSS class for a slave.
tagId The name and id of the main tag for this layout (used
a.o. for master/slave relationships).
tagCss Some additional CSS class for the main tag
(ie, the CSS class for a slave).
</tal:comment>
<metal:show define-macro="layout"
tal:define="contextMacro contextMacro| python: app.skyn;
slaveId slaveId|python:'';
slaveCss slaveCss|python:'';
tagId tagId|python:'';
tagCss tagCss|python:'';
layoutCss layout/css_class;">
<table tal:attributes="cellpadding layout/cellpadding;
cellspacing layout/cellspacing;
width layout/width;
align layout/align;
class python: slaveCss and ('%s %s' % (slaveCss, layoutCss)) or layoutCss;
class python: tagCss and ('%s %s' % (tagCss, layoutCss)) or layoutCss;
style layout/style;
id slaveId;
name slaveId;">
id tagId;
name tagId;">
<tal:comment replace="nothing">The table header row</tal:comment>
<tr tal:condition="layout/headerRow" tal:attributes="valign layout/headerRow/valign">
<th tal:repeat="cell layout/headerRow/cells"
@ -48,23 +49,27 @@
page The page where the widget lies
layoutType "edit"? "view"? "cell?"
widget The widget to render
Optionally:
widgetName If the field to show is within a List, we cheat and
include, within the widgetName, the row index.
</tal:comment>
<metal:field define-macro="field"
tal:define="contextMacro python: app.skyn.widgets;
layout python: widget['layouts'][layoutType];
name widget/name;
name widgetName| widget/name;
sync python: widget['sync'][layoutType];
rawValue python: contextObj.getFieldValue(name, onlyIfSync=True, layoutType=layoutType);
value python: contextObj.getFormattedFieldValue(name, rawValue);
requestValue python: request.get(name, None);
requestValue python: contextObj.getRequestFieldValue(name);
inRequest python: request.has_key(name);
errors errors | python: ();
inError python: (widget['name'] in errors) and True or False;
inError python: name in errors;
isMultiple python: (widget['multiplicity'][1] == None) or (widget['multiplicity'][1] &gt; 1);
masterCss python: widget['slaves'] and ('master_%s' % name) or '';
slaveCss python: widget['master'] and ('slave_%s_%s' % (widget['masterName'], '_'.join(widget['masterValue']))) or '';
slaveId python: widget['master'] and 'slave' or ''">
tagCss tagCss | slaveCss;
tagId python: widget['master'] and 'slave' or ''">
<metal:layout use-macro="here/skyn/widgets/show/macros/layout"/>
</metal:field>
@ -77,10 +82,10 @@
widget The widget to render
</tal:comment>
<metal:group define-macro="group"
tal:define="slaveCss python: widget['master'] and ('slave_%s_%s' % (widget['masterName'], '_'.join(widget['masterValue']))) or '';
tal:define="tagCss python: widget['master'] and ('slave_%s_%s' % (widget['masterName'], '_'.join(widget['masterValue']))) or '';
widgetCss widget/css_class;
groupCss python: slaveCss and ('%s %s' % (slaveCss, widgetCss)) or widgetCss;
slaveId python: widget['master'] and 'slave' or ''">
groupCss python: tagCss and ('%s %s' % (tagCss, widgetCss)) or widgetCss;
tagId python: widget['master'] and 'slave' or ''">
<fieldset tal:condition="python: widget['style'] == 'fieldset'">
<legend tal:condition="widget/hasLabel">
<i tal:content="structure python: contextObj.translate(widget['labelId'])"></i>
@ -98,7 +103,7 @@
<tal:asTabs condition="python: widget['style'] == 'tabs'">
<table tal:attributes="width widget/wide;
class groupCss;
id slaveId; name slaveId">
id tagId; name tagId">
<tal:comment replace="nothing">First row: the tabs.</tal:comment>
<tr valign="middle"><td style="border-bottom: 1px solid #ff8040">
<table style="position:relative; bottom:-1px;">
@ -150,7 +155,7 @@
class groupCss;
cellspacing widget/cellspacing;
cellpadding widget/cellpadding;
id slaveId; name slaveId">
id tagId; name tagId">
<tal:comment replace="nothing">Display the title of the group if it is not rendered a fieldset.</tal:comment>
<tr tal:condition="python: (widget['style'] != 'fieldset') and widget['hasLabel']">
<td tal:attributes="colspan python: len(widget['columnsWidths']);