[gen] Added ref.render == 'titles' = a way to render linked objects as a simple list of comma-separated, non clickable titles; bugfix in inline-edit of string XHTML fields.

This commit is contained in:
Gaetan Delannay 2014-04-30 21:08:42 +02:00
parent 4d78996938
commit 14f85509e1
15 changed files with 106 additions and 64 deletions

View file

@ -40,6 +40,7 @@ class Field:
cssFiles = {}
jsFiles = {}
dLayouts = 'lrv-d-f'
hLayouts = 'lhrv-f'
wLayouts = Table('lrv-f')
# Render a field. Optional vars:

View file

@ -78,7 +78,7 @@ class Pod(Field):
<td>
<a onclick=":'freezePod(%s,%s,%s,%s,%s)' % (q(uid), q(name), \
q(info.template), q(fmt), q('unfreeze'))"
class="podName">:_('unfreezeField')</a>
class="smaller">:_('unfreezeField')</a>
</td>
<td align="center"><img src=":url('unfreeze')"/></td>
</tr>
@ -87,7 +87,7 @@ class Pod(Field):
<td>
<a onclick=":'freezePod(%s,%s,%s,%s,%s)' % (q(uid), q(name), \
q(info.template), q(fmt), q('freeze'))"
class="podName">:_('freezeField')</a>
class="smaller">:_('freezeField')</a>
</td>
<td align="center"><img src=":url('freeze')"/></td>
</tr>
@ -96,7 +96,7 @@ class Pod(Field):
<td>
<a onclick=":'uploadPod(%s,%s,%s,%s)' % (q(uid), q(name), \
q(info.template), q(fmt))"
class="podName">:_('uploadField')</a>
class="smaller">:_('uploadField')</a>
</td>
<td align="center"><img src=":url('upload')"/></td>
</tr>
@ -107,7 +107,7 @@ class Pod(Field):
template. For a single template, the field label already does
the job. -->
<td if="len(field.template) &gt; 1"
class="podName">:field.getTemplateName(obj, info.template)</td>
class="smaller">:field.getTemplateName(obj, info.template)</td>
</tr>
</table>
</td>

View file

@ -195,24 +195,10 @@ class Ref(Field):
# PX that displays referred objects as a list.
pxViewList = Px('''
<!-- Display a simplified widget if at most 1 referenced object. -->
<table if="atMostOneRef">
<tr valign="top">
<!-- If there is no object -->
<x if="not objects">
<td class="discreet">:_('no_ref')</td>
<td>:field.pxAdd</td>
</x>
<!-- If there is an object -->
<x if="objects">
<td for="tied in objects"
var2="includeShownInfo=True">:field.pxObjectTitle</td>
</x>
</tr>
</table>
<!-- No object at all -->
<div if="not objects" class="smaller">:_('no_ref')</div>
<!-- Display a table in all other cases -->
<x if="not atMostOneRef">
<x if="objects">
<div if="not innerRef or showPlusIcon" style="margin-bottom: 4px">
<span if="subLabel" class="discreet">:_(subLabel)</span>
(<span class="discreet">:totalNumber</span>)
@ -231,10 +217,7 @@ class Ref(Field):
<!-- (Top) navigation -->
<x>:tool.pxNavigate</x>
<!-- No object is present -->
<p class="discreet" if="not objects">:_('no_ref')</p>
<!-- Show forward or backward reference(s) -->
<!-- Linked objects -->
<table if="objects" class=":not innerRef and 'list' or ''"
width=":innerRef and '100%' or field.layouts['view'].width"
var2="columns=ztool.getColumnsSpecifiers(tiedClassName, \
@ -312,7 +295,6 @@ class Ref(Field):
not field.isBack and zobj.allows(field.writePermission);
mayUnlink=False;
showPlusIcon=False;
atMostOneRef=False;
navBaseCall='askRefField(%s,%s,%s,%s,**v**)' % \
(q(ajaxHookId), q(zobj.absolute_url()), \
q(field.name), q(innerRef));
@ -356,14 +338,19 @@ class Ref(Field):
</td>
</tr></table>''')
# Simplified widget showing comma-separated not-clickable object titles.
pxViewTitles = Px('''<span class="smaller"
var2="titles=[o.title for o in objects]">:', '.join(titles) or \
_('no_ref')</span>''')
# PX that displays referred objects through this field. In mode link="list",
# if, in the request, key "scope" is present and holds value "objs", the
# pick list (containing possible values) will not be rendered.
pxView = Px('''
<x var="innerRef=req.get('innerRef', False) == 'True';
ajaxHookId='%s_%s_objs' % (zobj.id, field.name);
render=render|'list';
layoutType=layoutType|'view';
render=field.getRenderMode(layoutType);
linkList=field.link == 'list';
renderAll=req.get('scope') != 'objs';
inPickList=False;
@ -381,7 +368,6 @@ class Ref(Field):
mayUnlink=not isBack and canWrite and \
field.getAttribute(zobj, 'unlink');
showPlusIcon=field.mayAdd(zobj);
atMostOneRef=(field.multiplicity[1]==1) and (len(objects)&lt;=1);
addConfirmMsg=field.addConfirm and \
_('%s_addConfirm' % field.labelId) or '';
navBaseCall='askRefField(%s,%s,%s,%s,**v**)' % \
@ -395,10 +381,6 @@ class Ref(Field):
(layoutType != 'cell');
checkboxes=checkboxesEnabled and (totalNumber &gt; 1);
showSubTitles=req.get('showSubTitles', 'true') == 'true'">
<!-- The definition of "atMostOneRef" above may sound strange: we
shouldn't check the actual number of referenced objects. But for
back references people often forget to specify multiplicities. So
concretely, multiplicities (0,None) are coded as (0,1). -->
<!-- JS tables storing checkbox statuses if checkboxes are enabled -->
<script if="checkboxesEnabled and renderAll"
type="text/javascript">:field.getCbJsInit(zobj)</script>
@ -410,12 +392,11 @@ class Ref(Field):
<div if="renderAll" id=":ajaxHookId">:field.pxViewList</div>
<x if="not renderAll">:field.pxViewList</x>
</x>
<x if="render == 'menus'">:field.pxViewMenus</x>
<x if="render in ('menus','titles')">:getattr(field, 'pxView%s' % \
render.capitalize())</x>
</x>''')
# The "menus" render mode is only applicable in "cell", not in "view".
pxCell = Px('''<x var="render=field.render">:field.pxView</x>''')
pxCell = pxView
pxEdit = Px('''
<select if="field.link"
var2="objects=field.getPossibleValues(zobj);
@ -579,7 +560,9 @@ class Ref(Field):
# Note that render mode "menus" will only be applied in "cell" layouts.
# Indeed, we need to keep the "list" rendering in the "view" layout
# because the "menus" rendering is minimalist and does not allow to
# perform all operations on Ref objects (add, move, delete, edit...).
# perform all operations on linked objects (add, move, delete, edit...);
# - "titles" renders a list of comma-separated, not-even-clickable,
# titles.
self.render = render
# If render is 'menus', 2 methods must be provided.
# "menuIdMethod" will be called, with every linked object as single arg,
@ -1054,6 +1037,12 @@ class Ref(Field):
reverse = rq.get('reverse') == 'True'
obj.appy().sort(self.name, sortKey=sortKey, reverse=reverse)
def getRenderMode(self, layoutType):
'''Gets the render mode, determined by self.render and some
exceptions.'''
if (layoutType == 'view') and (self.render == 'menus'): return 'list'
return self.render
def onUiRequest(self, obj, rq):
'''This method is called when an action tied to this Ref field is
triggered from the user interface (link, unlink, link_many,

View file

@ -97,12 +97,13 @@ class String(Field):
<!-- Unformatted text -->
<x if="value and (fmt == 1)">::zobj.formatText(value, format='html')</x>
<!-- XHTML text -->
<x if="value and (fmt == 2)">
<div if="not mayAjaxEdit" class="xhtml">::value</div>
<x if="fmt == 2">
<div if="not mayAjaxEdit" class="xhtml">::value or '-'</div>
<div if="mayAjaxEdit" class="xhtml" contenteditable="true"
id=":'%s_%s_ck' % (zobj.UID(), name)">::value</div>
<script if="mayAjaxEdit">::field.getJsInlineInit(zobj)"></script>
id=":'%s_%s_ck' % (zobj.id, name)">::value or '-'</div>
<script if="mayAjaxEdit">::field.getJsInlineInit(zobj)</script>
</x>
<span if="not value and (fmt != 2)" class="smaller">-</span>
<input type="hidden" if="masterCss" class=":masterCss" value=":rawValue"
name=":name" id=":name"/>
</x>''')
@ -298,7 +299,7 @@ class String(Field):
label=None, sdefault='', scolspan=1, swidth=None, sheight=None,
persist=True, transform='none',
styles=('p','h1','h2','h3','h4'), allowImageUpload=True,
inlineEdit=False):
spellcheck=False, contentLanguage=None, inlineEdit=False):
# According to format, the widget will be different: input field,
# textarea, inline editor... Note that there can be only one String
# field of format CAPTCHA by page, because the captcha challenge is
@ -310,6 +311,10 @@ class String(Field):
self.styles = styles
# When format is XHTML, do we allow the user to upload images in it ?
self.allowImageUpload = allowImageUpload
# When format is XHTML, do we run the CK spellchecker ?
self.spellcheck = spellcheck
# What is the language of field content?
self.contentLanguage = contentLanguage
# When format in XHTML, can the field be inline-edited (ckeditor)?
self.inlineEdit = inlineEdit
# The following field has a direct impact on the text entered by the
@ -409,6 +414,12 @@ class String(Field):
type='warning')
Field.store(self, obj, value)
def storeFromAjax(self, obj):
'''Stores the new field value from an Ajax request, or do nothing if
the action was canceled.'''
rq = obj.REQUEST
if rq.get('cancel') != 'True': self.store(obj, rq['fieldContent'])
def getDiffValue(self, obj, value):
'''Returns a version of p_value that includes the cumulative diffs
between successive versions.'''
@ -656,27 +667,52 @@ class String(Field):
generator).'''
return self.getCaptchaChallenge({})['text']
def getJsInit(self, obj):
'''Gets the Javascript init code for displaying a rich editor for this
field (rich field only).'''
# Define the attributes that will initialize the ckeditor instance for
# this field.
ckLanguages = {'en': 'en_US', 'pt': 'pt_BR', 'da': 'da_DK', 'nl': 'nl_NL',
'fi': 'fi_FI', 'fr': 'fr_FR', 'de': 'de_DE', 'el': 'el_GR',
'it': 'it_IT', 'nb': 'nb_NO', 'pt': 'pt_PT', 'es': 'es_ES',
'sv': 'sv_SE'}
def getCkLanguage(self):
'''Gets the language for CK editor SCAYT. We will use
self.contentLanguage. If it is not supported by CK, we use
english.'''
lang = self.contentLanguage
if lang and (lang in self.ckLanguages): return self.ckLanguages[lang]
return 'en_US'
def getCkParams(self, obj):
'''Gets the base params to set on a rich text field.'''
ckAttrs = {'toolbar': 'Appy',
'format_tags': '%s' % ';'.join(self.styles)}
'format_tags': ';'.join(self.styles),
'scayt_sLang': self.getCkLanguage()}
if self.width: ckAttrs['width'] = self.width
if self.spellcheck: ckAttrs['scayt_autoStartup'] = True
if self.allowImageUpload:
ckAttrs['filebrowserUploadUrl'] = '%s/upload' % obj.absolute_url()
ck = []
for k, v in ckAttrs.iteritems():
if isinstance(v, int): sv = str(v)
if isinstance(v, bool): sv = str(v).lower()
else: sv = '"%s"' % v
ck.append('%s: %s' % (k, sv))
return 'CKEDITOR.replace("%s", {%s})' % (self.name, ', '.join(ck))
return ', '.join(ck)
def getJsInit(self, obj):
'''Gets the Javascript init code for displaying a rich editor for this
field (rich field only).'''
return 'CKEDITOR.replace("%s", {%s})' % \
(self.name, self.getCkParams(obj))
def getJsInlineInit(self, obj):
'''Gets the Javascript init code for enabling inline edition of this
field (rich text only).'''
uid = obj.UID()
uid = obj.id
return "CKEDITOR.disableAutoInline = true;\n" \
"CKEDITOR.inline('%s_%s_ck', {%s, on: {blur: " \
"function( event ) { var content = event.editor.getData(); " \
"doInlineSave('%s', '%s', '%s', content)}}})" % \
(uid, self.name, self.getCkParams(obj), uid, self.name,
obj.absolute_url())
return "CKEDITOR.disableAutoInline = true;\n" \
"CKEDITOR.inline('%s_%s_ck', {on: {blur: " \
"function( event ) { var data = event.editor.getData(); " \

View file

@ -12,7 +12,7 @@ class Protos:
# List of attributes that can't be given to a Type constructor
notInit = ('id', 'type', 'pythonType', 'slaves', 'isSelect', 'hasLabel',
'hasDescr', 'hasHelp', 'required', 'filterable', 'validable',
'isBack', 'pageName', 'masterName')
'isBack', 'pageName', 'masterName', 'renderLabel')
@classmethod
def get(self, appyType):
'''Returns a prototype instance for p_appyType.'''

View file

@ -79,7 +79,7 @@ msgstr ""
msgid "max_ref_violated"
msgstr ""
#. Default: "No object."
#. Default: "-"
msgid "no_ref"
msgstr ""

View file

@ -79,7 +79,7 @@ msgstr ""
msgid "max_ref_violated"
msgstr ""
#. Default: "No object."
#. Default: "-"
msgid "no_ref"
msgstr ""

View file

@ -79,7 +79,7 @@ msgstr "Hier müssen Sie Elemente auswählen."
msgid "max_ref_violated"
msgstr "Sie haben zuviele Elemente ausgewählt."
#. Default: "No object."
#. Default: "-"
msgid "no_ref"
msgstr "Kein Element"

View file

@ -80,7 +80,7 @@ msgstr "You must choose more elements here."
msgid "max_ref_violated"
msgstr "Too much elements are selected here."
#. Default: "No object."
#. Default: "-"
msgid "no_ref"
msgstr "No object."

View file

@ -79,7 +79,7 @@ msgstr "Debe elegir más elementos aquí."
msgid "max_ref_violated"
msgstr "Demasiados elementos son seleccionados aquí."
#. Default: "No object."
#. Default: "-"
msgid "no_ref"
msgstr "Ningún elemento."

View file

@ -80,9 +80,9 @@ msgstr "Vous devez choisir plus d'éléments ici."
msgid "max_ref_violated"
msgstr "Trop d'éléments sont sélectionnés ici."
#. Default: "No object."
#. Default: "-"
msgid "no_ref"
msgstr "Aucun élément."
msgstr "-"
#. Default: "Add a new one"
msgid "add_ref"

View file

@ -79,9 +79,9 @@ msgstr "Qui deve scegliere un maggior numero di elementi"
msgid "max_ref_violated"
msgstr "Un numero eccessivo di elementi sono scelti"
#. Default: "No object."
#. Default: "-"
msgid "no_ref"
msgstr "Nessun elemento"
msgstr "-"
#. Default: "Add a new one"
msgid "add_ref"

View file

@ -79,9 +79,9 @@ msgstr "U moet hier meerdere elementen selecteren."
msgid "max_ref_violated"
msgstr "U hebt teveel elementen geselecteerd."
#. Default: "No object."
#. Default: "-"
msgid "no_ref"
msgstr "Geen element."
msgstr "-"
#. Default: "Add a new one"
msgid "add_ref"

View file

@ -100,7 +100,7 @@ td.search { padding-top: 8px }
border: 1px solid grey; box-shadow: 2px 2px 2px #888888}
.dropdown { display:none; position: absolute; border: 1px solid #cccccc;
background-color: white; padding: 3px 4px 0; font-size: 8pt;
font-weight: normal }
font-weight: normal; z-index: 2 }
.dropdownMenu { cursor: pointer; padding-right: 4px; font-size: 93% }
.dropdown a:hover { text-decoration: underline }
.list { margin-bottom: 3px }
@ -155,7 +155,7 @@ td.search { padding-top: 8px }
.homeTable th { padding-top: 5px; font-size: 105% }
.first { margin-top: 0px }
.error { margin: 5px }
.podName { font-size: 95% }
.smaller { font-size: 95% }
.podTable { margin-left: 15px }
.cbCell { width: 10px; text-align: center}
.tabs { position:relative; bottom:-2px }

View file

@ -275,6 +275,22 @@ function askField(hookId, objectUrl, layoutType, showChanges, masterValues,
askAjaxChunk(hookId, 'GET', objectUrl, px, params, null, evalInnerScripts);
}
function doInlineSave(objectUid, name, objectUrl, content){
/* Ajax-saves p_content of field named p_name on object whose id is
p_objectUid and whose URL is p_objectUrl. Asks a confirmation before
doing it. */
var doIt = confirm('Do it?');
var params = {'action': 'storeFromAjax', 'layoutType': 'view'};
var hook = null;
if (!doIt) {
params['cancel'] = 'True';
hook = objectUid + '_' + name;
}
else { params['fieldContent'] = encodeURIComponent(content) }
askAjaxChunk(hook, 'POST', objectUrl, name + ':pxRender', params, null,
evalInnerScripts);
}
// Used by checkbox widgets for having radio-button-like behaviour.
function toggleCheckbox(visibleCheckbox, hiddenBoolean) {
vis = document.getElementById(visibleCheckbox);