Okay, here's the core of the code. First, this is the function that creates
the form (in a component and submitted via ajax). It's a generic form for
editing db records based on a url arg supplying the table name. You'll
notice that I have to use a hack at the moment to get the values from my
custom form (AjaxSelect) to submit. The widget code is below:
def editform(self, rargs=None, rvars=None):
"""
"""
db = current.db
flash = ''
rjs = ''
duplink = ''
default_vars = {}
if rargs is not None:
tablename = rargs[0]
showid = rvars['showid'] or True
dbio = False if 'dbio' in rvars.keys() and rvars['dbio'] ==
'False' else True
formstyle = rvars['formstyle'] or 'ul'
deletable = rvars['deletable'] or True
copylabel = rvars['copylabel'] or SPAN(_class='glyphicon
glyphicon-file')
orderby = rvars['orderby'] or 'id'
restrictor = rvars['restrictor'] or None
collation = rvars['collation'] or None
postprocess = rvars['postprocess'] or None
if len(rargs) > 1: # editing specific item
rowid = rargs[1]
formname = '{}/{}'.format(tablename, rowid)
formargs = [db[tablename], rowid]
# create a link for adding a new row to the table
duplink = A(copylabel,
_href=URL('plugin_listandedit',
'dupAndEdit.load',
args=[tablename, rowid],
vars=rvars),
_class='plugin_listandedit_duplicate',
cid='viewpane')
elif len(rargs) == 1: # creating new item
formname = '{}/create'.format(tablename)
default_vars = {k: v for k, v in rvars.iteritems()
if hasattr(db[tablename], k)}
formargs = [db[tablename]]
form = self._myform(formargs,
deletable=deletable,
showid=showid,
formstyle=formstyle)
# print {'default_vars': default_vars}
# for k in default_vars: form.vars.setitem(k, default_vars[k])
for k in default_vars: form.vars[k] = default_vars[k]
# FIXME: ajaxselect field values have to be added manually
# FIXME: this check will fail if ajaxselect widget is for field
indx[1]
if db[tablename].fields[1] in rvars.keys():
extras = [f for f in db[tablename].fields
if f not in form.vars.keys()]
for e in extras:
form.vars[e] = rvars[e] if e in rvars.keys() \
else ''
if 'id' in form.vars.keys() and form.vars['id'] in (None, ''
):
del(form.vars['id'])
else:
pass
# print 'form vars in editform
---------------------------------'
# pprint(form.vars)
if form.process(formname=formname, dbio=dbio).accepted:
flash = ''
if postprocess:
flash += '{} '.format(self._post_process(form.vars,
postprocess))
if dbio:
flash += 'The changes were recorded successfully.'
# either redirect or refresh the list pane
if 'redirect' in rvars and 'True' == rvars['redirect']:
redirect(URL(rvars['redirect_c'], rvars['redirect_a']))
else:
the_url = URL('plugin_listandedit', 'itemlist.load',
args=[tablename], vars={'orderby': orderby
,
'restrictor':
restrictor,
'collation':
collation})
rjs = "window.setTimeout(web2py_component('{}', " \
"'listpane'), 500);".format(the_url)
elif form.errors:
print '\n\nlistandedit form errors:'
pprint({k: v for k, v in form.errors.iteritems()})
print '\n\nlistandedit form vars'
pprint({k: v for k, v in form.vars.iteritems()})
print '\n\nlistandedit request vars'
pprint({k: v for k, v in rvars.iteritems()})
flash = 'Sorry, there was an error processing ' \
'the form. The changes have not been
recorded.'
else:
pass
else:
flash = 'Sorry, you need to specify a type of record before' \
'I can list the records.'
form = None
return form, duplink, flash, rjs
Now here's the first bit of the custom widget class. The thing to
understand here is that the widget is refreshable (via ajax) without
affecting the rest of the form. So you can update the select options via
ajax from the database while you're in the midst of filling in the form. I
do this by creating a web2py component to hold the form. So the
AjaxSelect.widget() method below sets up the component, and the
set_widget() controller function calls back to AjaxSelect.widget_content()
to actually create the widget instance. (The widget also does other things
like display a sortable list of selected options from a multi-select and
dynamically add new db entries to the linked table.) If you want to see the
full plugin code for the widget it's on github here:
https://github.com/monotasker/plugin_ajaxselect
I'm wondering whether putting the widget in a component is what creates the
problem. If so I'd really like to find a way around that. It's a very
powerful and configurable widget that (if I can get the last kinks worked
out) I'd like to share with the community.
class AjaxSelect(object):
"""
"""
def __init__(self, field, value, indx=0,
refresher=None, adder=True,
restricted=None, restrictor=None,
multi=True, lister=False,
rval=None, sortable=False,
orderby=None):
# raw args
self.field = field
self.indx = indx
self.refresher = refresher
self.adder = adder
# isolate setting of param for easy overriding in subclasses
self.restricted = self.restrict(restricted)
self.restrictor = restrictor
self.multi = multi
self.lister = lister
self.rval = rval
self.sortable = sortable
self.orderby = orderby
# find table referenced by widget
self.fieldset = str(field).split('.')
self.linktable = get_linktable(field)
# processed variables
self.wrappername = self.get_wrappername(self.fieldset)
self.form_name = '%s_adder_form' % self.linktable # for referenced
table form
# get the field value (choosing db or session here)
self.value = self.choose_val(value)
try:
if value and len(value) > 0:
self.clean_val = ','.join(map(str, value))
else:
self.clean_val = value
except TypeError:
self.clean_val = value
# args for add and refresh urls
self.uargs = self.fieldset
# vars for add and refresh urls
self.uvars = {'wrappername': self.wrappername,
'refresher': refresher,
'adder': self.adder,
'restrictor': self.restrictor,
'multi': self.multi,
'lister': self.lister,
'restricted': self.restricted,
'sortable': self.sortable,
'orderby': self.orderby,
'indx': self.indx}
def widget(self):
"""
Place initial load container for controller to fill.
"""
# prepare classes for widget wrapper
wclasses = self.get_classes(self.linktable, self.restricted,
self.restrictor, self.lister, self.
sortable)
uvars = self.uvars
uvars.update({self.fieldset[1]: self.value})
# create SPAN to wrap widget
wrapper = SPAN(_id=self.wrappername, _class=wclasses)
wrapper.append(LOAD('plugin_ajaxselect', 'set_widget.load',
args=self.uargs, vars=uvars,
target=self.wrappername,
ajax=False))
return wrapper
def widget_contents(self):
"""
Main method to create the ajaxselect widget. Calls helper methods
and returns the wrapper element containing all associated elements
"""
#session = current.session
#request = current.request
wrapper = CAT()
# create and add content of SPAN
widget = self.create_widget()
refreshlink = self.make_refresher(self.wrappername, self.linktable,
self.uargs, self.uvars)
adder, modal = self.make_adder(self.wrappername, self.linktable)
wrapper.components.extend([widget, refreshlink, adder])
# create and add tags/links if multiple select widget
if self.multi and (self.lister == 'simple'):
taglist = self.make_taglist()
elif self.multi and (self.lister == 'editlinks'):
taglist = self.make_linklist()
else:
taglist = ''
wrapper.append(taglist)
return wrapper, modal
def create_widget(self):
"""
create either a single select widget or multiselect widget
"""
if not self.multi in [None, False, 'False']:
if self.orderby:
w = FilteredMultipleOptionsWidget.widget(self.field, self.
value,
orderby=self.orderby,
multiple='multiple')
else:
w = MultipleOptionsWidget.widget(self.field, self.value)
#place selected items at end of sortable select widget
if self.sortable:
try:
for v in self.value:
opt = w.element(_value=v)
i = w.elements().index(opt)
w.append(opt)
del w[i - 1]
except AttributeError, e:
if type(v) == 'IntType':
opt = w.element(_value=self.value)
i = w.elements().index(opt)
w.append(opt)
del w[i - 1]
else:
print e
except Exception, e:
print e, type(e)
else:
if self.orderby:
w = FilteredOptionsWidget.widget(self.field, self.value,
orderby=self.orderby)
else:
w = OptionsWidget.widget(self.field, self.value)
w['_id'] = '{}_{}'.format(self.fieldset[0], self.fieldset[1])
w['_name'] = self.fieldset[1]
return w
On Thursday, February 18, 2016 at 8:33:23 PM UTC-5, Anthony wrote:
>
> When you pass a record to SQLFORM, it retains the changed values by
> passing the values in request.post_vars to the respective field widgets.
> Can't say what's going on in your case without the code.
>
> Anthony
>
> On Thursday, February 18, 2016 at 4:13:23 PM UTC-5, Ian W. Scott wrote:
>>
>> The code is very extensive (it's a complex widget) so first I'm just
>> trying to understand what happens in a normal form. You say that other
>> widgets aren't preserving values from the db. So where do the values come
>> from if you have keepvalues=True?
>>
>> Also, this *is* a situation where an existing record is loaded into the
>> form, then modified on form submission. So how does the form present to me
>> the values I just changed if it's not coming from the db? I think if I can
>> understand how that works I'll be able to solve the problem in my code
>> myself.
>>
>> Thanks,
>>
>> Ian
>>
>> On Thursday, February 18, 2016 at 4:06:06 PM UTC-5, Anthony wrote:
>>>
>>> On Thursday, February 18, 2016 at 3:54:43 PM UTC-5, Ian W. Scott wrote:
>>>>
>>>> Okay, that explains the behaviour. How, then, are values usually
>>>> preserved on an ajax form for the built-in widgets? When I submit the form
>>>> all of the other fields (not using my widget) preserve the newly submitted
>>>> values.
>>>>
>>>
>>> Again, hard to say what's going on in your case without seeing any code,
>>> but the other widgets are not presenting values from the database (unless a
>>> pre-existing record has been loaded).
>>>
>>> Anthony
>>>
>>
--
Resources:
- http://web2py.com
- http://web2py.com/book (Documentation)
- http://github.com/web2py/web2py (Source code)
- https://code.google.com/p/web2py/issues/list (Report Issues)
---
You received this message because you are subscribed to the Google Groups
"web2py-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
For more options, visit https://groups.google.com/d/optout.