[gen] Master-slave fields: slave values can now ajax-change when the user modifies master values.

This commit is contained in:
Gaetan Delannay 2014-03-03 18:54:21 +01:00
parent f7172be6ee
commit b8ceb66a49
15 changed files with 253 additions and 74 deletions

View file

@ -171,6 +171,9 @@ class Config:
# People having one of these roles will be able to create instances
# of classes defined in your application.
defaultCreators = ['Manager']
# The "root" classes are those that will get their menu in the user
# interface. Put their names in the list below.
rootClasses = []
# Number of translations for every page on a Translation object
translationsPerPage = 30
# Language that will be used as a basis for translating to other

View file

@ -553,9 +553,6 @@ class ZopeGenerator(Generator):
if theImport not in imports:
imports.append(theImport)
repls['imports'] = '\n'.join(imports)
# Compute root classes
repls['rootClasses'] = ','.join(["'%s'" % c.name \
for c in classesButTool if c.isRoot()])
# Compute list of class definitions
repls['appClasses'] = ','.join(['%s.%s' % (c.klass.__module__, \
c.klass.__name__) for c in classes])
@ -564,6 +561,9 @@ class ZopeGenerator(Generator):
for c in classes])
repls['allClassNames'] = ','.join(['"%s"' % c.name \
for c in classesButTool])
allShortClassNames = ['"%s":"%s"' % (c.name.split('_')[-1], c.name) \
for c in classesAll]
repls['allShortClassNames'] = ','.join(allShortClassNames)
# Compute the list of ordered attributes (forward and backward,
# inherited included) for every Appy class.
attributes = []

View file

@ -209,8 +209,12 @@ class ToolMixin(BaseMixin):
def getRootClasses(self):
'''Returns the list of root classes for this application.'''
cfg = self.getProductConfig()
return [self.getAppyClass(k) for k in cfg.rootClasses]
cfg = self.getProductConfig().appConfig
rootClasses = cfg.rootClasses
if not rootClasses:
# We consider every class as being a root class.
rootClasses = cfg.appClassNames
return [self.getAppyClass(k) for k in rootClasses]
def getSearchInfo(self, className, refInfo=None):
'''Returns, as an object:
@ -466,6 +470,10 @@ class ToolMixin(BaseMixin):
'''Gets the Appy class corresponding to the Zope class named p_name.
If p_wrapper is True, it returns the Appy wrapper. Else, it returns
the user-defined class.'''
# p_zopeName may be the name of the Zope class *or* the name of the Appy
# class (shorter, not prefixed with the underscored package path).
classes = self.getProductConfig().allShortClassNames
if zopeName in classes: zopeName = classes[zopeName]
zopeClass = self.getZopeClass(zopeName)
if wrapper: return zopeClass.wrapperClass
else: return zopeClass.wrapperClass.__bases__[-1]

View file

@ -814,6 +814,20 @@ class BaseMixin:
res.append(field)
return res
def getSlaveFieldsRequestValues(self, pageName):
'''Returns the list of slave fields having a masterValue being a
method.'''
res = {}
req = self.REQUEST
for field in self.getAllAppyTypes():
if field.page.name != pageName: continue
if field.masterValue and callable(field.masterValue):
# We have such a field
name = field.name
if req.has_key(name) and req[name]:
res[name] = req[name]
return sutils.getStringDict(res)
def getCssJs(self, fields, layoutType, res):
'''Gets, in p_res ~{'css':[s_css], 'js':[s_js]}~ the lists of
Javascript and CSS files required by Appy types p_fields when shown

View file

@ -20,10 +20,10 @@ PROJECTNAME = '<!applicationName!>'
diskFolder = os.path.dirname(<!applicationName!>.__file__)
# Applications classes, in various formats
rootClasses = [<!rootClasses!>]
appClasses = [<!appClasses!>]
appClassNames = [<!appClassNames!>]
allClassNames = [<!allClassNames!>]
allShortClassNames = {<!allShortClassNames!>}
# In the following dict, we store, for every Appy class, the ordered list of
# fields.

View file

