Added an AJAX framework within appy.gen, and its first use: a pagination mechanism for producing paginated references in the reference widget.

This commit is contained in:
Gaetan Delannay 2009-10-25 21:42:08 +01:00
parent 4c4b2d0f87
commit 605c42d94e
20 changed files with 546 additions and 187 deletions

25
gen/plone25/skin/ajax.pt Normal file
View file

@ -0,0 +1,25 @@
<tal:comment replace="nothing">
This page is called by a XmlHttpRequest object. It requires parameters "page" and "macro":
they are used to call the macro that will render the HTML chunk to be returned to the browser.
It also requires parameters "objectUid", which is the UID of the related object. The object will
be available to the macro as "contextObj".
It can also have a parameter "action", that refers to a method that will be triggered on
contextObj before returning the result of the macro to the browser.
</tal:comment>
<tal:ajax define="page request/page;
macro request/macro;
macroPath python: 'here/%s/macros/%s' % (page, macro);
contextObj python: context.uid_catalog(UID=request['objectUid'])[0].getObject();
action request/action|nothing;
response request/RESPONSE;
member context/portal_membership/getAuthenticatedMember;
portal context/portal_url/getPortalObject;
portal_url context/portal_url/getPortalPath;
dummy python:response.setHeader('Content-Type','text/html;;charset=utf-8');
dummy2 python:response.setHeader('Expires', 'Mon, 11 Dec 1975 12:05:05 GMT');
dummy3 python:response.setHeader('CacheControl', 'no-cache')">
<tal:executeAction condition="action">
<tal:do define="dummy python: contextObj.getAppyAttribute('on'+action)()" omit-tag=""/>
</tal:executeAction>
<metal:callMacro use-macro="python: context.get(page).macros.get(macro)"/>
</tal:ajax>

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 B

View file

@ -74,7 +74,7 @@
<form name="edit_form" method="post" enctype="multipart/form-data"
class="enableUnloadProtection atBaseEditForm"
tal:attributes="action python: contextObj.absolute_url()+'/skyn/do'">
<div metal:use-macro="here/skyn/macros/macros/listFields" />
<div metal:use-macro="here/skyn/macros/macros/listFields" /><br/>
<input type="hidden" name="action" value="Update"/>
<input type="hidden" name="fieldset" tal:attributes="value fieldset"/>
<input type="hidden" name="pageName" tal:attributes="value pageName"/>

View file

@ -107,7 +107,10 @@
<input type="hidden" name="action" value="ExecuteAppyAction"/>
<input type="hidden" name="objectUid" tal:attributes="value contextObj/UID"/>
<input type="hidden" name="fieldName" tal:attributes="value field/getName"/>
<input type="submit" name="do" tal:attributes="value label"/>
<input type="submit" name="do" tal:attributes="value label" onClick="javascript:;"/>
<tal:comment replace="nothing">The previous onClick is simply used to prevent Plone
from adding a CSS class that displays a popup when the user triggers the form multiple
times.</tal:comment>
</form>
</div>
@ -115,7 +118,8 @@
tal:define="field fieldDescr/atField|widgetDescr/atField;
appyType fieldDescr/appyType|widgetDescr/appyType;
showLabel showLabel|python:True;
label python: tool.translate(field.widget.label_msgid);
labelId field/widget/label_msgid;
label python: tool.translate(labelId);
descrId field/widget/description_msgid|python:'';
description python: tool.translate(descrId)"
tal:attributes="class python: contextObj.getCssClasses(appyType, asSlave=True)">
@ -157,8 +161,7 @@
<tal:comment replace="nothing">For other fields like Refs we use specific view/edit macros.</tal:comment>
<tal:viewRef condition="python: (not isEdit) and (appyType['type'] == 'Ref')">
<tal:ref define="isBack python:False;
fieldRel python:field.relationship;
objs python:contextObj.getAppyRefs(field.getName());
fieldName field/getName;
innerRef innerRef|python:False">
<metal:viewRef use-macro="here/skyn/ref/macros/showReference" />
</tal:ref>
@ -186,10 +189,9 @@
<div metal:define-macro="showBackwardField"
tal:define="isBack python:True;
appyType widgetDescr/appyType;
fieldRel widgetDescr/fieldRel;
objs python:contextObj.getBRefs(fieldRel);
label python:contextObj.translate('%s_%s_back' % (contextObj.meta_type, appyType['backd']['attribute']));
description python:'';
fieldName widgetDescr/fieldRel;
labelId python: '%s_%s_back' % (contextObj.meta_type, appyType['backd']['attribute']);
descrId python: '';
innerRef innerRef|python:False">
<div metal:use-macro="here/skyn/ref/macros/showReference" />
</div>
@ -309,6 +311,63 @@
<tal:comment replace="nothing">"Static" javascripts</tal:comment>
<script language="javascript">
<!--
// AJAX machinery
var xhrObjects = new Array(); // An array of XMLHttpRequest objects
function XhrObject() { // Wraps a XmlHttpRequest object
this.freed = 1; // Is this xhr object already dealing with a request or not?
this.xhr = false;
if (window.XMLHttpRequest) this.xhr = new XMLHttpRequest();
else this.xhr = new ActiveXObject("Microsoft.XMLHTTP");
this.hook = ''; // The ID of the HTML element in the page that will be
// replaced by result of executing the Ajax request.
}
function getAjaxChunk(pos) {
// This function is the callback called by the AJAX machinery (see function
// askAjaxChunk below) when an Ajax response is available.
// First, find back the correct XMLHttpRequest object
if ( (typeof(xhrObjects[pos]) != 'undefined') &&
(xhrObjects[pos].freed == 0)) {
var hook = xhrObjects[pos].hook;
if (xhrObjects[pos].xhr.readyState == 1) {
// The request has been initialized: display the waiting radar
var hookElem = document.getElementById(hook);
if (hookElem) hookElem.innerHTML = "<div align=\"center\"><img src=\"skyn/waiting.gif\"/><\/div>";
}
if (xhrObjects[pos].xhr.readyState == 4) {
// We have received the HTML chunk
var hookElem = document.getElementById(hook);
if (hookElem && (xhrObjects[pos].xhr.status == 200)) {
hookElem.innerHTML = xhrObjects[pos].xhr.responseText;
}
xhrObjects[pos].freed = 1;
}
}
}
function askAjaxChunk(hook, url) {
// This function will ask to get a chunk of HTML on the server by
// triggering a XMLHttpRequest.
// First, get a non-busy XMLHttpRequest object.
var pos = -1;
for (var i=0; i < xhrObjects.length; i++) {
if (xhrObjects[i].freed == 1) { pos = i; break; }
}
if (pos == -1) {
pos = xhrObjects.length;
xhrObjects[pos] = new XhrObject();
}
xhrObjects[pos].hook = hook;
if (xhrObjects[pos].xhr) {
xhrObjects[pos].freed = 0;
// Perform the asynchronous HTTP GET
xhrObjects[pos].xhr.open('GET', url, true);
xhrObjects[pos].xhr.onreadystatechange = function() { getAjaxChunk(pos); }
if (window.XMLHttpRequest) { xhrObjects[pos].xhr.send(null); }
else if (window.ActiveXObject) { xhrObjects[pos].xhr.send(); }
}
}
// Function used by checkbox widgets for having radio-button-like behaviour
function toggleCheckbox(visibleCheckbox, hiddenBoolean) {
vis = document.getElementById(visibleCheckbox);
@ -805,3 +864,47 @@
<metal:phases use-macro="here/skyn/macros/macros/phases"/>
</dt>
</metal:portletContent>
<tal:comment replace="nothing">
Buttons for navigating among a list of elements (next, back, first, last, etc).
</tal:comment>
<metal:appyNavigate define-macro="appyNavigate" tal:condition="python: totalNumber &gt; batchSize">
<table cellpadding="0" cellspacing="0" align="right" class="appyNav"
tal:define="baseUrl python: contextObj.getUrl('showRef', **{'fieldName': fieldName, 'isBack': isBack, 'innerRef': innerRef, 'labelId': labelId, 'descrId': descrId}) + '&%s_startNumber=' % ajaxHookId">
<tr>
<tal:comment replace="nothing">Go to the first page</tal:comment>
<td><img style="cursor:pointer" tal:condition="python: (startNumber != 0) and (startNumber != batchSize)"
tal:attributes="src string: $portal_url/skyn/arrowLeftDouble.png;
title python: tool.translate('goto_first');
onClick python: 'askAjaxChunk(\'%s\', \'%s\')' % (ajaxHookId, baseUrl+'0')"/></td>
<tal:comment replace="nothing">Go to the previous page</tal:comment>
<td><img style="cursor:pointer" tal:condition="python: startNumber != 0"
tal:define="sNumber python: startNumber - batchSize"
tal:attributes="src string: $portal_url/skyn/arrowLeftSimple.png;
title python: tool.translate('goto_previous');
onClick python: 'askAjaxChunk(\'%s\', \'%s\')' % (ajaxHookId, baseUrl+str(sNumber))"/></td>
<tal:comment replace="nothing">Explain which elements are currently shown</tal:comment>
<td class="discreet">&nbsp;
<span tal:replace="python: startNumber+1"/>
<img tal:attributes="src string: $portal_url/skyn/to.png"/>
<span tal:replace="python: startNumber+len(objs)"/>&nbsp;&nbsp;
</td>
<tal:comment replace="nothing">Go to the next page</tal:comment>
<td><img style="cursor:pointer" tal:condition="python: sNumber &lt; totalNumber"
tal:define="sNumber python: startNumber + batchSize"
tal:attributes="src string: $portal_url/skyn/arrowRightSimple.png;
title python: tool.translate('goto_next');
onClick python: 'askAjaxChunk(\'%s\', \'%s\')' % (ajaxHookId, baseUrl+str(sNumber))"/></td>
<tal:comment replace="nothing">Go to the last page</tal:comment>
<td><img style="cursor:pointer" tal:condition="python: (startNumber != sNumber) and (startNumber != sNumber-batchSize)"
tal:define="lastPageIsIncomplete python: totalNumber % batchSize;
nbOfCompletePages python: totalNumber/batchSize;
nbOfCountedPages python: test(lastPageIsIncomplete, nbOfCompletePages, nbOfCompletePages-1);
sNumber python: (nbOfCountedPages*batchSize)"
tal:attributes="src string: $portal_url/skyn/arrowRightDouble.png;
title python: tool.translate('goto_last');
onClick python: 'askAjaxChunk(\'%s\', \'%s\')' % (ajaxHookId, baseUrl+str(sNumber))"/></td>
</tr>
</table>
</metal:appyNavigate>

View file

@ -21,31 +21,28 @@
<img src="edit.gif" title="label_edit" i18n:domain="plone" i18n:attributes="title" />
</a></td>
<tal:comment replace="nothing">Delete the element</tal:comment>
<td class="noPadding"><a tal:attributes="href python: obj.absolute_url() + '/delete_confirmation'"
tal:condition="python: member.has_permission('Delete objects', obj)">
<img src="delete_icon.gif" title="label_remove" i18n:domain="plone" i18n:attributes="title" />
</a></td>
<td class="noPadding">
<img tal:condition="python: member.has_permission('Delete objects', obj)"
src="delete_icon.gif" title="Delete" i18n:domain="plone" i18n:attributes="title" style="cursor:pointer"
tal:attributes="onClick python:'javascript:onDeleteObject(\'%s\')' % obj.UID()"/>
</td>
<tal:comment replace="nothing">Arrows for moving objects up or down</tal:comment>
<td class="noPadding" tal:condition="python: len(objs)&gt;1">
<form tal:condition="python: member.has_permission('Modify portal content', obj)"
tal:attributes="action python: contextObj.absolute_url() + '/skyn/do'"
tal:define="objectIndex python:contextObj.getAppyRefIndex(field.getName(), obj)">
<input type="hidden" name="action" value="ChangeRefOrder"/>
<input type="hidden" name="fieldName" tal:attributes="value field/getName"/>
<input type="hidden" name="refObjectUid" tal:attributes="value obj/UID"/>
<tal:comment replace="nothing">Arrow up</tal:comment>
<span tal:condition="python: objectIndex &gt; 0">
<input type="image" name="moveUp" class="imageInput"
tal:attributes="src string: $portal_url/skyn/arrowUp.png;
title python: tool.translate('move_up')"/>
</span>
<tal:comment replace="nothing">Arrow down</tal:comment>
<span tal:condition="python: objectIndex &lt; (len(objs)-1)">
<input type="image" name="moveDown" class="imageInput"
tal:attributes="src string: $portal_url/skyn/arrowDown.png;
title python: tool.translate('move_down')"/>
</span>
</form>
<td class="noPadding" tal:condition="python: (len(objs)&gt;1) and member.has_permission('Modify portal content', obj)">
<tal:moveRef define="objectIndex python:contextObj.getAppyRefIndex(fieldName, obj);
baseUrl python: contextObj.getUrl('showRef', **{'fieldName': fieldName, 'isBack': isBack, 'innerRef': innerRef, 'labelId': labelId, 'descrId': descrId, '%s_startNumber' % ajaxHookId: startNumber, 'action':'ChangeRefOrder', 'refObjectUid': obj.UID()})">
<tal:comment replace="nothing">Move up</tal:comment>
<img tal:define="ajaxUrl python: baseUrl + '&move=up'" tal:condition="python: objectIndex &gt; 0"
tal:attributes="src string: $portal_url/skyn/arrowUp.png;
title python: tool.translate('move_up');
onClick python: 'askAjaxChunk(\'%s\', \'%s\')' % (ajaxHookId, ajaxUrl)"
style="cursor:pointer"/>
<tal:comment replace="nothing">Move down</tal:comment>
<img tal:define="ajaxUrl python: baseUrl + '&move=down'" tal:condition="python: objectIndex &lt; (totalNumber-1)"
tal:attributes="src string: $portal_url/skyn/arrowDown.png;
title python: tool.translate('move_down');
onClick python: 'askAjaxChunk(\'%s\', \'%s\')' % (ajaxHookId, ajaxUrl)"
style="cursor:pointer"/>
</tal:moveRef>
</td>
</tr>
</table>
@ -58,18 +55,59 @@
<img style="cursor:pointer" tal:condition="showPlusIcon"
tal:attributes="src string:$portal_url/skyn/plus.png;
title python: tool.translate('add_ref');
onClick python: 'href: window.location=\'%s/skyn/do?action=Create&initiator=%s&field=%s&type_name=%s\'' % (folder.absolute_url(), contextObj.UID(), field.getName(), linkedPortalType)"/>
onClick python: 'href: window.location=\'%s/skyn/do?action=Create&initiator=%s&field=%s&type_name=%s\'' % (folder.absolute_url(), contextObj.UID(), fieldName, linkedPortalType)"/>
</metal:plusIcon>
<tal:comment replace="nothing">
This macro shows a reference field. More precisely, it shows nothing, but calls
a Javascript function that will asynchonously call (via a XmlHttpRequest object) the
macro 'showReferenceContent' defined below, that will really show content.
It requires:
- isBack (bool) Is the reference a backward or forward reference?
- fieldName (string) The name of the reference field (if it is a forward reference)
or the name of the Archetypes relationship (if it is a backward reference)
- innerRef (bool) Are we rendering a reference within a reference or not?
- contextObj (object) the object from which the reference starts
- labelId (string) the i18n id of the reference field label
- descrId (string) the i18n id of the reference field description
</tal:comment>
<div metal:define-macro="showReference"
tal:define="folder python: test(contextObj.isPrincipiaFolderish, contextObj, contextObj.getParentNode());
tal:define="ajaxHookId python: contextObj.UID()+fieldName;
ajaxUrl python: contextObj.getUrl('showRef', **{'fieldName': fieldName, 'isBack': isBack, 'innerRef': innerRef, 'labelId': labelId, 'descrId': descrId})"
tal:attributes="id ajaxHookId">
<script language="javascript"
tal:content="python: 'askAjaxChunk(\'%s\',\'%s\')' % (ajaxHookId, ajaxUrl)">
</script>
</div>
<tal:comment replace="nothing">
This macro is called by a XmlHttpRequest for displaying the paginated referred objects
of a reference field.
</tal:comment>
<div metal:define-macro="showReferenceContent"
tal:define="fieldName request/fieldName;
isBack python: test(request['isBack']=='True', True, False);
innerRef python: test(request['innerRef']=='True', True, False);
labelId request/labelId;
descrId request/descrId;
ajaxHookId python: contextObj.UID()+fieldName;
startNumber python: int(request.get('%s_startNumber' % ajaxHookId, 0));
appyType python: contextObj.getAppyType(fieldName, not isBack);
tool contextObj/getTool;
refObjects python:contextObj.getAppyRefs(fieldName, not isBack, startNumber);
objs refObjects/objects;
totalNumber refObjects/totalNumber;
batchSize refObjects/batchSize;
folder python: test(contextObj.isPrincipiaFolderish, contextObj, contextObj.getParentNode());
flavour python:tool.getFlavour(contextObj);
linkedPortalType python:flavour.getPortalType(appyType['klass']);
addPermission python: '%s: Add %s' % (appName, linkedPortalType);
addPermission python: '%s: Add %s' % (tool.getAppName(), linkedPortalType);
multiplicity python:test(isBack, appyType['backd']['multiplicity'], appyType['multiplicity']);
maxReached python:(multiplicity[1] != None) and (len(objs) >= multiplicity[1]);
showPlusIcon python:not isBack and appyType['add'] and not maxReached and member.has_permission(addPermission, folder);
atMostOneRef python: (multiplicity[1] == 1) and (len(objs)&lt;=1)">
atMostOneRef python: (multiplicity[1] == 1) and (len(objs)&lt;=1);
label python: tool.translate(labelId);
description python: tool.translate(descrId)">
<tal:comment replace="nothing">This macro displays the Reference widget on a "consult" page.
@ -106,6 +144,7 @@
<fieldset tal:attributes="class python:test(innerRef, 'innerAppyFieldset', '')">
<legend tal:condition="python: not innerRef or showPlusIcon">
<span tal:condition="not: innerRef" tal:content="label"/>
<tal:numberOfRefs>(<span tal:replace="totalNumber"/>)</tal:numberOfRefs>
<metal:plusIcon use-macro="here/skyn/ref/macros/plusIcon"/>
</legend>
@ -113,6 +152,9 @@
<p tal:condition="python: not innerRef and description"
tal:content="description" class="discreet" ></p>
<tal:comment replace="nothing">Appy (top) navigation</tal:comment>
<metal:nav use-macro="here/skyn/macros/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>
@ -167,9 +209,8 @@
</tal:showNormalField>
<tal:showRef condition="python: appyType['type'] == 'Ref'">
<tal:ref tal:define="isBack python:appyType['isBack'];
fieldRel python:field.relationship;
objs python:contextObj.getAppyRefs(field.getName());
innerRef python:True">
fieldName python: test(isBack, field.relationship, field.getName());
innerRef python:True">
<metal:showField use-macro="here/skyn/ref/macros/showReference" />
</tal:ref>
</tal:showRef>
@ -190,6 +231,10 @@
</td></tr>
</table>
<tal:comment replace="nothing">Appy (bottom) navigation</tal:comment>
<metal:nav use-macro="here/skyn/macros/macros/appyNavigate"/>
</fieldset>
<tal:comment replace="nothing">A carriage return needed in some cases.</tal:comment>
<br tal:define="widgetDescr widgetDescr|nothing"
@ -202,7 +247,7 @@
appyType python:here.getAppyType(field.getName());
allBrains python:here.uid_catalog(portal_type=refPortalType);
brains python:here.callAppySelect(appyType['select'], allBrains);
refUids python: [o.UID() for o in here.getAppyRefs(field.getName())];
refUids python: [o.UID() for o in here.getAppyRefs(field.getName())['objects']];
isMultiple python:test(appyType['multiplicity'][1]!=1, 'multiple', '');
appyFieldName python: 'appy_ref_%s' % field.getName();
inError python:test(errors.has_key(field.getName()), True, False);

BIN
gen/plone25/skin/to.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 B

BIN
gen/plone25/skin/waiting.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB