Changing content without refreshing (Ajax)¶
Note
See also
Dishing out money¶
You can make parts of your page refresh in response to having changed the value of an input.
This example is a page on which you choose how to divide an amount you want to invest into two different funds:
You enter the total amount, and then the percentage allocated to each fund. Each time you tab out of a percentage input, the amount portion next to it and the totals at the bottom of the table are recalculated.
You can also change the total amount or elect to rather specify portions as amounts instead of percentages.
Make an HTMLElement
refreshable¶
You make an HTMLElement
(AllocationDetailForm in this example) refreshable by calling enable_refresh()
on it:
def __init__(self, view):
super().__init__(view, 'investment_order_allocation_details_form')
self.use_layout(FormLayout())
self.investment_order = InvestmentOrder.for_current_session()
self.enable_refresh(on_refresh=self.investment_order.events.allocation_changed)
self.add_allocation_controls()
self.add_allocation_table()
self.define_event_handler(self.investment_order.events.submit)
self.add_child(Button(self, self.investment_order.events.submit))
A TextInput
will trigger a refresh of the HTMLElement
passed as its refresh_widget.
def add_allocation_controls(self):
allocation_controls = self.add_child(FieldSet(self.view, legend_text='Investment allocation'))
allocation_controls.use_layout(FormLayout())
if self.exception:
self.layout.add_alert_for_domain_exception(self.exception)
total_amount_input = TextInput(self, self.investment_order.fields.amount, refresh_widget=self)
allocation_controls.layout.add_input(total_amount_input)
amount_or_percentage_radio = RadioButtonSelectInput(self, self.investment_order.fields.amount_or_percentage, refresh_widget=self)
allocation_controls.layout.add_input(amount_or_percentage_radio)
Recalculate¶
Note
The values you recalculate must be attributes of persisted model objects.
Specify an on_refresh Event
in your enable_refresh()
call to trigger a recalculate Action
:
self.enable_refresh(on_refresh=self.investment_order.events.allocation_changed)
@exposed('submit')
def events(self, events):
events.submit = Event(label='Submit', action=Action(self.submit))
events.allocation_changed = Event(action=Action(self.recalculate))
def recalculate(self):
for allocation in self.allocations:
allocation.recalculate(self.amount)
Validating results¶
When the user finally submits the InvestmentOrder, recalculate the order before validating the newly recalculated results.
def submit(self):
print('Submitting investment')
self.recalculate()
self.validate_allocations()
print('\tAmount: %s' % self.amount)
print('\tAllocations (%s)' % self.amount_or_percentage)
for allocation in self.allocations:
allocation_size = allocation.percentage if self.is_in_percentage else allocation.amount
print('\t\tFund %s(%s): %s (%s)' % (allocation.fund, allocation.fund_code, allocation_size, allocation.amount))
Session.delete(self)
If the submitted data is not correct, raise a DomainException
to indicate the problem:
def validate_allocations(self):
if self.is_in_percentage:
if self.total_allocation_percentage != 100:
raise DomainException(message='Please ensure allocation percentages add up to 100')
else:
if self.total_allocation_amount != self.amount:
raise DomainException(message='Please ensure allocation amounts add up to your total amount (%s)' % self.amount)
Communicate the error condition to the user by displaying the DomainException
as part of
AllocationDetailForm:
def add_allocation_controls(self):
allocation_controls = self.add_child(FieldSet(self.view, legend_text='Investment allocation'))
allocation_controls.use_layout(FormLayout())
if self.exception:
self.layout.add_alert_for_domain_exception(self.exception)
total_amount_input = TextInput(self, self.investment_order.fields.amount, refresh_widget=self)
allocation_controls.layout.add_input(total_amount_input)
amount_or_percentage_radio = RadioButtonSelectInput(self, self.investment_order.fields.amount_or_percentage, refresh_widget=self)
allocation_controls.layout.add_input(amount_or_percentage_radio)