[gen] Modified Ajax system to be able to ajax-refresh a single row within query results or ref tied object (ongoing work).

This commit is contained in:
Gaetan Delannay 2015-02-03 10:56:15 +01:00
parent f047242d81
commit 1bd6cf29a3
11 changed files with 417 additions and 302 deletions

View file

@ -97,6 +97,64 @@ class Field:
context[k] = ctx[k] context[k] = ctx[k]
return self.pxRender(context).encode('utf-8') return self.pxRender(context).encode('utf-8')
# Show the field content for some object on a list of results
pxRenderAsResult = Px('''
<!-- Title -->
<x if="field.name == 'title'"
var2="navInfo='search.%s.%s.%d.%d' % (className, searchName, \
startNumber+currentNumber, totalNumber)">
<x if="mayView"
var2="titleMode=inPopup and 'select' or 'link';
pageName=zobj.getDefaultViewPage();
selectJs=inPopup and 'onSelectObject(%s,%s,%s)' % (q(cbId), \
q(rootHookId), q(uiSearch.initiator.url))">
<x var="sup=zobj.getSupTitle(navInfo)" if="sup">::sup</x>
<x>::zobj.getListTitle(mode=titleMode, nav=navInfo, target=target, \
page=pageName, inPopup=inPopup, selectJs=selectJs, highlight=True)</x>
<span style=":showSubTitles and 'display:inline' or 'display:none'"
name="subTitle" var="sub=zobj.getSubTitle()"
if="sub">::zobj.highlight(sub)</span>
<!-- Actions -->
<div if="not inPopup and uiSearch.showActions and zobj.mayAct()"
class="objectActions" style=":'display:%s' % uiSearch.showActions"
var2="layoutType='buttons'" >
<!-- Edit -->
<a if="zobj.mayEdit()"
var2="linkInPopup=inPopup or (target.target != '_self')"
target=":target.target" onclick=":target.openPopup"
href=":zobj.getUrl(mode='edit', page=zobj.getDefaultEditPage(), \
nav=navInfo, inPopup=linkInPopup)">
<img src=":url('edit')" title=":_('object_edit')"/>
</a>
<!-- Delete -->
<img if="zobj.mayDelete()" class="clickable" src=":url('delete')"
title=":_('object_delete')"
onClick=":'onDeleteObject(%s)' % q(zobj.id)"/>
<!-- Workflow transitions -->
<x if="zobj.showTransitions('result')"
var2="targetObj=zobj">:targetObj.appy().pxTransitions</x>
<!-- Fields (actions) defined with layout "buttons" -->
<x if="not inPopup"
var2="fields=zobj.getAppyTypes('buttons', 'main');
layoutType='cell'">
<!-- Call pxCell and not pxRender to avoid having a table -->
<x for="field in fields"
var2="name=field.name; smallButtons=True">:field.pxCell</x>
</x>
</div>
</x>
<x if="not mayView">
<img src=":url('fake')" style="margin-right: 5px"/>
<x>:_('unauthorized')</x>
</x>
</x>
<!-- Any other field -->
<x if="(field.name != 'title') and mayView">
<x var="layoutType='cell'; innerRef=True"
if="field.isShowable(zobj, 'result')">:field.pxRender</x>
</x>''')
# Displays a field label # Displays a field label
pxLabel = Px('''<label if="field.hasLabel and field.renderLabel" pxLabel = Px('''<label if="field.hasLabel and field.renderLabel"
lfor=":field.name">::_('label', field=field)</label>''') lfor=":field.name">::_('label', field=field)</label>''')

View file

@ -80,7 +80,7 @@ class Ref(Field):
# (edit, delete, etc). # (edit, delete, etc).
pxObjectActions = Px(''' pxObjectActions = Px('''
<div if="field.showActions" class="objectActions" <div if="field.showActions" class="objectActions"
style=":'display:%s' % field.showActions"> style=":'display:%s' % field.showActions" var2="layoutType='buttons'">
<!-- Arrows for moving objects up or down --> <!-- Arrows for moving objects up or down -->
<x if="(totalNumber &gt;1) and changeOrder and not inPickList \ <x if="(totalNumber &gt;1) and changeOrder and not inPickList \
and not inMenu" and not inMenu"
@ -132,7 +132,7 @@ class Ref(Field):
q(field.name), q(tiedUid))"/> q(field.name), q(tiedUid))"/>
<!-- Workflow transitions --> <!-- Workflow transitions -->
<x if="tied.o.showTransitions('result')" <x if="tied.o.showTransitions('result')"
var2="targetObj=tied.o; buttonsMode='small'">:tied.pxTransitions</x> var2="targetObj=tied.o">:tied.pxTransitions</x>
<!-- Fields (actions) defined with layout "buttons" --> <!-- Fields (actions) defined with layout "buttons" -->
<x if="not inPopup" <x if="not inPopup"
var2="fields=tied.o.getAppyTypes('buttons', 'main'); var2="fields=tied.o.getAppyTypes('buttons', 'main');
@ -279,7 +279,8 @@ class Ref(Field):
class=":loop.tied.odd and 'even' or 'odd'" class=":loop.tied.odd and 'even' or 'odd'"
var2="tiedUid=tied.o.id; var2="tiedUid=tied.o.id;
objectIndex=field.getIndexOf(zobj, tiedUid)|None; objectIndex=field.getIndexOf(zobj, tiedUid)|None;
mayView=tied.o.mayView()"> mayView=tied.o.mayView()"
id=":'%s_%s' % (ajaxHookId, tiedUid)">
<td if="not inPickList and numbered">:field.pxNumber</td> <td if="not inPickList and numbered">:field.pxNumber</td>
<td if="checkboxes" class="cbCell"> <td if="checkboxes" class="cbCell">
<input if="mayView" type="checkbox" name=":ajaxHookId" checked="checked" <input if="mayView" type="checkbox" name=":ajaxHookId" checked="checked"

View file