@ -228,10 +228,13 @@ function askComputedField(hookId, objectUrl, fieldName) {
askAjaxChunk(hookId, 'GET', objectUrl, fieldName+':pxViewContent');
}
function askField(hookId, objectUrl, layoutType, showChanges){
function askField(hookId, objectUrl, layoutType, showChanges, masterValues,
requestValue){
// Sends an Ajax request for getting the content of any field.
var fieldName = hookId.split('_')[1];
var params = {'layoutType': layoutType, 'showChanges': showChanges};
if (masterValues) params['masterValues'] = masterValues.join('*');
if (requestValue) params[fieldName] = requestValue;
askAjaxChunk(hookId, 'GET', objectUrl, fieldName+':pxRender', params, null,
evalInnerScripts);
}
@ -286,14 +289,15 @@ function toggleSubTitles(tag) {
// Functions used for master/slave relationships between widgets
function getSlaveInfo(slave, infoType) {
// Returns the appropriate info about slavery, depending on p_infoType.
cssClasses = slave.className.split(' ');
var cssClasses = slave.className.split(' ');
var masterInfo = null;
// Find the CSS class containing master-related info.
for (var j=0; j < cssClasses.length; j++) {
if (cssClasses[j].indexOf('slave_') == 0) {
if (cssClasses[j].indexOf('slave*') == 0) {
// Extract, from this CSS class, master name or master values.
masterInfo = cssClasses[j].split('_');
masterInfo = cssClasses[j].split('*');
if (infoType == 'masterName') return masterInfo[1];
else return masterInfo.slice(2); // Master values
else return masterInfo.slice(2);
}
}
}
@ -337,7 +341,7 @@ function getSlaves(master) {
if (master.type == 'checkbox') {
masterName = masterName.substr(0, masterName.length-8);
}
slavePrefix = 'slave_' + masterName + '_';
slavePrefix = 'slave*' + masterName + '*';
for (var i=0; i < allSlaves.length; i++){
cssClasses = allSlaves[i].className.split(' ');
for (var j=0; j < cssClasses.length; j++) {
@ -350,37 +354,52 @@ function getSlaves(master) {
return res;
}
function updateSlaves(master, slave) {
// Given the value(s) in a master field, we must update slave's visibility.
// If p_slave is given, it updates only this slave. Else, it updates all
// slaves of p_master.
function updateSlaves(master, slave, objectUrl, layoutType, requestValues){
/* Given the value(s) in a master field, we must update slave's visibility or
value(s). If p_slave is given, it updates only this slave. Else, it updates
all slaves of p_master. */
var slaves = null;
if (slave) { slaves = [slave]; }
else { 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";
if (slaveryValues[0] != '+') {
// Update slaves visibility depending on master values.
var showSlave = false;
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';
}
else {
// Update slaves' values depending on master values.
var slaveId = slaves[i].id;
var slaveName = slaveId.split('_')[1];
var reqValue = null;
if (requestValues && (slaveName in requestValues))
reqValue = requestValues[slaveName];
askField(slaveId, objectUrl, layoutType, false, masterValues, reqValue);
}
}
}
function initSlaves() {
// When the current page is loaded, we must set the correct state for all
// slave fields.
function initSlaves(objectUrl, layoutType, requestValues) {
/* When the current page is loaded, we must set the correct state for all
slave fields. p_requestValues are those from the slave fields that must
be ajax-updated. */
slaves = getElementsHavingName('table', 'slave');
i = slaves.length -1;
while (i >= 0) {
masterName = getSlaveInfo(slaves[i], 'masterName');
master = document.getElementById(masterName);
// If master is not here, we can't hide its slaves when appropriate.
if (master) updateSlaves(master, slaves[i]);
if (master) {
updateSlaves(master, slaves[i], objectUrl, layoutType, requestValues);
}
i -= 1;
}
}

View file

@ -211,7 +211,8 @@ def writeCookie(login, password, request):
# ------------------------------------------------------------------------------
def initMasterValue(v):
'''Standardizes p_v as a list of strings.'''
'''Standardizes p_v as a list of strings, excepted if p_v is a method.'''
if callable(v): return v
if not isinstance(v, bool) and not v: res = []
elif type(v) not in sutils.sequenceTypes: res = [v]
else: res = v

View file

@ -141,7 +141,9 @@ class ToolWrapper(AbstractWrapper):
</x>''')
pxPageBottom = Px('''
<script type="text/javascript">initSlaves();</script>''')
<script type="text/javascript">:'initSlaves(%s,%s,%s)' % \
(q(zobj.absolute_url()), q(layoutType), \
zobj.getSlaveFieldsRequestValues(page))</script>''')
pxPortlet = Px('''
<x var="toolUrl=tool.url;
@ -260,11 +262,11 @@ class ToolWrapper(AbstractWrapper):
# Hook for defining a PX that proposes additional links, after the links
# corresponding to top-level pages.
pxLinks = ''
pxLinks = Px('')
# Hook for defining a PX that proposes additional icons after standard
# icons in the user strip.
pxIcons = ''
pxIcons = Px('')
# Displays the content of a layouted object (a page or a field). If the
# layouted object is a page, the "layout target" (where to look for PXs)
@ -310,7 +312,7 @@ class ToolWrapper(AbstractWrapper):
</table>''', template=AbstractWrapper.pxTemplate, hook='content')
# Show on query list or grid, the field content for a given object.
pxQueryField = Px('''<x>
pxQueryField = Px('''
<!-- Title -->
<x if="field.name == 'title'"
var2="navInfo='search.%s.%s.%d.%d' % \
@ -350,8 +352,7 @@ class ToolWrapper(AbstractWrapper):
<x if="field.name != 'title'">
<x var="layoutType='cell'; innerRef=True"
if="zobj.showField(field.name, 'result')">:field.pxRender</x>
</x>
</x>''')
</x>''')
# Show query results as a list.
pxQueryResultList = Px('''