tools, frontends (xmlui): add Widget.setInternalCallback:

- to add UI callback not communication with the bridge
    - a list of predefined actions can be used to copy, move, select values
    - extra data can be passed within the 'internal_data' element
TODO: frontend side operation to retrieve the data from node to data structure should be generic
This commit is contained in:
souliane 2014-04-03 14:56:16 +02:00
commit 38eb876e94
2 changed files with 141 additions and 3 deletions

View file

@ -191,7 +191,6 @@ class XMLUI(object):
raise ValueError(_("XMLUI can have only one main container"))
self._main_cont = value
def _parseChilds(self, parent, current_node, wanted = ('container',), data = None):
""" Recursively parse childNodes of an elemen
@param parent: widget container with '_xmluiAppend' method
@ -315,14 +314,25 @@ class XMLUI(object):
print(_("FIXME FIXME FIXME: widget type [%s] is not implemented") % type_)
raise NotImplementedError(_("FIXME FIXME FIXME: type [%s] is not implemented") % type_)
if self.type == 'param' and type_ != 'text':
if self.type == 'param' and type_ not in ('text', 'button'):
try:
ctrl._xmluiOnChange(self.onParamChange)
ctrl._param_category = self._current_category
except (AttributeError, TypeError): # XXX: TypeError is here because pyjamas raise a TypeError instead of an AttributeError
except (AttributeError, TypeError): # XXX: TypeError is here because pyjamas raise a TypeError instead of an AttributeError
if not isinstance(ctrl, (EmptyWidget, TextWidget, LabelWidget, JidWidget)):
print(_("No change listener on [%s]") % ctrl)
if type_ != 'text':
callback = node.getAttribute("internal_callback") or None
if callback:
fields = [field.getAttribute('name') for field in node.getElementsByTagName("internal_field")]
data = self.getInternalCallbackData(callback, node)
ctrl._xmlui_param_internal = (callback, fields, data)
if type_ == 'button':
ctrl._xmluiOnClick(self.onChangeInternal)
else:
ctrl._xmluiOnChange(self.onChangeInternal)
ctrl._xmlui_name = name
parent._xmluiAppend(ctrl)
@ -403,6 +413,8 @@ class XMLUI(object):
"""
callback_id, fields = button._xmlui_param_id
if not callback_id: # the button is probably bound to an internal action
return
data = {}
for field in fields:
escaped = self.escape(field)
@ -413,6 +425,85 @@ class XMLUI(object):
data[escaped] = ctrl['control']._xmluiGetValue()
self._xmluiLaunchAction(callback_id, data)
def onChangeInternal(self, ctrl):
""" Called when a widget that has been bound to an internal callback is changed.
This is used to perform some UI actions without communicating with the backend.
See sat.tools.xml_tools.Widget.setInternalCallback for more details.
@param ctrl: widget modified
"""
action, fields, data = ctrl._xmlui_param_internal
if action not in ('copy', 'move', 'groups_of_contact'):
raise NotImplementedError(_("FIXME: XMLUI internal action [%s] is not implemented") % action)
def copy_move(source, target):
"""Depending of 'action' value, copy or move from source to target."""
if isinstance(target, ListWidget):
if isinstance(source, ListWidget):
values = source._xmluiGetSelectedValues()
else:
values = [source._xmluiGetValue()]
if action == 'move':
source._xmluiSetValue('')
values = [value for value in values if value]
if values:
target._xmluiAddValues(values, select=True)
else:
if isinstance(source, ListWidget):
value = u', '.join(source._xmluiGetSelectedValues())
else:
value = source._xmluiGetValue()
if action == 'move':
source._xmluiSetValue('')
target._xmluiSetValue(value)
def groups_of_contact(source, target):
"""Select in target the groups of the contact which is selected in source."""
assert(isinstance(source, ListWidget))
assert(isinstance(target, ListWidget))
try:
contact_jid_s = source._xmluiGetSelectedValues()[0]
except IndexError:
return
target._xmluiSelectValues(data[contact_jid_s])
pass
source = None
for field in fields:
widget = self.ctrl_list[field]['control']
if not source:
source = widget
continue
if action in ('copy', 'move'):
copy_move(source, widget)
elif action == 'groups_of_contact':
groups_of_contact(source, widget)
source = None
def getInternalCallbackData(self, action, node):
"""Retrieve from node the data needed to perform given action.
TODO: it would be better to not have a specific way to retrieve
data for each action, but instead to have a generic method to
extract any kind of data structure from the 'internal_data' element.
@param action (string): a value from the one that can be passed to the
'callback' parameter of sat.tools.xml_tools.Widget.setInternalCallback
@param node (DOM Element): the node of the widget that triggers the callback
"""
try: # data is stored in the first 'internal_data' element of the node
data_elts = node.getElementsByTagName('internal_data')[0].childNodes
except IndexError:
return None
data = {}
if action == 'groups_of_contact': # return a dict(key: string, value: list[string])
for elt in data_elts:
jid_s = elt.getAttribute('name')
data[jid_s] = []
for value_elt in elt.childNodes:
data[jid_s].append(value_elt.getAttribute('name'))
return data
def onFormSubmitted(self, ignore=None):
""" An XMLUI form has been submited
call the submit action associated with this form

View file

@ -24,6 +24,7 @@ from wokkel import data_form
from twisted.words.xish import domish
from sat.core import exceptions
"""This library help manage XML used in SàT (parameters, registration, etc) """
SAT_FORM_PREFIX = "SAT_FORM_"
@ -299,6 +300,26 @@ class FieldBackElement(Element):
self.elem.setAttribute('name', name)
class InternalFieldElement(Element):
""" Used by internal callbacks to indicate which fields are manipulated """
type = 'internal_field'
def __init__(self, parent, name):
super(InternalFieldElement, self).__init__(parent.xmlui, parent)
self.elem.setAttribute('name', name)
class InternalDataElement(Element):
""" Used by internal callbacks to retrieve extra data """
type = 'internal_data'
def __init__(self, parent, children):
super(InternalDataElement, self).__init__(parent.xmlui, parent)
assert(isinstance(children, list))
for child in children:
self.elem.childNodes.append(child)
class OptionElement(Element):
"""" Used by ListWidget to specify options """
type = 'option'
@ -510,6 +531,32 @@ class Widget(Element):
self.elem.setAttribute('name', name)
self.elem.setAttribute('type', self.type)
def setInternalCallback(self, callback, fields, data_elts=None):
"""Set an internal UI callback when the widget value is changed.
The internal callbacks are NO callback ids, they are strings from
a predefined set of actions that are running in the scope of XMLUI.
@param callback (string): a value from:
- 'copy': process the widgets given in 'fields' two by two, by
copying the values of one widget to the other. Target widgets
of type List do not accept the empty value.
- 'move': same than copy but moves the values if the source widget
is not a List.
- 'groups_of_contact': process the widgets two by two, assume A is
is a list of JID and B a list of groups, select in B the groups
to which the JID selected in A belongs.
- more operation to be added when necessary...
@param fields (list): a list of widget names (string)
@param data_elts (list[Element]): extra data elements
"""
self.elem.setAttribute('internal_callback', callback)
if fields:
for field in fields:
InternalFieldElement(self, field)
if data_elts:
InternalDataElement(self, data_elts)
class InputWidget(Widget):
pass