[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:
parent
4d78996938
commit
14f85509e1
|
@ -40,6 +40,7 @@ class Field:
|
|||
cssFiles = {}
|
||||
jsFiles = {}
|
||||
dLayouts = 'lrv-d-f'
|
||||
hLayouts = 'lhrv-f'
|
||||
wLayouts = Table('lrv-f')
|
||||
|
||||
# Render a field. Optional vars:
|
||||
|
|
|
@ -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) > 1"
|
||||
class="podName">:field.getTemplateName(obj, info.template)</td>
|
||||
class="smaller">:field.getTemplateName(obj, info.template)</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
|
|
|
@ -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)<=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 > 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,
|
||||
|
|
|
@ -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(); " \
|
||||
|
|
|
@ -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.'''
|
||||
|
|
|
@ -79,7 +79,7 @@ msgstr ""
|
|||
msgid "max_ref_violated"
|
||||
msgstr ""
|
||||
|
||||
#. Default: "No object."
|
||||
#. Default: "-"
|
||||
msgid "no_ref"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -79,7 +79,7 @@ msgstr ""
|
|||
msgid "max_ref_violated"
|
||||
msgstr ""
|
||||
|
||||
#. Default: "No object."
|
||||
#. Default: "-"
|
||||
msgid "no_ref"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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."
|
||||
|
||||
|
|
|
@ -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."
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue