appy.gen: reimplemented master/slave-related Javascript code without Plone queryCss.

This commit is contained in:
Gaetan Delannay 2011-10-01 22:40:13 +02:00
parent c9353b46db
commit e821307b4c
14 changed files with 172 additions and 158 deletions

View file

@ -22,6 +22,13 @@ validatorTypes = (types.FunctionType, types.UnboundMethodType,
emptyTuple = ()
labelTypes = ('label', 'descr', 'help')
def initMasterValue(v):
'''Standardizes p_v as a list.'''
if not v: res = []
elif type(v) not in sequenceTypes: res = [v]
else: res = v
return res
# Descriptor classes used for refining descriptions of elements in types
# (pages, groups,...) ----------------------------------------------------------
class Page:
@ -153,26 +160,10 @@ class Group:
# Header labels will be used as labels for the tabs.
self.hasHeaders = True
self.css_class = css_class
self.master = None
self.masterValue = None
if master:
self._addMaster(master, masterValue)
self.label = label # See similar attr of Type class.
def _addMaster(self, master, masterValue):
'''Specifies this group being a slave of another field: we will add css
classes allowing to show/hide, in Javascript, its widget according
to master value.'''
self.master = master
self.masterValue = masterValue
classes = 'slave_%s' % self.master.id
if type(self.masterValue) not in sequenceTypes:
masterValues = [self.masterValue]
else:
masterValues = self.masterValue
for masterValue in masterValues:
classes += ' slaveValue_%s_%s' % (self.master.id, masterValue)
self.css_class += ' ' + classes
self.masterValue = initMasterValue(masterValue)
if master: master.slaves.append(self)
self.label = label # See similar attr of Type class.
def _setColumns(self):
'''Standardizes field "columns" as a list of Column instances. Indeed,
@ -440,17 +431,14 @@ class Type:
# If the widget is in a group with multiple columns, the following
# attribute specifies on how many columns to span the widget.
self.colspan = colspan
# The list of slaves of this field, if it is a master
self.slaves = []
# The behaviour of this field may depend on another, "master" field
self.master = master
self.slaves = [] # The list of slaves of this field, if it is a master
# Every HTML input field corresponding to a master must get some
# CSS classes for controlling its slaves.
self.master_css = ''
if master:
self.master.slaves.append(self)
self.master.master_css = 'appyMaster master_%s' % self.master.id
# When master has some value(s), there is impact on this field.
self.masterValue = masterValue
self.masterValue = initMasterValue(masterValue)
# If a field must retain attention in a particular way, set focus=True.
# It will be rendered in a special way.
self.focus = focus
@ -494,7 +482,8 @@ class Type:
# Recompute the ID (and derived attributes) that may have changed if
# we are in debug mode (because we recreate new Type instances).
self.id = id(self)
if self.slaves: self.master_css = 'appyMaster master_%s' % self.id
# Remember master name on every slave
for slave in self.slaves: slave.masterName = name
# Determine ids of i18n labels for this field
labelName = name
trPrefix = None
@ -591,13 +580,10 @@ class Type:
master, masterValue = masterData
reqValue = master.getRequestValue(obj.REQUEST)
reqValue = master.getStorableValue(reqValue)
# Manage the fact that values can be lists or single values
multiMaster = type(masterValue) in sequenceTypes
multiReq = type(reqValue) in sequenceTypes
if not multiMaster and not multiReq: return reqValue == masterValue
elif multiMaster and not multiReq: return reqValue in masterValue
elif not multiMaster and multiReq: return masterValue in reqValue
else: # multiMaster and multiReq
# reqValue can be a list or not
if type(reqValue) not in sequenceTypes:
return reqValue in masterValue
else:
for m in masterValue:
for r in reqValue:
if m == r: return True
@ -674,19 +660,6 @@ class Type:
layouts['cell'] = Table(other=layouts['view'], derivedType='cell')
# Put the required CSS classes in the layouts
layouts['cell'].addCssClasses('noStyle')
if self.master:
# This type has a master (so is a slave): we add css classes
# allowing to show/hide, in Javascript, its widget according to
# master value.
classes = 'slave_%s' % self.master.id
if type(self.masterValue) not in sequenceTypes:
masterValues = [self.masterValue]
else:
masterValues = self.masterValue
for masterValue in masterValues:
classes += ' slaveValue_%s_%s' % (self.master.id, masterValue)
layouts['view'].addCssClasses(classes)
layouts['edit'].addCssClasses(classes)
if self.focus:
# We need to make it flashy
layouts['view'].addCssClasses('appyFocus')

View file

@ -1,5 +1,5 @@
# ------------------------------------------------------------------------------
import re, os, os.path, time, Cookie, types
import re, os, os.path, time, types
from appy.shared import mimeTypes
from appy.shared.utils import getOsTempFolder
import appy.gen
@ -84,7 +84,6 @@ class ToolMixin(BaseMixin):
'''Returns the (translated) names of fields of p_contentType.'''
res = []
for appyType in self.getAllAppyTypes(className=contentType):
if appyType.name == 'title': continue # Will be included by default.
res.append((appyType.name, self.translate(appyType.labelId)))
# Add object state
res.append(('state', self.translate('workflow_state')))
@ -611,13 +610,6 @@ class ToolMixin(BaseMixin):
res.append(dSearch)
return res
def getCookieValue(self, cookieId, default=''):
'''Server-side code for getting the value of a cookie entry.'''
cookie = Cookie.SimpleCookie(self.REQUEST['HTTP_COOKIE'])
cookieValue = cookie.get(cookieId)
if cookieValue: return cookieValue.value
return default
def getQueryUrl(self, contentType, searchName, startNumber=None):
'''This method creates the URL that allows to perform a (non-Ajax)
request for getting queried objects from a search named p_searchName

View file

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

View file

@ -8,18 +8,26 @@ table { font-size: 100%; border-spacing: 0px; border-collapse:collapse;}
form { margin: 0; padding: 0;}
p { margin: 0;}
acronym {cursor: help;}
input { border: 1px solid #cccccc; background-color: #f8f8f8;
font-family: Lucida,Helvetica,Arial,sans-serif; margin-bottom: 1px}
input[type=image] { border-width: 0px; background: none; }
input[type=button] { cursor: pointer; }
input[type=image] { border: 0; background: none; }
input[type=checkbox] { border: 0; background: none; cursor: pointer;}
input[type=button] { border: 1px solid #cccccc; background-color: #f8f8f8;
cursor: pointer;}
input[type=submit] { border: 1px solid #cccccc; background-color: #f8f8f8;
cursor: pointer; }
input[type=text] { border: 1px solid #cccccc; background-color: #f8f8f8;
font-family: Lucida,Helvetica,Arial,sans-serif;
margin-bottom: 1px}
select { border: 1px solid #cccccc; background-color: #f8f8f8;}
textarea { width: 99%; font: 100% Lucida,Helvetica,Arial,sans-serif;
border: 1px solid #a79e9e; background-color: #f8f8f8;}
label { font-weight: 600; font-style: italic; line-height: 1.4em;}
legend { padding-bottom: 2px; padding-right: 3px;}
legend { padding-bottom: 2px; padding-right: 3px; color: black;}
ul { line-height: 1.2em; margin: 0 0 0.2em 0.6em; padding: 0;
list-style: none outside none;}
li { margin: 0; background-image: url("skyn/li.gif"); padding-left: 10px;
background-repeat: no-repeat; background-position: 0 4px;}
img {border: 0;}
.main { width: 900px; background-color: white; box-shadow: 3px 3px 3px #A9A9A9;
border-style: solid; border-width: 1px; border-color: grey; }

View file

@ -177,7 +177,7 @@ function askRefField(hookId, objectUrl, fieldName, innerRef, startNumber,
action, actionParams){
// Sends an Ajax request for getting the content of a reference field.
var startKey = hookId + '_startNumber';
var params = {'fieldName': fieldName, 'innerRef': innerRef, };
var params = {'fieldName': fieldName, 'innerRef': innerRef };
params[startKey] = startNumber;
if (action) params['action'] = action;
if (actionParams) {
@ -201,70 +201,94 @@ function toggleCheckbox(visibleCheckbox, hiddenBoolean) {
}
// Functions used for master/slave relationships between widgets
function getMasterValue(widget) {
// Returns an array of selected options in a select widget
res = new Array();
if (widget.type == 'checkbox') {
var mv = widget.checked + '';
mv = mv.charAt(0).toUpperCase() + mv.substr(1);
res.push(mv);
function getSlaveInfo(slave, infoType) {
// Returns the appropriate info about slavery, depending on p_infoType.
cssClasses = slave.className.split(' ');
// Find the CSS class containing master-related info.
for (var j=0; j < cssClasses.length; j++) {
if (cssClasses[j].indexOf('slave_') == 0) {
// Extract, from this CSS class, master name or master values.
masterInfo = cssClasses[j].split('_');
if (infoType == 'masterName') return masterInfo[1];
else return masterInfo.slice(2); // Master values
}
}
}
function getMasterValues(master) {
// Returns the list of values that p_master currently has.
if (master.tagName == 'SPAN') {
res = master.attributes['value'].value;
if ((res[0] == '(') || (res[0] == '[')) {
// There are multiple values, split it
values = res.substring(1, res.length-1).split(',');
res = [];
for (var i=0; i < values.length; i++){
v = values[i].replace(' ', '');
res.push(v.substring(1, v.length-1));
}
}
else res = [res]; // A single value
}
else if (master.type == 'checkbox') {
res = master.checked + '';
res = res.charAt(0).toUpperCase() + res.substr(1);
res = [res];
}
else { // SELECT widget
for (var i=0; i < widget.options.length; i++) {
if (widget.options[i].selected) res.push(widget.options[i].value);
res = [];
for (var i=0; i < master.options.length; i++) {
if (master.options[i].selected) res.push(master.options[i].value);
}
}
return res;
}
function updateSlaves(masterValues, appyTypeId) {
// Given the value(s) selected in a master field, this function updates the
// state of all corresponding slaves.
var slaves = cssQuery('table.slave_' + appyTypeId);
function getSlaves(master) {
// Gets all the slaves of master.
allSlaves = document.getElementsByName('slave');
res = [];
slavePrefix = 'slave_' + master.attributes['name'].value + '_';
for (var i=0; i < slaves.length; i++){
slaves[i].style.display = "none";
cssClasses = slaves[i].className.split(' ');
for (var j=0; j < cssClasses.length; j++) {
if (cssClasses[j].indexOf(slavePrefix) == 0) {
res.push(slaves[i]);
break;
}
for (var i=0; i < masterValues.length; i++) {
var activeSlaves = cssQuery('table.slaveValue_' + appyTypeId + '_' + masterValues[i]);
for (var j=0; j < activeSlaves.length; j++){
activeSlaves[j].style.display = "";
}
}
return res;
}
function updateSlaves(master) {
// Given the value(s) in a master field, we must update slave's visibility.
slaves = getSlaves(master);
masterValues = getMasterValues(master);
for (var i=0; i < slaves.length; i++) {
showSlave = false;
slaveryValues = getSlaveInfo(slaves[i], 'masterValues');
for (var j=0; j < slaveryValues.length; j++) {
for (var k=0; k< masterValues.length; k++) {
if (slaveryValues[j] == masterValues[k]) showSlave = true;
}
}
if (showSlave) slaves[i].style.display = "";
else slaves[i].style.display = "none";
}
}
function initSlaves() {
// When the current page is loaded, we must set the correct state for all
// slave fields.
var masters = cssQuery('.appyMaster');
for (var i=0; i < masters.length; i++) {
var cssClasses = masters[i].className.split(' ');
for (var j=0; j < cssClasses.length; j++) {
if (cssClasses[j].indexOf('master_') == 0) {
var appyId = cssClasses[j].split('_')[1];
var masterValue = [];
if (masters[i].nodeName == 'SPAN'){
var idField = masters[i].id;
if (idField == '') {
masterValue.push(idField);
}
else {
if ((idField[0] == '(') || (idField[0] == '[')) {
// There are multiple values, split it
var subValues = idField.substring(1, idField.length-1).split(',');
for (var k=0; k < subValues.length; k++){
var subValue = subValues[k].replace(' ','');
masterValue.push(subValue.substring(1, subValue.length-1));
}
}
else { masterValue.push(masters[i].id);
}
}
}
else { masterValue = getMasterValue(masters[i]);
}
updateSlaves(masterValue, appyId);
}
}
slaves = document.getElementsByName('slave');
walkedMasters = {}; // Remember the already walked masters.
for (var i=0; i < slaves.length; i++) {
masterName = getSlaveInfo(slaves[i], 'masterName');
if (masterName in walkedMasters) continue;
master = document.getElementById(masterName);
updateSlaves(master);
walkedMasters[masterName] = 'walked';
}
}

View file

@ -24,7 +24,7 @@
<!--
var importedElemsShown = false;
function toggleViewableElements() {
var rows = cssQuery('#importedElem');
var rows = document.getElementsByName('importedElem');
var newDisplay = 'table-row';
if (isIe) newDisplay = 'block';
if (importedElemsShown) newDisplay = 'none';
@ -33,10 +33,9 @@
}
importedElemsShown = !importedElemsShown;
}
var checkBoxesChecked = true;
function toggleCheckboxes() {
var checkBoxes = cssQuery('#cbElem');
var checkBoxes = document.getElementsByName('cbElem');
var newCheckValue = true;
if (checkBoxesChecked) newCheckValue = false;
for (var i=0; i<checkBoxes.length; i++) {
@ -44,18 +43,16 @@
}
checkBoxesChecked = newCheckValue;
}
function importSingleElement(importPath) {
var f = document.forms['importElements'];
f.importPath.value = importPath;
f.submit();
}
function importManyElements() {
var f = document.forms['importElements'];
var importPaths = '';
// Get the values of the checkboxes
var checkBoxes = cssQuery('#cbElem');
var checkBoxes = document.getElementsByName('cbElem');
for (var i=0; i<checkBoxes.length; i++) {
if (checkBoxes[i].checked) {
importPaths += checkBoxes[i].value + '|';
@ -67,7 +64,6 @@
f.submit();
}
}
-->
</script>
<tal:comment replace="nothing">Form for importing several meetings at once.</tal:comment>
@ -98,9 +94,10 @@
<tr tal:define="alreadyImported python: tool.isAlreadyImported(contentType, row[0]);
global allAreImported python: allAreImported and alreadyImported;
odd repeat/row/odd"
tal:attributes="id python:test(alreadyImported, 'importedElem', 'notImportedElem');
style python:test(alreadyImported, 'display:none', 'display:table-row');
class python:test(odd, 'even', 'odd')">
tal:attributes="id python: alreadyImported and 'importedElem' or 'notImportedElem';
name python: alreadyImported and 'importedElem' or 'notImportedElem';
style python: alreadyImported and 'display:none' or 'display:table-row';
class python: odd and 'even' or 'odd'">
<td tal:repeat="elem python: row[1:]" tal:content="elem">
</td>
<td>
@ -109,8 +106,10 @@
value python: tool.translate('query_import')"/>
<span tal:condition="alreadyImported" tal:replace="python: tool.translate('import_already')"/>
</td>
<td align="center"><input type="checkbox" checked="checked" class="noborder" id="cbElem"
tal:attributes="value python: row[0]" tal:condition="not: alreadyImported"/></td>
<td align="center">
<input type="checkbox" checked="checked" class="noborder" id="cbElem" name="cbElem"
tal:attributes="value python: row[0]" tal:condition="not: alreadyImported"/>
</td>
</tr>
</tal:row>
<tr tal:condition="python: not importElems[1] or allAreImported"><td colspan="15" tal:content="python: tool.translate('query_no_result')"></td></tr>

View file

@ -187,7 +187,7 @@
tal:define="showWorkflow python: tool.getAttr('showWorkflowFor' + contextObj.meta_type);
hasHistory contextObj/hasHistory;
historyMaxPerPage options/maxPerPage|python: 5;
historyExpanded python: tool.getCookieValue('appyHistory', default='collapsed') == 'expanded';
historyExpanded python: request.get('appyHistory', 'collapsed') == 'expanded';
creator contextObj/Creator"
tal:condition="not: contextObj/isTemporary">

View file

@ -56,7 +56,7 @@
<tal:searchOrGroup repeat="searchOrGroup python: tool.getSearches(rootClass)">
<tal:group condition="searchOrGroup/isGroup">
<tal:expanded define="group searchOrGroup;
expanded python: tool.getCookieValue(group['labelId'], default='collapsed') == 'expanded'">
expanded python: request.get(group['labelId'], 'collapsed') == 'expanded'">
<tal:comment replace="nothing">Group name</tal:comment>
<dt class="portletAppyItem portletGroup">
<img align="left" style="cursor:pointer"

View file

@ -8,7 +8,7 @@
<tal:comment replace="nothing">Query result</tal:comment>
<div id="queryResult"></div>
<script language="javascript"
<script type="text/javascript"
tal:define="ajaxUrl python: tool.getQueryUrl(contentType, searchName)"
tal:content="python: 'askQueryResult(\'queryResult\', \'%s\',\'%s\',\'%s\',0)' % (tool.absolute_url(), contentType, searchName)">
</script>

View file

@ -1,6 +1,7 @@
<tal:comment replace="nothing">View macro for a Boolean.</tal:comment>
<metal:view define-macro="view">
<span tal:attributes="class widget/master_css; id rawValue" tal:content="value"></span>
<span tal:attributes="class masterCss; value rawValue; name name; id name"
tal:content="value"></span>
</metal:view>
<tal:comment replace="nothing">Edit macro for an Boolean.</tal:comment>
@ -9,8 +10,8 @@
tal:attributes="name python: name + '_visible';
id name;
checked python:contextObj.checkboxChecked(name, rawValue);
onClick python:'toggleCheckbox(\'%s\', \'%s_hidden\');;updateSlaves(getMasterValue(this), \'%s\')' % (name, name, widget['id']);
class python: 'noborder ' + widget['master_css']"/>
onClick python:'toggleCheckbox(\'%s\', \'%s_hidden\');;updateSlaves(this)' % (name, name);
class python: 'noborder %s' % masterCss"/>
<input tal:attributes="name name;
id string:${name}_hidden;
value python: test(contextObj.checkboxChecked(name, rawValue), 'True', 'False')"

View file

@ -1,7 +1,7 @@
<tal:comment replace="nothing">View macro for a Float.</tal:comment>
<metal:view define-macro="view">
<span tal:content="value"
tal:attributes="id value; class widget/master_css"></span>
tal:attributes="value value; class masterCss; name name; id name"></span>
</metal:view>
<tal:comment replace="nothing">Edit macro for an Float.</tal:comment>

View file

@ -1,6 +1,7 @@
<tal:comment replace="nothing">View macro for an Integer.</tal:comment>
<metal:view define-macro="view">
<span tal:content="value" tal:attributes="id value; class widget/master_css"></span>
<span tal:content="value"
tal:attributes="value value; class masterCss; name name; id name"></span>
</metal:view>
<tal:comment replace="nothing">Edit macro for an Integer.</tal:comment>

View file

@ -9,15 +9,23 @@
contextMacro The base folder containing the macros to call for
rendering the elements within the layout.
Defaults to app.skyn
slaveId The name and id of the main tag for this layout (used
for master/slave relationships).
slaveCss The CSS class for a slave.
</tal:comment>
<metal:show define-macro="layout"
tal:define="contextMacro contextMacro| python: app.skyn">
tal:define="contextMacro contextMacro| python: app.skyn;
slaveId slaveId|python:'';
slaveCss slaveCss|python:'';
layoutCss layout/css_class;">
<table tal:attributes="cellpadding layout/cellpadding;
cellspacing layout/cellspacing;
width layout/width;
align layout/align;
class layout/css_class;
style layout/style">
class python: slaveCss and ('%s %s' % (slaveCss, layoutCss)) or layoutCss;
style layout/style;
id slaveId;
name slaveId;">
<tal:comment replace="nothing">The table header row</tal:comment>
<tr tal:condition="layout/headerRow" tal:attributes="valign layout/headerRow/valign">
<th tal:repeat="cell layout/headerRow/cells"
@ -52,7 +60,10 @@
inRequest python: request.has_key(name);
errors errors | python: ();
inError python: (widget['name'] in errors) and True or False;
isMultiple python: (widget['multiplicity'][1] == None) or (widget['multiplicity'][1] &gt; 1)">
isMultiple python: (widget['multiplicity'][1] == None) or (widget['multiplicity'][1] &gt; 1);
masterCss python: widget['slaves'] and ('master_%s' % name) or '';
slaveCss python: widget['master'] and ('slave_%s_%s' % (widget['masterName'], '_'.join(widget['masterValue']))) or '';
slaveId python: widget['master'] and 'slave' or ''">
<metal:layout use-macro="here/skyn/widgets/show/macros/layout"/>
</metal:field>
@ -65,7 +76,11 @@
layoutType "edit"? "view"? "cell?"
widget The widget to render
</tal:comment>
<metal:group define-macro="group">
<metal:group define-macro="group"
tal:define="slaveCss python: widget['master'] and ('slave_%s_%s' % (widget['masterName'], '_'.join(widget['masterValue']))) or '';
widgetCss widget/css_class;
groupCss python: slaveCss and ('%s %s' % (slaveCss, widgetCss)) or widgetCss;
slaveId python: widget['master'] and 'slave' or ''">
<fieldset tal:condition="python: widget['style'] == 'fieldset'">
<legend tal:condition="widget/hasLabel">
<i tal:content="structure python: contextObj.translate(widget['labelId'])"></i>
@ -81,9 +96,9 @@
<metal:content use-macro="app/skyn/widgets/show/macros/groupContent"/>
</tal:asSection>
<tal:asTabs condition="python: widget['style'] == 'tabs'">
<table cellpadding="0" cellspacing="0"
tal:attributes="width widget/wide;
class widget/css_class">
<table tal:attributes="width widget/wide;
class groupCss;
id slaveId; name slaveId">
<tal:comment replace="nothing">First row: the tabs.</tal:comment>
<tr valign="middle"><td style="border-bottom: 1px solid #ff8040">
<table cellpadding="0" cellspacing="0" style="position:relative; bottom:-1px;">
@ -132,9 +147,10 @@
tal:define="cellgap widget/cellgap"
tal:attributes="width widget/wide;
align widget/align;
class widget/css_class;
class groupCss;
cellspacing widget/cellspacing;
cellpadding widget/cellpadding">
cellpadding widget/cellpadding;
id slaveId; name slaveId">
<tal:comment replace="nothing">Display the title of the group if it is not rendered a fieldset.</tal:comment>
<tr tal:condition="python: (widget['style'] != 'fieldset') and widget['hasLabel']">
<td tal:attributes="colspan python: len(widget['columnsWidths']);

View file

@ -2,7 +2,7 @@
<metal:view define-macro="view"
tal:define="fmt widget/format">
<span tal:condition="python: fmt in (0, 3)"
tal:attributes="class widget/master_css; id rawValue">
tal:attributes="class masterCss; value rawValue; name name; id name">
<ul tal:condition="python: value and isMultiple">
<li tal:repeat="sv value"><i tal:content="structure sv"></i></li>
</ul>
@ -31,8 +31,8 @@
tal:attributes="name name;
id name;
multiple python: isMultiple and 'multiple' or '';
onchange python: isMaster and ('updateSlaves(getMasterValue(this), \'%s\')' % widget['id']) or '';
class widget/master_css;
onchange python: isMaster and 'updateSlaves(this)' or '';
class masterCss;
size python: isMultiple and widget['height'] or 1">
<option tal:repeat="possibleValue possibleValues"
tal:attributes="value python: possibleValue[0];