@ -146,14 +146,6 @@ class Search:
return gutils.callMethod(tool, self.show, klass=klass) return gutils.callMethod(tool, self.show, klass=klass)
return self.show return self.show
def getCbJsInit(self, hookId):
'''Returns the code that creates JS data structures for storing the
status of checkboxes for every result of this search.'''
default = self.checkboxesDefault and 'unchecked' or 'checked'
return '''var node=document.getElementById('%s');
node['_appy_objs_cbs'] = {};
node['_appy_objs_sem'] = '%s';''' % (hookId, default)
def getSessionKey(self, className, full=True): def getSessionKey(self, className, full=True):
'''Returns the name of the key, in the session, where results for this '''Returns the name of the key, in the session, where results for this
search are stored when relevant. If p_full is False, only the suffix search are stored when relevant. If p_full is False, only the suffix
@ -166,7 +158,7 @@ class Search:
class UiSearch: class UiSearch:
'''Instances of this class are generated on-the-fly for manipulating a '''Instances of this class are generated on-the-fly for manipulating a
Search from the User Interface.''' Search from the User Interface.'''
# PX for rendering a search. # Rendering a search
pxView = Px(''' pxView = Px('''
<div class="portletSearch"> <div class="portletSearch">
<a href=":'%s?className=%s&amp;search=%s' % \ <a href=":'%s?className=%s&amp;search=%s' % \
@ -175,11 +167,169 @@ class UiSearch:
title=":search.translatedDescr">:search.translated</a> title=":search.translatedDescr">:search.translated</a>
</div>''') </div>''')
# Search results, as a list (used by pxResult below)
pxResultList = Px('''
<x var="showHeaders=showHeaders|True;
checkboxes=uiSearch.search.checkboxes;
checkboxesId=rootHookId + '_objs';
cbShown=uiSearch.showCheckboxes();
cbDisplay=cbShown and 'table-cell' or 'none'">
<script>:uiSearch.getAjaxData(ajaxHookId, ztool, popup=inPopup, \
checkboxes=checkboxes, checkboxesId=checkboxesId, \
cbDisplay=cbDisplay, startNumber=startNumber, \
totalNumber=totalNumber)</script>
<table class="list" width="100%">
<!-- Headers, with filters and sort arrows -->
<tr if="showHeaders">
<th if="checkboxes" class="cbCell" style=":'display:%s' % cbDisplay">
<img src=":url('checkall')" class="clickable"
title=":_('check_uncheck')"
onclick=":'toggleAllCbs(%s)' % q(checkboxesId)"/>
</th>
<th for="column in columns"
var2="field=column.field;
sortable=field.isSortable(usage='search');
filterable=field.filterable"
width=":column.width" align=":column.align">
<x>::ztool.truncateText(_(field.labelId))</x>
<x if="(totalNumber &gt; 1) or filterValue">:tool.pxSortAndFilter</x>
<x>:tool.pxShowDetails</x>
</th>
</tr>
<!-- Results -->
<tr if="not zobjects">
<td colspan=":len(columns)+1">:_('query_no_result')</td>
</tr>
<x for="zobj in zobjects"
var2="@currentNumber=currentNumber + 1;
rowCss=loop.zobj.odd and 'even' or 'odd'">:obj.pxViewAsResult</x>
</table>
<!-- The button for selecting objects and closing the popup -->
<div if="inPopup and cbShown" align=":dleft">
<input type="button"
var="label=_('object_link_many'); css=ztool.getButtonCss(label)"
value=":label" class=":css" style=":url('linkMany', bg=True)"
onclick=":'onSelectObjects(%s,%s,%s,%s,%s,%s,%s)' % \
(q(rootHookId), q(uiSearch.initiator.url), \
q(uiSearch.initiatorMode), q(sortKey), q(sortOrder), \
q(filterKey), q(filterValue))"/>
</div>
<!-- Init checkboxes if present -->
<script if="checkboxes">:'initCbs(%s)' % q(checkboxesId)</script>
<script>:'initFocus(%s)' % q(ajaxHookId)</script></x>''')
# Search results, as a grid (used by pxResult below)
pxResultGrid = Px('''
<table width="100%"
var="modeElems=resultMode.split('_');
cols=(len(modeElems)==2) and int(modeElems[1]) or 4;
rows=ztool.splitList(zobjects, cols)">
<tr for="row in rows" valign="middle">
<td for="zobj in row" width=":'%d%%' % (100/cols)" align="center"
style="padding-top: 25px"
var2="obj=zobj.appy(); mayView=zobj.mayView()">
<x var="@currentNumber=currentNumber + 1"
for="column in columns"
var2="field=column.field">:field.pxRenderAsResult</x>
</td>
</tr>
</table>''')
# Render search results
pxResult = Px('''
<div var="ajaxHookId='queryResult';
className=req['className'];
searchName=req.get('search', '');
uiSearch=uiSearch|ztool.getSearch(className,searchName,ui=True);
rootHookId=uiSearch.getRootHookId();
refInfo=ztool.getRefInfo();
refObject=refInfo[0];
refField=refInfo[1];
refUrlPart=refObject and ('&amp;ref=%s:%s' % (refObject.id, \
refField)) or '';
startNumber=req.get('startNumber', '0');
startNumber=int(startNumber);
sortKey=req.get('sortKey', '');
sortOrder=req.get('sortOrder', 'asc');
filterKey=req.get('filterKey', '');
filterValue=req.get('filterValue', '');
queryResult=ztool.executeQuery(className, \
search=uiSearch.search, startNumber=startNumber, \
remember=True, sortBy=sortKey, sortOrder=sortOrder, \
filterKey=filterKey, filterValue=filterValue, \
refObject=refObject, refField=refField);
zobjects=queryResult.objects;
totalNumber=queryResult.totalNumber;
batchSize=queryResult.batchSize;
batchNumber=len(zobjects);
navBaseCall='askQueryResult(%s,%s,%s,%s,%s,**v**)' % \
(q(ajaxHookId), q(ztool.absolute_url()), q(className), \
q(searchName),int(inPopup));
showNewSearch=showNewSearch|True;
newSearchUrl='%s/search?className=%s%s' % \
(ztool.absolute_url(), className, refUrlPart);
showSubTitles=req.get('showSubTitles', 'true') == 'true';
klass=ztool.getAppyClass(className);
resultMode=uiSearch.getResultMode(klass);
target=ztool.getLinksTargetInfo(klass)"
id=":ajaxHookId">
<x if="zobjects or filterValue">
<!-- Display here POD templates if required -->
<table var="fields=ztool.getResultPodFields(className);
layoutType='view'"
if="not inPopup and zobjects and fields" align=":dright">
<tr>
<td var="zobj=zobjects[0]; obj=zobj.appy()"
for="field in fields"
class=":not loop.field.last and 'pod' or ''">:field.pxRender</td>
</tr>
</table>
<!-- The title of the search -->
<p if="not inPopup">
<x>::uiSearch.translated</x> (<span class="discreet">:totalNumber</span>)
<x if="showNewSearch and (searchName == 'customSearch')">&nbsp;&mdash;
&nbsp;<i><a href=":newSearchUrl">:_('search_new')</a></i>
</x>
</p>
<table width="100%">
<tr valign="top">
<!-- Search description -->
<td if="uiSearch.translatedDescr">
<span class="discreet">:uiSearch.translatedDescr</span><br/>
</td>
<!-- (Top) navigation -->
<td align=":dright" width="150px">:tool.pxNavigate</td>
</tr>
</table>
<!-- Results, as a list or grid -->
<x var="columnLayouts=ztool.getResultColumnsLayouts(className, refInfo);
columns=ztool.getColumnsSpecifiers(className,columnLayouts,dir);
currentNumber=0">
<x if="resultMode == 'list'">:uiSearch.pxResultList</x>
<x if="resultMode != 'list'">:uiSearch.pxResultGrid</x>
</x>
<!-- (Bottom) navigation -->
<x>:tool.pxNavigate</x>
</x>
<x if="not zobjects and not filterValue">
<x>:_('query_no_result')</x>
<x if="showNewSearch and (searchName == 'customSearch')"><br/>
<i class="discreet"><a href=":newSearchUrl">:_('search_new')</a></i></x>
</x>
</div>''')
def __init__(self, search, className, tool): def __init__(self, search, className, tool):
self.search = search self.search = search
self.name = search.name self.name = search.name
self.type = 'search' self.type = 'search'
self.colspan = search.colspan self.colspan = search.colspan
self.className = className
# Property "display" of the div tag containing actions for every search # Property "display" of the div tag containing actions for every search
# result. # result.
self.showActions = search.showActions self.showActions = search.showActions
@ -188,7 +338,7 @@ class UiSearch:
self.translated = search.translated self.translated = search.translated
self.translatedDescr = search.translatedDescr self.translatedDescr = search.translatedDescr
else: else:
# The label may be specific in some special cases. # The label may be specific in some special cases
labelDescr = '' labelDescr = ''
if search.name == 'allSearch': if search.name == 'allSearch':
label = '%s_plural' % className label = '%s_plural' % className
@ -221,6 +371,11 @@ class UiSearch:
Else, simply return the name of the search.''' Else, simply return the name of the search.'''
return getattr(self, 'initiatorHook', self.name) return getattr(self, 'initiatorHook', self.name)
def getResultMode(self, klass):
'''Must we show, on pxResult, instances of p_klass as a list or
as a grid?'''
return getattr(klass, 'resultMode', 'list')
def showCheckboxes(self): def showCheckboxes(self):
'''If checkboxes are enabled for this search (and if an initiator field '''If checkboxes are enabled for this search (and if an initiator field
is there), they must be visible only if the initiator field is is there), they must be visible only if the initiator field is
@ -229,4 +384,32 @@ class UiSearch:
the DOM because they store object UIDs.''' the DOM because they store object UIDs.'''
if not self.search.checkboxes: return if not self.search.checkboxes: return
return not self.initiator or self.initiatorField.isMultiValued() return not self.initiator or self.initiatorField.isMultiValued()
def getCbJsInit(self, hookId):
'''Returns the code that creates JS data structures for storing the
status of checkboxes for every result of this search.'''
default = self.search.checkboxesDefault and 'unchecked' or 'checked'
return '''var node=document.getElementById('%s');
node['_appy_objs_cbs'] = {};
node['_appy_objs_sem'] = '%s';''' % (hookId, default)
def getAjaxData(self, hook, ztool, **params):
'''Initializes an AjaxData object on the DOM node corresponding to
p_hook = the whole search result.'''
# Complete params with default parameters
params['className'] = self.className
params['searchName'] = self.name
params = sutils.getStringDict(params)
return "document.getElementById('%s')['ajax']=new AjaxData('%s', " \
"'pxResult', %s, null, '%s')" % \
(hook, hook, params, ztool.absolute_url())
def getAjaxDataRow(self, zobj, parentHook, **params):
'''Initializes an AjaxData object on the DOM node corresponding to
p_hook = a row within the list of results.'''
hook = zobj.id
return "document.getElementById('%s')['ajax']=new AjaxData('%s', " \
"'pxViewAsResultFromAjax',%s,'%s','%s')" % \
(hook, hook, sutils.getStringDict(params), parentHook,
zobj.absolute_url())
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View file

@ -361,7 +361,7 @@ class Transition:
"onTrigger" on the workflow class by convention. The common action is "onTrigger" on the workflow class by convention. The common action is
executed before the transition-specific action (if any).''' executed before the transition-specific action (if any).'''
obj = obj.appy() obj = obj.appy()
wf = wf.__instance__ # We need the prototypical instance here. wf = wf.__instance__ # We need the prototypical instance here
wf.onTrigger(obj, name, fromState) wf.onTrigger(obj, name, fromState)
def trigger(self, name, obj, wf, comment, doAction=True, doHistory=True, def trigger(self, name, obj, wf, comment, doAction=True, doHistory=True,
@ -374,11 +374,11 @@ class Transition:
the transition is triggered programmatically, and no message is the transition is triggered programmatically, and no message is
returned to the user. If p_reindex is False, object reindexing will returned to the user. If p_reindex is False, object reindexing will
be performed by the calling method.''' be performed by the calling method.'''
# "Triggerability" and security checks. # "Triggerability" and security checks
if (name != '_init_') and \ if (name != '_init_') and \
not self.isTriggerable(obj, wf, noSecurity=noSecurity): not self.isTriggerable(obj, wf, noSecurity=noSecurity):
raise Exception('Transition "%s" can\'t be triggered.' % name) raise Exception('Transition "%s" can\'t be triggered.' % name)
# Create the workflow_history dict if it does not exist. # Create the workflow_history dict if it does not exist
if not hasattr(obj.aq_base, 'workflow_history'): if not hasattr(obj.aq_base, 'workflow_history'):
from persistent.mapping import PersistentMapping from persistent.mapping import PersistentMapping
obj.workflow_history = PersistentMapping() obj.workflow_history = PersistentMapping()
@ -403,11 +403,11 @@ class Transition:
action = None action = None
fromState = None fromState = None
else: else:
fromState = obj.State() # Remember the "from" (=start) state. fromState = obj.State() # Remember the "from" (=start) state
if not doHistory: comment = '_invisible_' if not doHistory: comment = '_invisible_'
obj.addHistoryEvent(action, review_state=targetStateName, obj.addHistoryEvent(action, review_state=targetStateName,
comments=comment) comments=comment)
# Execute the action that is common to all transitions, if defined. # Execute the action that is common to all transitions, if defined
if doAction and hasattr(wf, 'onTrigger'): if doAction and hasattr(wf, 'onTrigger'):
self.executeCommonAction(obj, name, wf, fromState) self.executeCommonAction(obj, name, wf, fromState)
# Execute the related action if needed # Execute the related action if needed
@ -417,19 +417,22 @@ class Transition:
# (Allowed, State) need to be updated here. # (Allowed, State) need to be updated here.
if reindex and not obj.isTemporary(): obj.reindex() if reindex and not obj.isTemporary(): obj.reindex()
# Return a message to the user if needed # Return a message to the user if needed
if not doSay or (name == '_init_'): return if not doSay: return
if not msg: msg = obj.translate('object_saved') if not msg: msg = obj.translate('object_saved')
obj.say(msg) return msg
def onUiRequest(self, obj, wf, name, rq): def onUiRequest(self, obj, wf, name, rq):
'''Executed when a user wants to trigger this transition from the UI.''' '''Executed when a user wants to trigger this transition from the UI.'''
tool = obj.getTool() tool = obj.getTool()
# Trigger the transition # Trigger the transition
self.trigger(name, obj, wf, rq.get('comment', ''), reindex=False) msg = self.trigger(name, obj, wf, rq.get('comment', ''), reindex=False)
# Reindex obj if required. # Reindex obj if required
if not obj.isTemporary(): obj.reindex() if not obj.isTemporary(): obj.reindex()
# If we are called from an Ajax request, return a message
if hasattr(rq, 'pxContext') and rq.pxContext['ajax']: return msg
# If we are viewing the object and if the logged user looses the # If we are viewing the object and if the logged user looses the
# permission to view it, redirect the user to its home page. # permission to view it, redirect the user to its home page.
if msg: obj.say(msg)
if not obj.mayView() and \ if not obj.mayView() and \
(obj.absolute_url_path() in rq['HTTP_REFERER']): (obj.absolute_url_path() in rq['HTTP_REFERER']):
back = tool.getHomePage() back = tool.getHomePage()
@ -462,22 +465,24 @@ class Transition:
if startOk and endOk: return trName if startOk and endOk: return trName
class UiTransition: class UiTransition:
'''Represents a widget that displays a transition.''' '''Represents a widget that displays a transition'''
pxView = Px(''' pxView = Px('''
<x var="label=transition.title; <x var="label=transition.title;
css=ztool.getButtonCss(label, buttonsMode == 'small')"> inButtons=layoutType == 'buttons';
css=ztool.getButtonCss(label, inButtons)">
<!-- Real button --> <!-- Real button -->
<input if="transition.mayTrigger" type="button" class=":css" <input if="transition.mayTrigger" type="button" class=":css"
var="back=inButtons and q(zobj.id) or 'null'"
style=":url(transition.icon, bg=True)" value=":label" style=":url(transition.icon, bg=True)" value=":label"
onclick=":'triggerTransition(%s,%s,%s)' % (q(formId), \ onclick=":'triggerTransition(%s,%s,%s,%s)' % (q(formId), \
q(transition.name), q(transition.confirm))"/> q(transition.name), q(transition.confirm), back)"/>
<!-- Fake button, explaining why the transition can't be triggered --> <!-- Fake button, explaining why the transition can't be triggered -->
<input if="not transition.mayTrigger" type="button" <input if="not transition.mayTrigger" type="button"
class=":'fake %s' % css" style=":url('fake', bg=True)" class=":'fake %s' % css" style=":url('fake', bg=True)"
value=":label" title=":transition.reason"/></x>''') value=":label" title=":transition.reason"/></x>''')
def __init__(self, name, transition, obj, mayTrigger, ): def __init__(self, name, transition, obj, mayTrigger):
self.name = name self.name = name
self.transition = transition self.transition = transition
self.type = 'transition' self.type = 'transition'
@ -494,7 +499,7 @@ class UiTransition:
if not mayTrigger: if not mayTrigger:
self.mayTrigger = False self.mayTrigger = False
self.reason = mayTrigger.msg self.reason = mayTrigger.msg
# Required by the UiGroup. # Required by the UiGroup
self.colspan = 1 self.colspan = 1
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View file

@ -272,12 +272,6 @@ class ToolMixin(BaseMixin):
for key in self.queryParamNames]) for key in self.queryParamNames])
return res return res
def getResultMode(self, className):
'''Must we show, on pxQueryResult, instances of p_className as a list or
as a grid?'''
klass = self.getAppyClass(className)
return getattr(klass, 'resultMode', 'list')
def showPortlet(self, obj, layoutType): def showPortlet(self, obj, layoutType):
'''When must the portlet be shown? p_obj and p_layoutType can be None '''When must the portlet be shown? p_obj and p_layoutType can be None
if we are not browing any objet (ie, we are on the home page).''' if we are not browing any objet (ie, we are on the home page).'''
@ -1298,4 +1292,18 @@ class ToolMixin(BaseMixin):
if field: msg = getattr(field, action)(obj.o) if field: msg = getattr(field, action)(obj.o)
else: msg = getattr(obj.o, action)() else: msg = getattr(obj.o, action)()
return msg return msg
def updatePxContextFromRequest(self):
'''Takes any user-defined key from the request and put it as a variable
on the current PX context.'''
req = self.REQUEST
ctx = req.pxContext
# Get "form" data (get, post) and cookie values
for source in (req.form, req.cookies):
for k, v in source.iteritems():
# Convert v to some Python data when relevant
if v in ('True', 'False', 'true', 'false'):
exec 'v = %s' % v.capitalize()
elif v.isdigit(): v = int(v)
ctx[k] = v
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View file

@ -1019,9 +1019,9 @@ class BaseMixin:
wf = self.getWorkflow() wf = self.getWorkflow()
# Get the initial workflow state # Get the initial workflow state
initialState = self.State(name=False) initialState = self.State(name=False)
# Create a Transition instance representing the initial transition. # Create a Transition instance representing the initial transition
initialTransition = gen.Transition((initialState, initialState)) initialTransition = gen.Transition((initialState, initialState))
initialTransition.trigger('_init_', self, wf, '') initialTransition.trigger('_init_', self, wf, '', doSay=False)
def getWorkflow(self, name=False, className=None): def getWorkflow(self, name=False, className=None):
'''Returns the workflow applicable for p_self (or for any instance of '''Returns the workflow applicable for p_self (or for any instance of

View file

@ -79,7 +79,7 @@ function XhrObject() { // Wraps a XmlHttpRequest object
replaced by result of executing the Ajax request. */ replaced by result of executing the Ajax request. */
this.onGet = ''; /* The name of a Javascript function to call once we this.onGet = ''; /* The name of a Javascript function to call once we
receive the result. */ receive the result. */
this.info = {}; /* An associative array for putting anything else. */ this.info = {}; /* An associative array for putting anything else */
} }
/* When inserting HTML at some DOM node in a page via Ajax, scripts defined in /* When inserting HTML at some DOM node in a page via Ajax, scripts defined in
@ -228,11 +228,61 @@ function askAjaxChunk(hook,mode,url,px,params,beforeSend,onGet) {
} }
} }
// Object representing all the data required to perform an Ajax request
function AjaxData(hook, px, params, parentHook, url, mode, beforeSend, onGet) {
this.hook = hook;
this.mode = mode;
if (!mode) this.mode = 'GET';
this.url = url;
this.px = px;
this.params = params;
this.beforeSend = beforeSend;
this.onGet = onGet;
/* If a parentHook is spefified, this AjaxData must be completed with a parent
AjaxData instance. */
this.parentHook = parentHook;
}
function askAjax(hook, form) {
/* Call askAjaxChunk by getting an AjaxData instance from p_hook and a
potential action from p_form). */
var d = document.getElementById(hook)['ajax'];
// Complete data with a parent data if present
if (d['parentHook']) {
var parent = document.getElementById(d['parentHook'])['ajax'];
for (var key in parent) {
if (key == 'params') continue; // Will get a specific treatment herafter
if (!d[key]) d[key] = parent[key]; // Override if no value on child
}
// Merge parameters
if (parent.params) {
for (var key in parent.params) {
if (key in d.params) continue; // Override if not value on child
d.params[key] = parent.params[key];
}
}
}
// If a p_form id is given, integrate the form submission in the ajax request
if (form) {
var f = document.getElementById(form);
var mode = 'POST';
// Deduce the action from the form action
d.params['action'] = _rsplit(f.action, '/', 2)[1];
// Get the other params
var elems = f.elements;
for (var i=0; i < elems.length; i++) {
d.params[elems[i].name] = elems[i].value;
}
}
else var mode = d.mode;
askAjaxChunk(d.hook,mode,d.url,d.px,d.params,d.beforeSend,d.onGet) }
/* The functions below wrap askAjaxChunk for getting specific content through /* The functions below wrap askAjaxChunk for getting specific content through
an Ajax request. */ an Ajax request. */
function askQueryResult(hookId, objectUrl, className, searchName, popup, function askQueryResult(hookId, objectUrl, className, searchName, popup,
startNumber, sortKey, sortOrder, filterKey) { startNumber, sortKey, sortOrder, filterKey) {
// Sends an Ajax request for getting the result of a query. // Sends an Ajax request for getting the result of a query
var params = {'className': className, 'search': searchName, var params = {'className': className, 'search': searchName,
'startNumber': startNumber, 'popup': popup}; 'startNumber': startNumber, 'popup': popup};
if (sortKey) params['sortKey'] = sortKey; if (sortKey) params['sortKey'] = sortKey;
@ -244,8 +294,8 @@ function askQueryResult(hookId, objectUrl, className, searchName, popup,
params['filterValue'] = encodeURIComponent(filterWidget.value); params['filterValue'] = encodeURIComponent(filterWidget.value);
} }
} }
askAjaxChunk(hookId, 'GET', objectUrl, 'pxQueryResult', params, null, var px = className + ':' + searchName + ':' + 'pxResult';
evalInnerScripts); askAjaxChunk(hookId, 'GET', objectUrl, px, params, null, evalInnerScripts);
} }
function askObjectHistory(hookId, objectUrl, maxPerPage, startNumber) { function askObjectHistory(hookId, objectUrl, maxPerPage, startNumber) {
@ -265,7 +315,7 @@ function askRefField(hookId, objectUrl, innerRef, startNumber, action,
params[startKey] = startNumber; params[startKey] = startNumber;
if (action) params['action'] = action; if (action) params['action'] = action;
if (actionParams) { if (actionParams) {
for (key in actionParams) { for (var key in actionParams) {
if ((key == 'move') && (typeof actionParams[key] == 'object')) { if ((key == 'move') && (typeof actionParams[key] == 'object')) {
// Get the new index from an input field // Get the new index from an input field
var id = actionParams[key].id; var id = actionParams[key].id;
@ -343,7 +393,7 @@ function _rsplit(s, delimiter, limit) {
var elems = s.split(delimiter); var elems = s.split(delimiter);
var exc = elems.length - limit; var exc = elems.length - limit;
if (exc <= 0) return elems; if (exc <= 0) return elems;
// Merge back first elements to get p_limit elements. // Merge back first elements to get p_limit elements
var head = ''; var head = '';
var res = []; var res = [];
for (var i=0; i < elems.length; i++) { for (var i=0; i < elems.length; i++) {
@ -582,12 +632,22 @@ function submitAppyForm(button) {
} }
// Function used for triggering a workflow transition // Function used for triggering a workflow transition
function triggerTransition(formId, transitionId, msg) { function triggerTransition(formId, transitionId, msg, back) {
var theForm = document.getElementById(formId); var f = document.getElementById(formId);
theForm.transition.value = transitionId; f.transition.value = transitionId;
if (!msg) { theForm.submit(); } if (!msg) {
else { // Ask the user to confirm. /* We must submit the form and either refresh the entire page (back is null)
askConfirm('form', formId, msg, true); or ajax-refresh a given part only (p_back corresponds to the id of the
DOM node to be refreshed. */
if (back) askAjax(back, formId);
else f.submit();
}
else {
// Ask a confirmation to the user before proceeding
if (back) {
var js = "askAjax('"+back+"', '"+formId+"');"
askConfirm('script', js, msg, true) }
else askConfirm('form', formId, msg, true);
} }
} }

View file

@ -100,11 +100,10 @@ class ToolWrapper(AbstractWrapper):
<x if="cssJs"> <x if="cssJs">
<link for="cssFile in cssJs['css']" rel="stylesheet" type="text/css" <link for="cssFile in cssJs['css']" rel="stylesheet" type="text/css"
href=":url(cssFile)"/> href=":url(cssFile)"/>
<script for="jsFile in cssJs['js']" type="text/javascript" <script for="jsFile in cssJs['js']" src=":url(jsFile)"></script></x>
src=":url(jsFile)"></script></x>
<!-- Javascript messages --> <!-- Javascript messages -->
<script type="text/javascript">::ztool.getJavascriptMessages()</script> <script>::ztool.getJavascriptMessages()</script>
<!-- Global form for deleting an object --> <!-- Global form for deleting an object -->
<form id="deleteForm" method="post" action="do"> <form id="deleteForm" method="post" action="do">
@ -271,7 +270,7 @@ class ToolWrapper(AbstractWrapper):
</x> </x>
</x>''') </x>''')
# The message that is shown when a user triggers an action. # The message that is shown when a user triggers an action
pxMessage = Px(''' pxMessage = Px('''
<div class=":inPopup and 'messagePopup message' or 'message'" <div class=":inPopup and 'messagePopup message' or 'message'"
style="display:none" id="appyMessage"> style="display:none" id="appyMessage">
@ -284,7 +283,7 @@ class ToolWrapper(AbstractWrapper):
<script type="text/javascript" var="messages=ztool.consumeMessages()" <script type="text/javascript" var="messages=ztool.consumeMessages()"
if="messages">::'showAppyMessage(%s)' % q(messages)</script>''') if="messages">::'showAppyMessage(%s)' % q(messages)</script>''')
# The page footer. # The page footer
pxFooter = Px(''' pxFooter = Px('''
<table cellpadding="0" cellspacing="0" width="100%" class="footer"> <table cellpadding="0" cellspacing="0" width="100%" class="footer">
<tr> <tr>
@ -343,231 +342,6 @@ class ToolWrapper(AbstractWrapper):
</tr> </tr>
</table>''', template=AbstractWrapper.pxTemplate, hook='content') </table>''', template=AbstractWrapper.pxTemplate, hook='content')
# Show on query list or grid, the field content for a given object.
pxQueryField = Px('''
<!-- Title -->
<x if="field.name == 'title'">
<x if="mayView"
var2="navInfo='search.%s.%s.%d.%d' % \
(className, searchName, startNumber+currentNumber, totalNumber);
titleMode=inPopup and 'select' or 'link';
pageName=zobj.getDefaultViewPage();
selectJs=inPopup and 'onSelectObject(%s,%s,%s)' % (q(cbId), \
q(rootHookId), q(uiSearch.initiator.url))">
<x var="sup=zobj.getSupTitle(navInfo)" if="sup">::sup</x>
<x>::zobj.getListTitle(mode=titleMode, nav=navInfo, target=target, \
page=pageName, inPopup=inPopup, selectJs=selectJs, highlight=True)</x>
<span style=":showSubTitles and 'display:inline' or 'display:none'"
name="subTitle" var="sub=zobj.getSubTitle()"
if="sub">::zobj.highlight(sub)</span>
<!-- Actions -->
<div if="not inPopup and uiSearch.showActions and zobj.mayAct()"
class="objectActions" style=":'display:%s' % uiSearch.showActions">
<!-- Edit -->
<a if="zobj.mayEdit()"
var2="navInfo='search.%s.%s.%d.%d' % \
(className, searchName, loop.zobj.nb+1+startNumber, totalNumber);
linkInPopup=inPopup or (target.target != '_self')"
target=":target.target" onclick=":target.openPopup"
href=":zobj.getUrl(mode='edit', page=zobj.getDefaultEditPage(), \
nav=navInfo, inPopup=linkInPopup)">
<img src=":url('edit')" title=":_('object_edit')"/>
</a>
<!-- Delete -->
<img if="zobj.mayDelete()" class="clickable" src=":url('delete')"
title=":_('object_delete')"
onClick=":'onDeleteObject(%s)' % q(zobj.id)"/>
<!-- Workflow transitions -->
<x if="zobj.showTransitions('result')"
var2="targetObj=zobj;
buttonsMode='small'">:targetObj.appy().pxTransitions</x>
<!-- Fields (actions) defined with layout "buttons" -->
<x if="not inPopup"
var2="fields=zobj.getAppyTypes('buttons', 'main');
layoutType='cell'">
<!-- Call pxCell and not pxRender to avoid having a table -->
<x for="field in fields"
var2="name=field.name; smallButtons=True">:field.pxCell</x>
</x>
</div>
</x>
<x if="not mayView">
<img src=":url('fake')" style="margin-right: 5px"/>
<x>:_('unauthorized')</x>
</x>
</x>
<!-- Any other field -->
<x if="(field.name != 'title') and mayView">
<x var="layoutType='cell'; innerRef=True"
if="field.isShowable(zobj, 'result')">:field.pxRender</x>
</x>''')
# Show query results as a list.
pxQueryResultList = Px('''
<x var="showHeaders=showHeaders|True;
checkboxes=uiSearch.search.checkboxes;
checkboxesId=rootHookId + '_objs';
cbShown=uiSearch.showCheckboxes();
cbDisplay=cbShown and 'display:table-cell' or 'display:none'">
<table class="list" width="100%">
<!-- Headers, with filters and sort arrows -->
<tr if="showHeaders">
<th if="checkboxes" class="cbCell" style=":cbDisplay">
<img src=":url('checkall')" class="clickable"
title=":_('check_uncheck')"
onclick=":'toggleAllCbs(%s)' % q(checkboxesId)"/>
</th>
<th for="column in columns"
var2="field=column.field;
sortable=field.isSortable(usage='search');
filterable=field.filterable"
width=":column.width" align=":column.align">
<x>::ztool.truncateText(_(field.labelId))</x>
<x if="(totalNumber &gt; 1) or filterValue">:tool.pxSortAndFilter</x>
<x>:tool.pxShowDetails</x>
</th>
</tr>
<!-- Results -->
<tr if="not zobjects">
<td colspan=":len(columns)+1">:_('query_no_result')</td>
</tr>
<tr for="zobj in zobjects" valign="top"
var2="@currentNumber=currentNumber + 1;
obj=zobj.appy(); mayView=zobj.mayView();
cbId='%s_%s' % (checkboxesId, currentNumber)"
class=":loop.zobj.odd and 'even' or 'odd'">
<!-- A checkbox if required -->
<td if="checkboxes" class="cbCell" id=":cbId" style=":cbDisplay">
<input type="checkbox" name=":checkboxesId" checked="checked"
value=":zobj.id" onclick="toggleCb(this)"/>
</td>
<td for="column in columns"
var2="field=column.field" id=":'field_%s' % field.name"
width=":column.width"
align=":column.align">:tool.pxQueryField</td>
</tr>
</table>
<!-- The button for selecting objects and closing the popup. -->
<div if="inPopup and cbShown" align=":dleft">
<input type="button"
var="label=_('object_link_many'); css=ztool.getButtonCss(label)"
value=":label" class=":css" style=":url('linkMany', bg=True)"
onclick=":'onSelectObjects(%s,%s,%s,%s,%s,%s,%s)' % \
(q(rootHookId), q(uiSearch.initiator.url), \
q(uiSearch.initiatorMode), q(sortKey), q(sortOrder), \
q(filterKey), q(filterValue))"/>
</div>
<!-- Init checkboxes if present. -->
<script if="checkboxes">:'initCbs(%s)' % q(checkboxesId)</script>
<script>:'initFocus(%s)' % q(ajaxHookId)</script></x>''')
# Show query results as a grid.
pxQueryResultGrid = Px('''
<table width="100%"
var="modeElems=resultMode.split('_');
cols=(len(modeElems)==2) and int(modeElems[1]) or 4;
rows=ztool.splitList(zobjects, cols)">
<tr for="row in rows" valign="middle">
<td for="zobj in row" width=":'%d%%' % (100/cols)" align="center"
style="padding-top: 25px"
var2="obj=zobj.appy(); mayView=zobj.mayView()">
<x var="currentNumber=currentNumber + 1"
for="column in columns"
var2="field=column.field">:tool.pxQueryField</x>
</td>
</tr>
</table>''')
# Show paginated query results as a list or grid.
pxQueryResult = Px('''
<div var="ajaxHookId='queryResult';
_=ztool.translate;
className=req['className'];
searchName=req.get('search', '');
uiSearch=uiSearch|ztool.getSearch(className,searchName,ui=True);
rootHookId=uiSearch.getRootHookId();
refInfo=ztool.getRefInfo();
refObject=refInfo[0];
refField=refInfo[1];
refUrlPart=refObject and ('&amp;ref=%s:%s' % (refObject.id, \
refField)) or '';
startNumber=req.get('startNumber', '0');
startNumber=int(startNumber);
sortKey=req.get('sortKey', '');
sortOrder=req.get('sortOrder', 'asc');
filterKey=req.get('filterKey', '');
filterValue=req.get('filterValue', '');
queryResult=ztool.executeQuery(className, \
search=uiSearch.search, startNumber=startNumber, \
remember=True, sortBy=sortKey, sortOrder=sortOrder, \
filterKey=filterKey, filterValue=filterValue, \
refObject=refObject, refField=refField);
zobjects=queryResult.objects;
totalNumber=queryResult.totalNumber;
batchSize=queryResult.batchSize;
batchNumber=len(zobjects);
navBaseCall='askQueryResult(%s,%s,%s,%s,%s,**v**)' % \
(q(ajaxHookId), q(ztool.absolute_url()), q(className), \
q(searchName),int(inPopup));
showNewSearch=showNewSearch|True;
newSearchUrl='%s/search?className=%s%s' % \
(ztool.absolute_url(), className, refUrlPart);
showSubTitles=req.get('showSubTitles', 'true') == 'true';
resultMode=ztool.getResultMode(className);
target=ztool.getLinksTargetInfo(ztool.getAppyClass(className))"
id=":ajaxHookId">
<x if="zobjects or filterValue">
<!-- Display here POD templates if required. -->
<table var="fields=ztool.getResultPodFields(className);
layoutType='view'"
if="not inPopup and zobjects and fields" align=":dright">
<tr>
<td var="zobj=zobjects[0]; obj=zobj.appy()"
for="field in fields"
class=":not loop.field.last and 'pod' or ''">:field.pxRender</td>
</tr>
</table>
<!-- The title of the search -->
<p if="not inPopup">
<x>::uiSearch.translated</x> (<span class="discreet">:totalNumber</span>)
<x if="showNewSearch and (searchName == 'customSearch')">&nbsp;&mdash;
&nbsp;<i><a href=":newSearchUrl">:_('search_new')</a></i>
</x>
</p>
<table width="100%">
<tr valign="top">
<!-- Search description -->
<td if="uiSearch.translatedDescr">
<span class="discreet">:uiSearch.translatedDescr</span><br/>
</td>
<!-- (Top) navigation -->
<td align=":dright" width="150px">:tool.pxNavigate</td>
</tr>
</table>
<!-- Results, as a list or grid -->
<x var="columnLayouts=ztool.getResultColumnsLayouts(className, refInfo);
columns=ztool.getColumnsSpecifiers(className,columnLayouts, dir);
currentNumber=0">
<x if="resultMode == 'list'">:tool.pxQueryResultList</x>
<x if="resultMode != 'list'">:tool.pxQueryResultGrid</x>
</x>
<!-- (Bottom) navigation -->
<x>:tool.pxNavigate</x>
</x>
<x if="not zobjects and not filterValue">
<x>:_('query_no_result')</x>
<x if="showNewSearch and (searchName == 'customSearch')"><br/>
<i class="discreet"><a href=":newSearchUrl">:_('search_new')</a></i></x>
</x>
</div>''')
pxQuery = Px(''' pxQuery = Px('''
<div var="className=req['className']; <div var="className=req['className'];
searchName=req.get('search', ''); searchName=req.get('search', '');
@ -575,9 +349,8 @@ class ToolWrapper(AbstractWrapper):
rootHookId=uiSearch.getRootHookId(); rootHookId=uiSearch.getRootHookId();
cssJs=None" cssJs=None"
id=":rootHookId"> id=":rootHookId">
<script type="text/javascript">:uiSearch.search.getCbJsInit(rootHookId) <script>:uiSearch.getCbJsInit(rootHookId)</script>
</script> <x>:tool.pxPagePrologue</x><x>:uiSearch.pxResult</x>
<x>:tool.pxPagePrologue</x><x>:tool.pxQueryResult</x>
</div>''', template=AbstractWrapper.pxTemplate, hook='content') </div>''', template=AbstractWrapper.pxTemplate, hook='content')
pxSearch = Px(''' pxSearch = Px('''
@ -591,8 +364,7 @@ class ToolWrapper(AbstractWrapper):
<!-- Include type-specific CSS and JS --> <!-- Include type-specific CSS and JS -->
<link for="cssFile in cssJs['css']" rel="stylesheet" type="text/css" <link for="cssFile in cssJs['css']" rel="stylesheet" type="text/css"
href=":url(cssFile)"/> href=":url(cssFile)"/>
<script for="jsFile in cssJs['js']" type="text/javascript" <script for="jsFile in cssJs['js']" src=":url(jsFile)"></script>
src=":url(jsFile)"></script>
<!-- Search title --> <!-- Search title -->
<h1><x>:_('%s_plural'%className)</x> &ndash; <h1><x>:_('%s_plural'%className)</x> &ndash;
@ -625,13 +397,8 @@ class ToolWrapper(AbstractWrapper):
pxBack = Px(''' pxBack = Px('''
<html> <html>
<head> <head><script src=":ztool.getIncludeUrl('appy.js')"></script></head>
<script src=":ztool.getIncludeUrl('appy.js')" type="text/javascript"> <body><script>backFromPopup()</script></body>
</script>
</head>
<body>
<script type="text/javascript">backFromPopup()</script>
</body>
</html>''') </html>''')
def isManager(self): def isManager(self):

View file

@ -66,7 +66,7 @@ class AbstractWrapper(object):
req=ztool.REQUEST; resp=req.RESPONSE; req=ztool.REQUEST; resp=req.RESPONSE;
inPopup=req.get('popup') == '1'; inPopup=req.get('popup') == '1';
obj=obj or ztool.getHomeObject(inPopup); obj=obj or ztool.getHomeObject(inPopup);
zobj=obj and obj.o or None; zobj=obj and obj.o or None; ajax=False;
isAnon=user.login=='anon'; app=ztool.getApp(); isAnon=user.login=='anon'; app=ztool.getApp();
appFolder=app.data; url = ztool.getIncludeUrl; appFolder=app.data; url = ztool.getIncludeUrl;
appName=ztool.getAppName(); _=ztool.translate; appName=ztool.getAppName(); _=ztool.translate;
@ -88,8 +88,7 @@ class AbstractWrapper(object):
<x for="name in ztool.getGlobalCssJs(dir)"> <x for="name in ztool.getGlobalCssJs(dir)">
<link if="name.endswith('.css')" rel="stylesheet" type="text/css" <link if="name.endswith('.css')" rel="stylesheet" type="text/css"
href=":url(name)"/> href=":url(name)"/>
<script if="name.endswith('.js')" type="text/javascript" <script if="name.endswith('.js')" src=":url(name)"></script>
src=":url(name)"></script>
</x> </x>
</head> </head>
<body style=":(cfg.skin == 'wide') and 'margin:0' or ''"> <body style=":(cfg.skin == 'wide') and 'margin:0' or ''">
@ -376,9 +375,8 @@ class AbstractWrapper(object):
pxTransitions = Px(''' pxTransitions = Px('''
<form var="transitions=targetObj.getTransitions()" if="transitions" <form var="transitions=targetObj.getTransitions()" if="transitions"
var2="formId='trigger_%s' % targetObj.id" method="post" var2="formId='trigger_%s' % targetObj.id" method="post"
id=":formId" action=":targetObj.absolute_url() + '/do'" id=":formId" action=":targetObj.absolute_url() + '/onTrigger'"
style="display: inline"> style="display: inline">
<input type="hidden" name="action" value="Trigger"/>
<input type="hidden" name="transition"/> <input type="hidden" name="transition"/>
<!-- Input field for storing the comment coming from the popup --> <!-- Input field for storing the comment coming from the popup -->
<textarea id="comment" name="comment" cols="30" rows="3" <textarea id="comment" name="comment" cols="30" rows="3"
@ -388,8 +386,7 @@ class AbstractWrapper(object):
<x if="transition.type == 'transition'">:transition.pxView</x> <x if="transition.type == 'transition'">:transition.pxView</x>
<x if="transition.type == 'group'" <x if="transition.type == 'group'"
var2="uiGroup=transition">:uiGroup.px</x> var2="uiGroup=transition">:uiGroup.px</x>
</x> </x></form>''')
</form>''')
# Displays header information about an object: title, workflow-related info, # Displays header information about an object: title, workflow-related info,
# history... # history...
@ -534,7 +531,7 @@ class AbstractWrapper(object):
inPopup=inPopup))"/> inPopup=inPopup))"/>
</x> </x>
<!-- Workflow transitions --> <!-- Workflow transitions -->
<x var="targetObj=zobj; buttonsMode='normal'" <x var="targetObj=zobj"
if="mayAct and \ if="mayAct and \
targetObj.showTransitions(layoutType)">:obj.pxTransitions</x> targetObj.showTransitions(layoutType)">:obj.pxTransitions</x>
<!-- Fields (actions) defined with layout "buttons" --> <!-- Fields (actions) defined with layout "buttons" -->
@ -578,6 +575,39 @@ class AbstractWrapper(object):
</table> </table>
</form>''') </form>''')
# The object, as shown in a list of query results
pxViewAsResult = Px('''
<tr var2="obj=zobj.appy(); mayView=zobj.mayView();
cbId='%s_%s' % (checkboxesId, currentNumber)"
id=":zobj.id" class=":rowCss" valign="top">
<!-- A checkbox if required -->
<td if="checkboxes" class="cbCell" id=":cbId"
style=":'display:%s' % cbDisplay">
<input type="checkbox" name=":checkboxesId" checked="checked"
value=":zobj.id" onclick="toggleCb(this)"/>
</td>
<td for="column in columns"
var2="field=column.field" id=":'field_%s' % field.name"
width=":column.width"
align=":column.align">:field.pxRenderAsResult</td>
<!-- Store data in this tr node allowing to ajax-refresh it -->
<script>:uiSearch.getAjaxDataRow(zobj, ajaxHookId, \
currentNumber=currentNumber, rowCss=rowCss)</script>
</tr>''')
# When calling pxViewAsResult from Ajax, this surrounding PX is called to
# define the appropriate variables based on request values.
pxViewAsResultFromAjax = Px('''
<x var="ajaxHookId='queryResult';
dummy=ztool.updatePxContextFromRequest();
showSubTitles=showSubTitles|True;
refInfo=ztool.getRefInfo();
columnLayouts=ztool.getResultColumnsLayouts(className, refInfo);
columns=ztool.getColumnsSpecifiers(className, columnLayouts, dir);
target=ztool.getLinksTargetInfo(ztool.getAppyClass(className));
uiSearch=ztool.getSearch(\
className, searchName, ui=True)">:obj.pxViewAsResult</x>''')
pxView = Px(''' pxView = Px('''
<x var="x=zobj.mayView(raiseError=True); <x var="x=zobj.mayView(raiseError=True);
errors=req.get('errors', {}); errors=req.get('errors', {});
@ -639,11 +669,12 @@ class AbstractWrapper(object):
req=ztool.REQUEST; resp=req.RESPONSE; req=ztool.REQUEST; resp=req.RESPONSE;
dummy=setattr(req, 'pxContext', _ctx_); dummy=setattr(req, 'pxContext', _ctx_);
lang=ztool.getUserLanguage(); q=ztool.quote; lang=ztool.getUserLanguage(); q=ztool.quote;
action=req.get('action', ''); action=req.get('action', ''); ajax=True;
px=req['px'].split(':'); px=req['px'].split(':');
inPopup=req.get('popup') == '1'; inPopup=req.get('popup') == '1';
className=(len(px) == 3) and px[0] or None; className=(len(px) == 3) and px[0] or None;
field=className and zobj.getAppyType(px[1], className) or None; field=className and (zobj.getAppyType(px[1], className) or \
ztool.getSearch(className, px[1], ui=True));
field=(len(px) == 2) and zobj.getAppyType(px[0]) or field; field=(len(px) == 2) and zobj.getAppyType(px[0]) or field;
dir=ztool.getLanguageDirection(lang); dir=ztool.getLanguageDirection(lang);
dleft=(dir == 'ltr') and 'left' or 'right'; dleft=(dir == 'ltr') and 'left' or 'right';

View file

@ -95,11 +95,11 @@ class Px:
as is, without re-applying the template (else, an infinite as is, without re-applying the template (else, an infinite
recursion would occur). recursion would occur).
''' '''
# Developer, forget the following line forever. # Developer, forget the following line forever
if '_ctx_' not in context: context['_ctx_'] = context if '_ctx_' not in context: context['_ctx_'] = context
if self.hook and applyTemplate: if self.hook and applyTemplate:
# Call the template PX, filling the hook with the current PX. # Call the template PX, filling the hook with the current PX
context[self.hook] = self context[self.hook] = self
return self.template(context) return self.template(context)
else: else:

View file

@ -294,6 +294,8 @@ def getStringDict(d):
res = [] res = []
for k, v in d.iteritems(): for k, v in d.iteritems():
if type(v) not in sequenceTypes: if type(v) not in sequenceTypes:
if not isinstance(k, basestring): k = str(k)
if not isinstance(v, basestring): v = str(v)
value = "'%s':'%s'" % (k, v.replace("'", "\\'")) value = "'%s':'%s'" % (k, v.replace("'", "\\'"))
else: else:
value = "'%s':%s" % (k, v) value = "'%s':%s" % (k, v)