Reworked AJAX framework with a lot of new sorting and filtering possibilities.
This commit is contained in:
parent
46cda3f755
commit
fd775e17a2
|
@ -1,7 +1,7 @@
|
|||
# ------------------------------------------------------------------------------
|
||||
import re, time
|
||||
from appy.shared.utils import Traceback
|
||||
from appy.gen.utils import sequenceTypes, PageDescr
|
||||
from appy.gen.utils import sequenceTypes, PageDescr, Keywords
|
||||
from appy.shared.data import countries
|
||||
|
||||
# Default Appy permissions -----------------------------------------------------
|
||||
|
@ -54,6 +54,56 @@ class Search:
|
|||
self.limit = limit
|
||||
self.fields = fields # This is a dict whose keys are indexed field
|
||||
# names and whose values are search values.
|
||||
@staticmethod
|
||||
def getIndexName(fieldName, usage='search'):
|
||||
'''Gets the name of the technical index that corresponds to field named
|
||||
p_fieldName. Indexes can be used for searching (p_usage="search") or
|
||||
for sorting (usage="sort"). The method returns None if the field
|
||||
named p_fieldName can't be used for p_usage.'''
|
||||
if fieldName == 'title':
|
||||
if usage == 'search': return 'Title'
|
||||
else: return 'sortable_title'
|
||||
# Indeed, for field 'title', Plone has created a specific index
|
||||
# 'sortable_title', because index 'Title' is a ZCTextIndex
|
||||
# (for searchability) and can't be used for sorting.
|
||||
elif fieldName == 'description':
|
||||
if usage == 'search': return 'Description'
|
||||
else: return None
|
||||
elif fieldName == 'state': return 'review_state'
|
||||
else:
|
||||
return 'get%s%s'% (fieldName[0].upper(),fieldName[1:])
|
||||
@staticmethod
|
||||
def getSearchValue(fieldName, fieldValue):
|
||||
'''Returns a transformed p_fieldValue for producing a valid search
|
||||
value as required for searching in the index corresponding to
|
||||
p_fieldName.'''
|
||||
if fieldName == 'title':
|
||||
# Title is a ZCTextIndex. We must split p_fieldValue into keywords.
|
||||
res = Keywords(fieldValue.decode('utf-8')).get()
|
||||
elif isinstance(fieldValue, basestring) and fieldValue.endswith('*'):
|
||||
v = fieldValue[:-1]
|
||||
# Warning: 'z' is higher than 'Z'!
|
||||
res = {'query':(v,v+'z'), 'range':'min:max'}
|
||||
elif type(fieldValue) in sequenceTypes:
|
||||
if fieldValue and isinstance(fieldValue[0], basestring):
|
||||
# We have a list of string values (ie: we need to
|
||||
# search v1 or v2 or...)
|
||||
res = fieldValue
|
||||
else:
|
||||
# We have a range of (int, float, DateTime...) values
|
||||
minv, maxv = fieldValue
|
||||
rangev = 'minmax'
|
||||
queryv = fieldValue
|
||||
if minv == None:
|
||||
rangev = 'max'
|
||||
queryv = maxv
|
||||
elif maxv == None:
|
||||
rangev = 'min'
|
||||
queryv = minv
|
||||
res = {'query':queryv, 'range':rangev}
|
||||
else:
|
||||
res = fieldValue
|
||||
return res
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
class Type:
|
||||
|
|
|
@ -105,10 +105,11 @@ class ToolMixin(AbstractMixin):
|
|||
if res: return res[0].getObject()
|
||||
return None
|
||||
|
||||
_sortFields = {'title': 'sortable_title'}
|
||||
def executeQuery(self, contentType, flavourNumber=1, searchName=None,
|
||||
startNumber=0, search=None, remember=False,
|
||||
brainsOnly=False, maxResults=None, noSecurity=False):
|
||||
brainsOnly=False, maxResults=None, noSecurity=False,
|
||||
sortBy=None, sortOrder='asc',
|
||||
filterKey=None, filterValue=None):
|
||||
'''Executes a query on a given p_contentType (or several, separated
|
||||
with commas) in Plone's portal_catalog. Portal types are from the
|
||||
flavour numbered p_flavourNumber. If p_searchName is specified, it
|
||||
|
@ -136,7 +137,16 @@ class ToolMixin(AbstractMixin):
|
|||
p_maxResults equals string "NO_LIMIT".
|
||||
|
||||
If p_noSecurity is True, it gets all the objects, even those that the
|
||||
currently logged user can't see.'''
|
||||
currently logged user can't see.
|
||||
|
||||
The result is sorted according to the potential sort key defined in
|
||||
the Search instance (Search.sortBy). But if parameter p_sortBy is
|
||||
given, it defines or overrides the sort. In this case, p_sortOrder
|
||||
gives the order (*asc*ending or *desc*ending).
|
||||
|
||||
If p_filterKey is given, it represents an additional search parameter
|
||||
to take into account: the corresponding search value is in
|
||||
p_filterValue.'''
|
||||
# Is there one or several content types ?
|
||||
if contentType.find(',') != -1:
|
||||
# Several content types are specified
|
||||
|
@ -149,59 +159,40 @@ class ToolMixin(AbstractMixin):
|
|||
params = {'portal_type': portalTypes}
|
||||
if not brainsOnly: params['batch'] = True
|
||||
# Manage additional criteria from a search when relevant
|
||||
if searchName or search:
|
||||
if searchName:
|
||||
# In this case, contentType must contain a single content type.
|
||||
appyClass = self.getAppyClass(contentType)
|
||||
if searchName:
|
||||
if searchName != '_advanced':
|
||||
search = ArchetypesClassDescriptor.getSearch(
|
||||
appyClass, searchName)
|
||||
else:
|
||||
fields = self.REQUEST.SESSION['searchCriteria']
|
||||
search = Search('customSearch', **fields)
|
||||
if searchName != '_advanced':
|
||||
search = ArchetypesClassDescriptor.getSearch(
|
||||
appyClass, searchName)
|
||||
else:
|
||||
fields = self.REQUEST.SESSION['searchCriteria']
|
||||
search = Search('customSearch', **fields)
|
||||
if search:
|
||||
# Add additional search criteria
|
||||
for fieldName, fieldValue in search.fields.iteritems():
|
||||
# Make the correspondance between the name of the field and the
|
||||
# name of the corresponding index.
|
||||
attrName = fieldName
|
||||
if attrName == 'title': attrName = 'Title'
|
||||
elif attrName == 'description': attrName = 'Description'
|
||||
elif attrName == 'state': attrName = 'review_state'
|
||||
else: attrName = 'get%s%s'% (fieldName[0].upper(),fieldName[1:])
|
||||
attrName = Search.getIndexName(fieldName)
|
||||
# Express the field value in the way needed by the index
|
||||
if isinstance(fieldValue, basestring) and \
|
||||
fieldValue.endswith('*'):
|
||||
v = fieldValue[:-1]
|
||||
params[attrName] = {'query':(v,v+'z'), 'range':'min:max'}
|
||||
# Warning: 'z' is higher than 'Z'!
|
||||
elif type(fieldValue) in sequenceTypes:
|
||||
if fieldValue and isinstance(fieldValue[0], basestring):
|
||||
# We have a list of string values (ie: we need to
|
||||
# search v1 or v2 or...)
|
||||
params[attrName] = fieldValue
|
||||
else:
|
||||
# We have a range of (int, float, DateTime...) values
|
||||
minv, maxv = fieldValue
|
||||
rangev = 'minmax'
|
||||
queryv = fieldValue
|
||||
if minv == None:
|
||||
rangev = 'max'
|
||||
queryv = maxv
|
||||
elif maxv == None:
|
||||
rangev = 'min'
|
||||
queryv = minv
|
||||
params[attrName] = {'query':queryv, 'range':rangev}
|
||||
else:
|
||||
params[attrName] = fieldValue
|
||||
params[attrName] = Search.getSearchValue(fieldName, fieldValue)
|
||||
# Add a sort order if specified
|
||||
sb = search.sortBy
|
||||
if sb:
|
||||
# For field 'title', Plone has created a specific index
|
||||
# 'sortable_title', because index 'Title' is a ZCTextIndex
|
||||
# (for searchability) and can't be used for sorting.
|
||||
if self._sortFields.has_key(sb): sb = self._sortFields[sb]
|
||||
params['sort_on'] = sb
|
||||
sortKey = search.sortBy
|
||||
if sortKey:
|
||||
params['sort_on'] = Search.getIndexName(sortKey, usage='sort')
|
||||
# Determine or override sort if specified.
|
||||
if sortBy:
|
||||
params['sort_on'] = Search.getIndexName(sortBy, usage='sort')
|
||||
if sortOrder == 'desc': params['sort_order'] = 'reverse'
|
||||
else: params['sort_order'] = None
|
||||
# If defined, add the filter among search parameters.
|
||||
if filterKey:
|
||||
filterKey = Search.getIndexName(filterKey)
|
||||
filterValue = Search.getSearchValue(filterKey, filterValue)
|
||||
params[filterKey] = filterValue
|
||||
# TODO This value needs to be merged with an existing one if already
|
||||
# in params, or, in a first step, we should avoid to display the
|
||||
# corresponding filter widget on the screen.
|
||||
# Determine what method to call on the portal catalog
|
||||
if noSecurity: catalogMethod = 'unrestrictedSearchResults'
|
||||
else: catalogMethod = 'searchResults'
|
||||
|
@ -544,14 +535,13 @@ class ToolMixin(AbstractMixin):
|
|||
if cookieValue: return cookieValue.value
|
||||
return default
|
||||
|
||||
def getQueryUrl(self, contentType, flavourNumber, searchName, ajax=True,
|
||||
def getQueryUrl(self, contentType, flavourNumber, searchName,
|
||||
startNumber=None):
|
||||
'''This method creates the URL that allows to perform an ajax GET
|
||||
'''This method creates the URL that allows to perform a (non-Ajax)
|
||||
request for getting queried objects from a search named p_searchName
|
||||
on p_contentType from flavour numbered p_flavourNumber. If p_ajax
|
||||
is False, it returns the non-ajax URL.'''
|
||||
on p_contentType from flavour numbered p_flavourNumber.'''
|
||||
baseUrl = self.getAppFolder().absolute_url() + '/skyn'
|
||||
baseParams = 'type_name=%s&flavourNumber=%s'%(contentType,flavourNumber)
|
||||
baseParams= 'type_name=%s&flavourNumber=%s' %(contentType,flavourNumber)
|
||||
# Manage start number
|
||||
rq = self.REQUEST
|
||||
if startNumber != None:
|
||||
|
@ -559,12 +549,8 @@ class ToolMixin(AbstractMixin):
|
|||
elif rq.has_key('startNumber'):
|
||||
baseParams += '&startNumber=%s' % rq['startNumber']
|
||||
# Manage search name
|
||||
if searchName or ajax: baseParams += '&search=%s' % searchName
|
||||
if ajax:
|
||||
return '%s/ajax?objectUid=%s&page=macros¯o=queryResult&%s' % \
|
||||
(baseUrl, self.UID(), baseParams)
|
||||
else:
|
||||
return '%s/query?%s' % (baseUrl, baseParams)
|
||||
if searchName: baseParams += '&search=%s' % searchName
|
||||
return '%s/query?%s' % (baseUrl, baseParams)
|
||||
|
||||
def computeStartNumberFrom(self, currentNumber, totalNumber, batchSize):
|
||||
'''Returns the number (start at 0) of the first element in a list
|
||||
|
@ -666,7 +652,7 @@ class ToolMixin(AbstractMixin):
|
|||
startNumber = self.computeStartNumberFrom(res['currentNumber']-1,
|
||||
res['totalNumber'], batchSize)
|
||||
res['sourceUrl'] = self.getQueryUrl(contentType, flavourNumber,
|
||||
searchName, ajax=False, startNumber=startNumber)
|
||||
searchName, startNumber=startNumber)
|
||||
# Compute URLs
|
||||
for urlType in ('previous', 'next', 'first', 'last'):
|
||||
exec 'needIt = %sNeeded' % urlType
|
||||
|
|
|
@ -659,6 +659,15 @@ class AbstractMixin:
|
|||
isDelta = True
|
||||
self.changeRefOrder(rq['fieldName'], rq['refObjectUid'], move, isDelta)
|
||||
|
||||
def onSortReference(self):
|
||||
'''This method is called when the user wants to sort the content of a
|
||||
reference field.'''
|
||||
rq = self.REQUEST
|
||||
fieldName = rq.get('fieldName')
|
||||
sortKey = rq.get('sortKey')
|
||||
reverse = rq.get('reverse') == 'True'
|
||||
self.appy().sort(fieldName, sortKey=sortKey, reverse=reverse)
|
||||
|
||||
def getWorkflow(self, appy=True):
|
||||
'''Returns the Appy workflow instance that is relevant for this
|
||||
object. If p_appy is False, it returns the DC workflow.'''
|
||||
|
@ -1243,27 +1252,9 @@ class AbstractMixin:
|
|||
exec 'self.set%s%s([])' % (fieldName[0].upper(),
|
||||
fieldName[1:])
|
||||
|
||||
def getUrl(self, t='view', **kwargs):
|
||||
'''This method returns various URLs about this object.'''
|
||||
baseUrl = self.absolute_url()
|
||||
params = ''
|
||||
rq = self.REQUEST
|
||||
for k, v in kwargs.iteritems(): params += '&%s=%s' % (k, v)
|
||||
if params: params = params[1:]
|
||||
if t == 'showRef':
|
||||
chunk = '/skyn/ajax?objectUid=%s&page=ref&' \
|
||||
'macro=showReferenceContent&' % self.UID()
|
||||
startKey = '%s%s_startNumber' % (self.UID(), kwargs['fieldName'])
|
||||
if rq.has_key(startKey) and not kwargs.has_key(startKey):
|
||||
params += '&%s=%s' % (startKey, rq[startKey])
|
||||
return baseUrl + chunk + params
|
||||
elif t == 'showHistory':
|
||||
chunk = '/skyn/ajax?objectUid=%s&page=macros¯o=history' % \
|
||||
self.UID()
|
||||
if params: params = '&' + params
|
||||
return baseUrl + chunk + params
|
||||
else: # We consider t=='view'
|
||||
return baseUrl + '/skyn/view' + params
|
||||
def getUrl(self):
|
||||
'''Returns the Appy URL for viewing this object.'''
|
||||
return self.absolute_url() + '/skyn/view'
|
||||
|
||||
def translate(self, label, mapping={}, domain=None, default=None):
|
||||
'''Translates a given p_label into p_domain with p_mapping.'''
|
||||
|
|
|
@ -1,15 +1,12 @@
|
|||
<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();
|
||||
contextObj context/getParentNode;
|
||||
action request/action|nothing;
|
||||
response request/RESPONSE;
|
||||
member context/portal_membership/getAuthenticatedMember;
|
||||
|
@ -18,8 +15,11 @@
|
|||
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:comment replace="nothing">Keys "Expires" and "CacheControl" are used for preventing IE to cache
|
||||
this page. Indeed, this page is retrieved through an asynchronous XMLHttpRequest by the browser, and
|
||||
IE caches this by default.</tal:comment>
|
||||
<tal:executeAction condition="action">
|
||||
<tal:do define="dummy python: contextObj.getAppyValue('on'+action)()" omit-tag=""/>
|
||||
</tal:executeAction>
|
||||
<metal:callMacro use-macro="python: context.get(page).macros.get(macro)"/>
|
||||
<metal:callMacro use-macro="python: portal.skyn.get(page).macros.get(macro)"/>
|
||||
</tal:ajax>
|
||||
|
|
|
@ -295,12 +295,12 @@
|
|||
batchSize historyInfo/batchSize;
|
||||
totalNumber historyInfo/totalNumber;
|
||||
ajaxHookId python:'appyHistory';
|
||||
baseUrl python: contextObj.getUrl('showHistory', startNumber='**v**');
|
||||
navBaseCall python: 'askObjectHistory(\'%s\',\'%s\',**v**)' % (ajaxHookId, contextObj.absolute_url());
|
||||
tool contextObj/getTool">
|
||||
|
||||
<tal:comment replace="nothing">Table containing the history</tal:comment>
|
||||
<tal:history condition="objs">
|
||||
<metal:nav use-macro="here/skyn/macros/macros/appyNavigate"/>
|
||||
<metal:nav use-macro="here/skyn/navigate/macros/appyNavigate"/>
|
||||
<table width="100%" class="listing nosort">
|
||||
<tr i18n:domain="plone">
|
||||
<th i18n:translate="listingheader_action"/>
|
||||
|
@ -370,8 +370,11 @@
|
|||
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.
|
||||
this.hook = ''; /* The ID of the HTML element in the page that will be
|
||||
replaced by result of executing the Ajax request. */
|
||||
this.onGet = ''; /* The name of a Javascript function to call once we
|
||||
receive the result. */
|
||||
this.info = {}; /* An associative array for putting anything else. */
|
||||
}
|
||||
|
||||
function getAjaxChunk(pos) {
|
||||
|
@ -391,15 +394,35 @@
|
|||
var hookElem = document.getElementById(hook);
|
||||
if (hookElem && (xhrObjects[pos].xhr.status == 200)) {
|
||||
hookElem.innerHTML = xhrObjects[pos].xhr.responseText;
|
||||
// Call a custom Javascript function if required
|
||||
if (xhrObjects[pos].onGet) {
|
||||
xhrObjects[pos].onGet(xhrObjects[pos], hookElem);
|
||||
}
|
||||
}
|
||||
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.
|
||||
function askAjaxChunk(hook,mode,url,page,macro,params,beforeSend,onGet) {
|
||||
/* This function will ask to get a chunk of HTML on the server through a
|
||||
XMLHttpRequest. p_mode can be 'GET' or 'POST'. p_url is the URL of a
|
||||
given server object. On this URL we will call the page "ajax.pt" that
|
||||
will call a specific p_macro in a given p_page with some additional
|
||||
p_params (must be an associative array) if required.
|
||||
|
||||
p_hook is the ID of the HTML element that will be filled with the HTML
|
||||
result from the server.
|
||||
|
||||
p_beforeSend is a Javascript function to call before sending the request.
|
||||
This function will get 2 args: the XMLHttpRequest object and the
|
||||
p_params. This method can return, in a string, additional parameters to
|
||||
send, ie: "¶m1=blabla¶m2=blabla".
|
||||
|
||||
p_onGet is a Javascript function to call when we will receive the answer.
|
||||
This function will get 2 args, too: the XMLHttpRequest object and the
|
||||
HTML node element into which the result has been inserted.
|
||||
*/
|
||||
// First, get a non-busy XMLHttpRequest object.
|
||||
var pos = -1;
|
||||
for (var i=0; i < xhrObjects.length; i++) {
|
||||
|
@ -410,16 +433,86 @@
|
|||
xhrObjects[pos] = new XhrObject();
|
||||
}
|
||||
xhrObjects[pos].hook = hook;
|
||||
xhrObjects[pos].onGet = onGet;
|
||||
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(); }
|
||||
var rq = xhrObjects[pos];
|
||||
rq.freed = 0;
|
||||
// Construct parameters
|
||||
var paramsFull = 'page=' + page + '¯o=' + macro;
|
||||
if (params) {
|
||||
for (var paramName in params)
|
||||
paramsFull = paramsFull + '&' + paramName + '=' + params[paramName];
|
||||
}
|
||||
// Call beforeSend if required
|
||||
if (beforeSend) {
|
||||
var res = beforeSend(rq, params);
|
||||
if (res) paramsFull = paramsFull + res;
|
||||
}
|
||||
// Construct the URL to call
|
||||
var urlFull = url + '/skyn/ajax';
|
||||
if (mode == 'GET') {
|
||||
urlFull = urlFull + '?' + paramsFull;
|
||||
}
|
||||
// Perform the asynchronous HTTP GET or POST
|
||||
rq.xhr.open(mode, urlFull, true);
|
||||
if (mode == 'POST') {
|
||||
// Set the correct HTTP headers
|
||||
rq.xhr.setRequestHeader(
|
||||
"Content-Type", "application/x-www-form-urlencoded");
|
||||
rq.xhr.setRequestHeader("Content-length", paramsFull.length);
|
||||
rq.xhr.setRequestHeader("Connection", "close");
|
||||
rq.xhr.onreadystatechange = function(){ getAjaxChunk(pos); }
|
||||
rq.xhr.send(paramsFull);
|
||||
}
|
||||
else if (mode == 'GET') {
|
||||
rq.xhr.onreadystatechange = function() { getAjaxChunk(pos); }
|
||||
if (window.XMLHttpRequest) { rq.xhr.send(null); }
|
||||
else if (window.ActiveXObject) { rq.xhr.send(); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* The functions below wrap askAjaxChunk for getting specific content through
|
||||
an Ajax request. */
|
||||
function askQueryResult(hookId, objectUrl, contentType, flavourNumber,
|
||||
searchName, startNumber, sortKey, sortOrder, filterKey) {
|
||||
// Sends an Ajax request for getting the result of a query.
|
||||
var params = {'type_name': contentType, 'flavourNumber': flavourNumber,
|
||||
'search': searchName, 'startNumber': startNumber};
|
||||
if (sortKey) params['sortKey'] = sortKey;
|
||||
if (sortOrder) params['sortOrder'] = sortOrder;
|
||||
if (filterKey) {
|
||||
var filterWidget = document.getElementById(hookId + '_' + filterKey);
|
||||
if (filterWidget && filterWidget.value) {
|
||||
params['filterKey'] = filterKey;
|
||||
params['filterValue'] = filterWidget.value;
|
||||
}
|
||||
}
|
||||
askAjaxChunk(hookId,'GET',objectUrl,'macros','queryResult',params);
|
||||
}
|
||||
|
||||
function askObjectHistory(hookId, objectUrl, startNumber) {
|
||||
// Sends an Ajax request for getting the history of an object
|
||||
var params = {'startNumber': startNumber};
|
||||
askAjaxChunk(hookId, 'GET', objectUrl, 'macros', 'history', params);
|
||||
}
|
||||
|
||||
function askRefField(hookId, objectUrl, fieldName, isBack, innerRef, labelId,
|
||||
descrId, startNumber, action, actionParams){
|
||||
// Sends an Ajax request for getting the content of a reference field.
|
||||
var startKey = hookId + '_startNumber';
|
||||
var params = {'fieldName': fieldName, 'isBack': isBack,
|
||||
'innerRef': innerRef, 'labelId': labelId,
|
||||
'descrId': descrId };
|
||||
params[startKey] = startNumber;
|
||||
if (action) params['action'] = action;
|
||||
if (actionParams) {
|
||||
for (key in actionParams) { params[key] = actionParams[key]; };
|
||||
}
|
||||
askAjaxChunk(hookId, 'GET', objectUrl, 'ref', 'showReferenceContent',
|
||||
params);
|
||||
}
|
||||
|
||||
// Function used by checkbox widgets for having radio-button-like behaviour
|
||||
function toggleCheckbox(visibleCheckbox, hiddenBoolean) {
|
||||
vis = document.getElementById(visibleCheckbox);
|
||||
|
@ -633,10 +726,9 @@
|
|||
<td colspan="2">
|
||||
<span id="appyHistory"
|
||||
tal:attributes="style python:test(historyExpanded, 'display:block', 'display:none')">
|
||||
<div tal:define="ajaxHookId python: contextObj.UID() + '_history';
|
||||
ajaxUrl python: contextObj.getUrl('showHistory')"
|
||||
<div tal:define="ajaxHookId python: contextObj.UID() + '_history';"
|
||||
tal:attributes="id ajaxHookId">
|
||||
<script language="javascript" tal:content="python: 'askAjaxChunk(\'%s\',\'%s\')' % (ajaxHookId, ajaxUrl)">
|
||||
<script language="javascript" tal:content="python: 'askObjectHistory(\'%s\',\'%s\',0)' % (ajaxHookId, contextObj.absolute_url())">
|
||||
</script>
|
||||
</div>
|
||||
</span>
|
||||
|
@ -655,7 +747,7 @@
|
|||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<metal:nav use-macro="here/skyn/macros/macros/objectNavigate"/>
|
||||
<metal:nav use-macro="here/skyn/navigate/macros/objectNavigate"/>
|
||||
|
||||
<tal:comment replace="nothing">Tabs</tal:comment>
|
||||
<ul class="contentViews appyTabs" tal:condition="python: len(appyPages)>1">
|
||||
|
@ -726,12 +818,16 @@
|
|||
searchLabel python: test(searchName=='_advanced', 'search_results', '%s_search_%s' % (contentType, searchName));
|
||||
searchDescr python: '%s_descr' % searchLabel;
|
||||
severalTypes python: contentType and (contentType.find(',') != -1);
|
||||
queryResult python: tool.executeQuery(contentType, flavourNumber, searchName, startNumber, remember=True);
|
||||
sortKey request/sortKey| python:'';
|
||||
sortOrder request/sortOrder| python:'asc';
|
||||
filterKey request/filterKey| python:'';
|
||||
filterValue request/filterValue | python:'';
|
||||
queryResult python: tool.executeQuery(contentType, flavourNumber, searchName, startNumber, remember=True, sortBy=sortKey, sortOrder=sortOrder, filterKey=filterKey, filterValue=filterValue);
|
||||
objs queryResult/objects;
|
||||
totalNumber queryResult/totalNumber;
|
||||
batchSize queryResult/batchSize;
|
||||
ajaxHookId python:'queryResult';
|
||||
baseUrl python: tool.getQueryUrl(contentType, flavourNumber, searchName, startNumber='**v**');
|
||||
navBaseCall python: 'askQueryResult(\'%s\',\'%s\',\'%s\',\'%s\',\'%s\',**v**)' % (ajaxHookId, tool.absolute_url(), contentType, flavourNumber, searchName);
|
||||
newSearchUrl python: '%s/skyn/search?type_name=%s&flavourNumber=%d' % (tool.getAppFolder().absolute_url(), contentType, flavourNumber);">
|
||||
|
||||
<tal:result condition="objs">
|
||||
|
@ -753,7 +849,7 @@
|
|||
</td>
|
||||
<td align="right" width="25%">
|
||||
<tal:comment replace="nothing">Appy (top) navigation</tal:comment>
|
||||
<metal:nav use-macro="here/skyn/macros/macros/appyNavigate"/>
|
||||
<metal:nav use-macro="here/skyn/navigate/macros/appyNavigate"/>
|
||||
</td>
|
||||
</tr></table>
|
||||
|
||||
|
@ -763,26 +859,20 @@
|
|||
excepted for workflow state (which is not a field): in this case it is simply the
|
||||
string "workflowState".</tal:comment>
|
||||
|
||||
<tal:comment replace="nothing">Headers, with (disabled) filters and sort arrows</tal:comment>
|
||||
<tal:comment replace="nothing">Headers, with filters and sort arrows</tal:comment>
|
||||
<tr>
|
||||
<tal:comment replace="nothing">Mandatory column "Title"/"Name"</tal:comment>
|
||||
<th><!--img tal:attributes= "src string: $portal_url/arrowDown.gif;
|
||||
onClick python:'javascript:onSort(\'title\')';"
|
||||
id="arrow_title" style="cursor:pointer"/-->
|
||||
|
||||
<th tal:define="fieldName python:'title'; sortable python:True; filterable python:True">
|
||||
<span tal:content="python: tool.translate('ref_name')"/>
|
||||
<!--input id="filter_title" type="text" size="5" onkeyup="javascript:onTextEntered('title')"/-->
|
||||
<tal:comment replace="nothing">Input fields like this have been commented out because they will
|
||||
be replaced by Ajax server- searches that will be more relevant (the current Javascript search
|
||||
is limited to the batch, which has little interest).</tal:comment>
|
||||
<metal:sortAndFilter use-macro="here/skyn/navigate/macros/sortAndFilter"/>
|
||||
</th>
|
||||
|
||||
<tal:comment replace="nothing">Columns corresponding to other fields</tal:comment>
|
||||
<tal:columnHeader repeat="fieldDescr fieldDescrs">
|
||||
<th tal:define="fieldName fieldDescr/atField/getName|string:workflow_state">
|
||||
<!--img tal:attributes= "src string: $portal_url/arrowDown.gif;
|
||||
onClick python:'javascript:onSort(\'%s\')' % fieldName;
|
||||
id python: 'arrow_%s' % fieldName"
|
||||
style="cursor:pointer"/-->
|
||||
<th tal:define="fieldName fieldDescr/atField/getName|string:workflow_state;
|
||||
sortable fieldDescr/sortable|nothing;
|
||||
filterable fieldDescr/filterable|nothing;">
|
||||
<tal:comment replace="nothing">Display header for a "standard" field</tal:comment>
|
||||
<tal:standardField condition="python: fieldName != 'workflow_state'">
|
||||
<span tal:replace="python: tool.translate(fieldDescr['atField'].widget.label_msgid)"/>
|
||||
|
@ -791,20 +881,13 @@
|
|||
<tal:workflowState condition="python: fieldName == 'workflow_state'">
|
||||
<span tal:replace="python: tool.translate('workflow_state')"/>
|
||||
</tal:workflowState>
|
||||
<!--input type="text" size="5"
|
||||
tal:attributes="id python: 'filter_%s' % fieldName;
|
||||
onkeyup python:'javascript:onTextEntered(\'%s\')' % fieldName"/-->
|
||||
<metal:sortAndFilter use-macro="here/skyn/navigate/macros/sortAndFilter"/>
|
||||
</th>
|
||||
</tal:columnHeader>
|
||||
|
||||
<tal:comment replace="nothing">Column "Object type", shown if instances of several types are shown</tal:comment>
|
||||
<th tal:condition="severalTypes"><!--img
|
||||
tal:attributes= "src string: $portal_url/arrowDown.gif;
|
||||
onClick python:'javascript:onSort(\'root_type\')';"
|
||||
id = "arrow_root_type" style="cursor:pointer"/-->
|
||||
<th tal:condition="severalTypes">
|
||||
<span tal:replace="python: tool.translate('root_type')"/>
|
||||
<!--input type="text" size="5" id="filter_root_type"
|
||||
tal:attributes="onkeyup python:'javascript:onTextEntered(\'root_type\')'"/-->
|
||||
</th>
|
||||
|
||||
<tal:comment replace="nothing">Column "Actions"</tal:comment>
|
||||
|
@ -872,7 +955,7 @@
|
|||
</table>
|
||||
|
||||
<tal:comment replace="nothing">Appy (bottom) navigation</tal:comment>
|
||||
<metal:nav use-macro="here/skyn/macros/macros/appyNavigate"/>
|
||||
<metal:nav use-macro="here/skyn/navigate/macros/appyNavigate"/>
|
||||
</fieldset>
|
||||
</tal:result>
|
||||
|
||||
|
@ -952,91 +1035,3 @@
|
|||
</table>
|
||||
</form>
|
||||
</metal:transitions>
|
||||
|
||||
<div metal:define-macro="appyNavigate" tal:condition="python: totalNumber > batchSize" align="right">
|
||||
<tal:comment replace="nothing">
|
||||
Buttons for navigating among a list of elements (next, back, first, last, etc).
|
||||
</tal:comment>
|
||||
<table cellpadding="0" cellspacing="0" class="appyNav">
|
||||
<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.replace('**v**', '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.replace('**v**', str(sNumber)))"/></td>
|
||||
<tal:comment replace="nothing">Explain which elements are currently shown</tal:comment>
|
||||
<td class="discreet" valign="middle">
|
||||
<span tal:replace="python: startNumber+1"/>
|
||||
<img tal:attributes="src string: $portal_url/skyn/to.png"/>
|
||||
<span tal:replace="python: startNumber+len(objs)"/> <b>//</b>
|
||||
<span tal:replace="python: totalNumber"/>
|
||||
</td>
|
||||
<tal:comment replace="nothing">Go to the next page</tal:comment>
|
||||
<td><img style="cursor:pointer" tal:condition="python: sNumber < 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.replace('**v**', 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.replace('**v**', str(sNumber)))"/></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div metal:define-macro="objectNavigate" tal:condition="request/nav|nothing" align="right">
|
||||
<tal:comment replace="nothing">
|
||||
Buttons for going to next/previous elements if this one is among bunch of referenced or searched objects.
|
||||
currentNumber starts with 1.
|
||||
</tal:comment>
|
||||
<table cellpadding="0" cellspacing="0"
|
||||
tal:define="navInfo tool/getNavigationInfo;
|
||||
currentNumber navInfo/currentNumber;
|
||||
totalNumber navInfo/totalNumber;
|
||||
firstUrl navInfo/firstUrl;
|
||||
previousUrl navInfo/previousUrl;
|
||||
nextUrl navInfo/nextUrl;
|
||||
lastUrl navInfo/lastUrl;
|
||||
sourceUrl navInfo/sourceUrl;
|
||||
backText navInfo/backText">
|
||||
<tr>
|
||||
<tal:comment replace="nothing">Go to the source URL (search or referred object)</tal:comment>
|
||||
<td><a tal:condition="sourceUrl" tal:attributes="href sourceUrl"><img style="cursor:pointer"
|
||||
tal:attributes="src string: $portal_url/skyn/gotoSource.png;
|
||||
title python: backText + ' : ' + tool.translate('goto_source')"/></a></td>
|
||||
<tal:comment replace="nothing">Go to the first page</tal:comment>
|
||||
<td><a tal:condition="firstUrl" tal:attributes="href firstUrl"><img style="cursor:pointer"
|
||||
tal:attributes="src string: $portal_url/skyn/arrowLeftDouble.png;
|
||||
title python: tool.translate('goto_first')"/></a></td>
|
||||
<tal:comment replace="nothing">Go to the previous page</tal:comment>
|
||||
<td><a tal:condition="previousUrl" tal:attributes="href previousUrl"><img style="cursor:pointer"
|
||||
tal:attributes="src string: $portal_url/skyn/arrowLeftSimple.png;
|
||||
title python: tool.translate('goto_previous')"/></a></td>
|
||||
<tal:comment replace="nothing">Explain which element is currently shown</tal:comment>
|
||||
<td class="discreet" valign="middle">
|
||||
<span tal:replace="python: currentNumber"/> <b>//</b>
|
||||
<span tal:replace="python: totalNumber"/>
|
||||
</td>
|
||||
<tal:comment replace="nothing">Go to the next page</tal:comment>
|
||||
<td><a tal:condition="python: nextUrl" tal:attributes="href nextUrl"><img style="cursor:pointer"
|
||||
tal:attributes="src string: $portal_url/skyn/arrowRightSimple.png;
|
||||
title python: tool.translate('goto_next')"/></a></td>
|
||||
<tal:comment replace="nothing">Go to the last page</tal:comment>
|
||||
<td><a tal:condition="lastUrl" tal:attributes="href lastUrl"><img style="cursor:pointer"
|
||||
tal:attributes="src string: $portal_url/skyn/arrowRightDouble.png;
|
||||
title python: tool.translate('goto_last')"/></a></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
|
|
@ -21,132 +21,12 @@
|
|||
searchName python:context.REQUEST.get('search', '')">
|
||||
|
||||
<div metal:use-macro="here/skyn/macros/macros/pagePrologue"/>
|
||||
<script language="javascript">
|
||||
<!--
|
||||
function getSortValue(row, fieldName) {
|
||||
// Find, from p_fieldName, the cell that is used for sorting.
|
||||
var cellId = "field_" + fieldName;
|
||||
var cells = row.cells;
|
||||
for (var i=0; i < cells.length; i++) {
|
||||
if (cells[i].id == cellId) {
|
||||
// Ok we have the cell on which we must sort.
|
||||
// Now get the cell content.
|
||||
// If the cell contains links, content is the 1st link content
|
||||
var innerLinks = cells[i].getElementsByTagName("a");
|
||||
if (innerLinks.length > 0) {
|
||||
return innerLinks[0].innerHTML;
|
||||
} else {
|
||||
return cells[i].innerHTML;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sortRows(fieldName, ascending) {
|
||||
var queryRows = cssQuery('#query_row');
|
||||
// Create a wrapper for sorting
|
||||
var RowWrapper = function(row, fieldName) {
|
||||
this.value = getSortValue(row, fieldName);
|
||||
this.cloned_node = row.cloneNode(true);
|
||||
this.toString = function() {
|
||||
if (this.value.toString) {
|
||||
return this.value.toString();
|
||||
} else {
|
||||
return this.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Wrap nodes
|
||||
var items = new Array();
|
||||
for (var i=0; i<queryRows.length; i++) {
|
||||
items.push(new RowWrapper(queryRows[i], fieldName));
|
||||
}
|
||||
// Sort nodes
|
||||
items.sort();
|
||||
if (!ascending) {
|
||||
items.reverse();
|
||||
}
|
||||
// Reorder nodes
|
||||
for (var i=0; i<items.length; i++) {
|
||||
var dest = queryRows[i];
|
||||
dest.parentNode.replaceChild(items[i].cloned_node, dest);
|
||||
}
|
||||
};
|
||||
|
||||
function onSort(fieldName){
|
||||
// First, switch the sort arrow (up->down or down->up)
|
||||
var arrow = document.getElementById("arrow_" + fieldName);
|
||||
var sortAscending = (arrow.src.indexOf('arrowDown.gif') != -1);
|
||||
if (sortAscending){
|
||||
// Display "up" image
|
||||
arrow.src = arrow.src.replace('arrowDown.gif', 'arrowUp.gif')
|
||||
}
|
||||
else { // Display "down" image
|
||||
arrow.src = arrow.src.replace('arrowUp.gif', 'arrowDown.gif')
|
||||
}
|
||||
// Then, sort the rows on column "fieldName".
|
||||
sortRows(fieldName, sortAscending);
|
||||
}
|
||||
|
||||
function cellMatches(cell, searchValue) {
|
||||
// This function returns true if the HTML p_cell contains p_searchValue
|
||||
var innerLinks = cell.getElementsByTagName("a");
|
||||
// If the cell contains links, we search within the link contents
|
||||
for (var i=0; i < innerLinks.length; i++){
|
||||
var linkContent = innerLinks[i].innerHTML.toLowerCase();
|
||||
if (linkContent.indexOf(searchValue) != -1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// If we are here, we still have no match. Let's search directly within
|
||||
// the cell.
|
||||
var cellContent = cell.innerHTML.toLowerCase();
|
||||
if (cellContent.indexOf(searchValue) != -1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function onTextEntered(fieldName) {
|
||||
// Is called whenever text is entered into field named p_fieldName.
|
||||
var cellId = "field_" + fieldName
|
||||
var field = document.getElementById("filter_" + fieldName);
|
||||
var fieldValue = field.value.toLowerCase();
|
||||
if (fieldValue.length >= 3) {
|
||||
// Browse all rows and check if it should be visible or not.
|
||||
var queryRows = cssQuery('#query_row');
|
||||
for (var i=0; i < queryRows.length; i++) {
|
||||
// Find the value of the cell.
|
||||
var queryCells = queryRows[i].cells;
|
||||
for (var j=0; j < queryCells.length; j++) {
|
||||
if (queryCells[j].id == cellId) {
|
||||
if (cellMatches(queryCells[j], fieldValue)) {
|
||||
queryRows[i].style.display = "";
|
||||
}
|
||||
else {
|
||||
queryRows[i].style.display = "none";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Show all rows
|
||||
var queryRows = cssQuery('#query_row');
|
||||
for (var i=0; i < queryRows.length; i++) {
|
||||
queryRows[i].style.display = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
-->
|
||||
</script>
|
||||
|
||||
<tal:comment replace="nothing">Query result</tal:comment>
|
||||
<div id="queryResult"></div>
|
||||
|
||||
<script language="javascript"
|
||||
tal:define="ajaxUrl python: tool.getQueryUrl(contentType, flavourNumber, searchName)"
|
||||
tal:content="python: 'askAjaxChunk(\'queryResult\',\'%s\')' % ajaxUrl">
|
||||
tal:content="python: 'askQueryResult(\'queryResult\', \'%s\',\'%s\',\'%s\',\'%s\',0)' % (tool.absolute_url(), contentType, flavourNumber, searchName)">
|
||||
</script>
|
||||
</div>
|
||||
</body>
|
||||
|
|
|
@ -20,19 +20,19 @@
|
|||
<tr>
|
||||
<tal:comment replace="nothing">Arrows for moving objects up or down</tal:comment>
|
||||
<td class="noPadding" tal:condition="python: (len(objs)>1) and member.has_permission('Modify portal content', contextObj)">
|
||||
<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:moveRef define="objectIndex python: contextObj.getAppyRefIndex(fieldName, obj);
|
||||
ajaxBaseCall python: navBaseCall.replace('**v**', '\'%s\',\'ChangeRefOrder\', {\'refObjectUid\':\'%s\', \'move\':\'**v**\'}' % (startNumber, obj.UID()))">
|
||||
<tal:comment replace="nothing">Move up</tal:comment>
|
||||
<img tal:define="ajaxUrl python: baseUrl + '&move=up'" tal:condition="python: objectIndex > 0"
|
||||
<img tal:condition="python: objectIndex > 0"
|
||||
tal:attributes="src string: $portal_url/skyn/arrowUp.png;
|
||||
title python: tool.translate('move_up');
|
||||
onClick python: 'askAjaxChunk(\'%s\', \'%s\')' % (ajaxHookId, ajaxUrl)"
|
||||
onClick python: ajaxBaseCall.replace('**v**', 'up')"
|
||||
style="cursor:pointer"/>
|
||||
<tal:comment replace="nothing">Move down</tal:comment>
|
||||
<img tal:define="ajaxUrl python: baseUrl + '&move=down'" tal:condition="python: objectIndex < (totalNumber-1)"
|
||||
<img tal:condition="python: objectIndex < (totalNumber-1)"
|
||||
tal:attributes="src string: $portal_url/skyn/arrowDown.png;
|
||||
title python: tool.translate('move_down');
|
||||
onClick python: 'askAjaxChunk(\'%s\', \'%s\')' % (ajaxHookId, ajaxUrl)"
|
||||
onClick python: ajaxBaseCall.replace('**v**', 'down')"
|
||||
style="cursor:pointer"/>
|
||||
</tal:moveRef>
|
||||
</td>
|
||||
|
@ -63,6 +63,22 @@
|
|||
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 displays, in a cell header from a ref table, icons for sorting the
|
||||
ref field according to the field that corresponds to this column.
|
||||
</tal:comment>
|
||||
<metal:sortIcons define-macro="sortIcons"
|
||||
tal:define="ajaxBaseCall python: navBaseCall.replace('**v**', '\'%s\',\'SortReference\', {\'sortKey\':\'%s\', \'reverse\':\'**v**\'}' % (startNumber, shownField))">
|
||||
<img style="cursor:pointer"
|
||||
tal:attributes="src string:$portal_url/skyn/sortAsc.png;
|
||||
title python: tool.translate('sort_asc');
|
||||
onClick python: ajaxBaseCall.replace('**v**', 'False')"/>
|
||||
<img style="cursor:pointer"
|
||||
tal:attributes="src string:$portal_url/skyn/sortDesc.png;
|
||||
title python: tool.translate('sort_desc');
|
||||
onClick python: ajaxBaseCall.replace('**v**', 'True')"/>
|
||||
</metal:sortIcons>
|
||||
|
||||
<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
|
||||
|
@ -77,11 +93,10 @@
|
|||
- descrId (string) the i18n id of the reference field description
|
||||
</tal:comment>
|
||||
<div metal:define-macro="showReference"
|
||||
tal:define="ajaxHookId python: contextObj.UID() + fieldName;
|
||||
ajaxUrl python: contextObj.getUrl('showRef', **{'fieldName': fieldName, 'isBack': isBack, 'innerRef': innerRef, 'labelId': labelId, 'descrId': descrId})"
|
||||
tal:define="ajaxHookId python: contextObj.UID() + fieldName"
|
||||
tal:attributes="id ajaxHookId">
|
||||
<script language="javascript"
|
||||
tal:content="python: 'askAjaxChunk(\'%s\',\'%s\')' % (ajaxHookId, ajaxUrl)">
|
||||
tal:content="python: 'askRefField(\'%s\',\'%s\',\'%s\',\'%s\',\'%s\',\'%s\',\'%s\',0)' % (ajaxHookId, contextObj.absolute_url(), fieldName, isBack, innerRef, labelId, descrId)">
|
||||
</script>
|
||||
</div>
|
||||
|
||||
|
@ -113,7 +128,7 @@
|
|||
atMostOneRef python: (multiplicity[1] == 1) and (len(objs)<=1);
|
||||
label python: tool.translate(labelId);
|
||||
description python: tool.translate(descrId);
|
||||
baseUrl python: contextObj.getUrl('showRef', **{'fieldName': fieldName, 'isBack': isBack, 'innerRef': innerRef, 'labelId': labelId, 'descrId': descrId, '%s_startNumber' % ajaxHookId: '**v**'})">
|
||||
navBaseCall python: 'askRefField(\'%s\',\'%s\',\'%s\',\'%s\',\'%s\',\'%s\',\'%s\',**v**)' % (ajaxHookId, contextObj.absolute_url(), fieldName, isBack, innerRef, labelId, descrId)">
|
||||
|
||||
<tal:comment replace="nothing">This macro displays the Reference widget on a "consult" page.
|
||||
|
||||
|
@ -159,7 +174,7 @@
|
|||
tal:content="description" class="discreet" ></p>
|
||||
|
||||
<tal:comment replace="nothing">Appy (top) navigation</tal:comment>
|
||||
<metal:nav use-macro="here/skyn/macros/macros/appyNavigate"/>
|
||||
<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>
|
||||
|
@ -183,14 +198,21 @@
|
|||
align="right" tal:condition="python: not isBack and objs" cellpadding="0" cellspacing="0">
|
||||
<tr tal:condition="appyType/showHeaders">
|
||||
<th tal:condition="python: 'title' not in appyType['shownInfo']"
|
||||
tal:content="python: tool.translate('ref_name')"></th>
|
||||
tal:define="shownField python:'title'">
|
||||
<span tal:content="python: tool.translate('ref_name')"></span>
|
||||
<metal:sortIcons use-macro="here/skyn/ref/macros/sortIcons" />
|
||||
</th>
|
||||
<th tal:repeat="shownField appyType/shownInfo">
|
||||
<tal:showHeader condition="python: objs[0].getField(shownField)">
|
||||
<tal:titleHeader condition="python: shownField == 'title'"
|
||||
content="python: tool.translate('ref_name')"/>
|
||||
<tal:titleHeader condition="python: shownField == 'title'">
|
||||
<span tal:content="python: tool.translate('ref_name')"></span>
|
||||
<metal:sortIcons use-macro="here/skyn/ref/macros/sortIcons" />
|
||||
</tal:titleHeader>
|
||||
<tal:otherHeader condition="python: shownField != 'title'"
|
||||
define="labelId python: objs[0].getField(shownField).widget.label_msgid"
|
||||
content="python: tool.translate(labelId)"/>
|
||||
define="labelId python: objs[0].getField(shownField).widget.label_msgid">
|
||||
<span tal:content="python: tool.translate(labelId)"></span>
|
||||
<metal:sortIcons use-macro="here/skyn/ref/macros/sortIcons" />
|
||||
</tal:otherHeader>
|
||||
</tal:showHeader>
|
||||
</th>
|
||||
<th tal:content="python: tool.translate('ref_actions')"></th>
|
||||
|
@ -242,7 +264,7 @@
|
|||
</table>
|
||||
|
||||
<tal:comment replace="nothing">Appy (bottom) navigation</tal:comment>
|
||||
<metal:nav use-macro="here/skyn/macros/macros/appyNavigate"/>
|
||||
<metal:nav use-macro="here/skyn/navigate/macros/appyNavigate"/>
|
||||
|
||||
</fieldset>
|
||||
<tal:comment replace="nothing">A carriage return needed in some cases.</tal:comment>
|
||||
|
|
|
@ -123,13 +123,17 @@ class AbstractWrapper:
|
|||
# Update the ordered list of references
|
||||
self.o._appy_getSortedField(fieldName).append(obj.UID())
|
||||
|
||||
def sort(self, fieldName):
|
||||
'''Sorts referred elements linked to p_self via p_fieldName. At
|
||||
present, it can only sort elements based on their title.'''
|
||||
def sort(self, fieldName, sortKey='title', reverse=False):
|
||||
'''Sorts referred elements linked to p_self via p_fieldName according
|
||||
to a given p_sortKey which must be an attribute set on referred
|
||||
objects ("title", by default).'''
|
||||
sortedUids = getattr(self.o, '_appy_%s' % fieldName)
|
||||
c = self.o.uid_catalog
|
||||
sortedUids.sort(lambda x,y: \
|
||||
cmp(c(UID=x)[0].getObject().Title(),c(UID=y)[0].getObject().Title()))
|
||||
cmp(getattr(c(UID=x)[0].getObject().appy(), sortKey),
|
||||
getattr(c(UID=y)[0].getObject().appy(), sortKey)))
|
||||
if reverse:
|
||||
sortedUids.reverse()
|
||||
|
||||
def create(self, fieldNameOrClass, **kwargs):
|
||||
'''If p_fieldNameOfClass is the name of a field, this method allows to
|
||||
|
|
25
gen/utils.py
25
gen/utils.py
|
@ -10,11 +10,25 @@ class Descr:
|
|||
|
||||
class FieldDescr(Descr):
|
||||
def __init__(self, atField, appyType, fieldRel):
|
||||
self.atField = atField # The corresponding Archetypes field (may be None
|
||||
# in the case of backward references)
|
||||
self.appyType = appyType # The corresponding Appy type
|
||||
self.fieldRel = fieldRel # The field relatonship, needed when the field
|
||||
# description is a backward reference.
|
||||
# The corresponding Archetypes field (may be None in the case of
|
||||
# backward references)
|
||||
self.atField = atField
|
||||
# The corresponding Appy type
|
||||
self.appyType = appyType
|
||||
# The field relationship, needed when the field description is a
|
||||
# backward reference.
|
||||
self.fieldRel = fieldRel
|
||||
# Can we sort this field ?
|
||||
at = self.appyType
|
||||
self.sortable = False
|
||||
if not fieldRel and ((self.atField.getName() == 'title') or \
|
||||
(at['indexed'])):
|
||||
self.sortable = True
|
||||
# Can we filter this field?
|
||||
self.filterable = False
|
||||
if not fieldRel and at['indexed'] and (at['type'] == 'String') and \
|
||||
(at['format'] == 0) and not at['isSelect']:
|
||||
self.filterable = True
|
||||
if fieldRel:
|
||||
self.widgetType = 'backField'
|
||||
self.group = appyType['backd']['group']
|
||||
|
@ -25,6 +39,7 @@ class FieldDescr(Descr):
|
|||
self.group = appyType['group']
|
||||
self.show = appyType['show']
|
||||
self.page = appyType['page']
|
||||
fieldName = self.atField.getName()
|
||||
|
||||
class GroupDescr(Descr):
|
||||
def __init__(self, name, cols, page):
|
||||
|
|
|
@ -5,12 +5,12 @@ A POD template is a standard ODT file, where:
|
|||
a portion of the document zero, one or more times ("if" and "for" statements);
|
||||
- text insertions in "track changes" mode are interpreted as Python expressions.
|
||||
|
||||
When you invoke the tester.py program with one of those ODT files as unique parameter
|
||||
(ie "python tester.py ForCellOnlyOne.odt"), you get a result.odt file which is the
|
||||
When you run the Tester.py program with one of those ODT files as unique parameter
|
||||
(ie "python Tester.py ForCellOnlyOne.odt"), you get a result.odt file which is the
|
||||
result of executing the template with a bunch of Python objects. The "tests" dictionary
|
||||
defined in tester.py contains the objects that are given to each POD ODT template
|
||||
defined in Tester.py contains the objects that are given to each POD ODT template
|
||||
contained in this folder.
|
||||
|
||||
Opening the templates with OpenOffice (2.0 or higher), running tester.py on it and
|
||||
Opening the templates with OpenOffice (2.0 or higher), running Tester.py on it and
|
||||
checking the result in result.odt is probably the quickest way to have a good idea
|
||||
of what appy.pod can make for you !
|
||||
|
|
1062
pod/test/Tests.rtf
1062
pod/test/Tests.rtf
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue