Chapter 4: Write our Client Code

Now that we’ve written our server code, let’s move back to the client side. In Chapter 1 we created the UI on the design slide of the form, to write our code we’ll going to the aptly name Code tab.

https://extension-documentation.s3.amazonaws.com/tutorials/salesforce/image028.png

Add the following code to your form:

from trexjacket import api
dashboard = api.get_dashboard()

First thing we will do is import the trexjacket library we setup all the way back in the dependency section. Then we are going to setup the dashboard object.

Create a dictionary to house the Opportunity attributes we want to change. We’ll also call a function we haven’t created yet self.reset() to clear the dictionary on screen load and refresh.

self.reset()
self.new_dict = {
    'StageName': None,
       'CloseDate': None,
       'Amount': None
    }

Call our get_opportunity_stages server function to fetch the list from Tableau. When calling server functions, we use the syntax anvil.server.call(function name', args, *kwargs)

Then we want to stick those stages into a drop-down element in our screen. When you want to reference a screen element, we use self to reference the form class then the object name, self.object_name.

We can assign a list to a drop-down property with items, self.drop_down_name.items

Anvil Docs on Drop Downs

oppstages = anvil.server.call('opportunity_stages')
self.drop_down_stage.items = oppstages

To finish off the init we’re going to register the event handler. The event handler captures any selections the user makes in their dashboard so the extension can take action.

In our case, the event handler we give us the attributes of the selected Opportunity.

We want selection_changed and we’ll call our event handler self.my_form_event_handler.

All the code in this section goes before the line self.init_components(**properties) so it takes place before the form loads the UI.

dashboard.register_event_handler('selection_changed', self.my_form_event_handler)
self.init_components(**properties)

Functions

In this section we will create the functions for our class.

Event Handler

In the extension library the event is the thing that changes when a ‘selection_changed’ occurs. We want all the records from each change.

If something did change, we want to assign class variables with selected value, in our case the opportunity the user picked. We get the rest of the information using our server function get_opportunity.

The event handler closes with self.refresh_data_bindings(). Refresh data binding will update any screen objects with the new values, you will want to do this after changing any bound variables. We will bind screen objects in a later section.

def my_form_event_handler(self, event):
    selected_value = event.get_selected_marks()
    if selected_value:
      try:
        self.opp_name = selected_value[0]['Name']
        self.opp_ID = selected_value[0]['Opportunity ID']

        self.opportunity = anvil.server.call('get_opportunity',selected_value[0]['Opportunity ID'])
        self.opp_stage = self.opportunity['StageName']
        self.opp_date = self.opportunity['CloseDate']
        self.opp_amount = self.opportunity['Amount']
      except KeyError:
        pass
    else:
      self.reset()
    self.refresh_data_bindings()

Reset

We’re adding reset to allow the user to clear the selected opportunity on page refresh or by clicking on a blank space in the dashboard.

def reset(self):
    self.opp_name = None
    self.opp_ID = None
    self.opp_stage = None
    self.opp_date = None
    self.opp_amount = None

Clear Changes

Clear changes will change all the selections in our new_dict to None, we call clear changes after we submit the API request to update the opportunity. This prevents the extension from sending unnecessary update requests.

def clear_changes(self):
    self.new_dict = {x: None for x in self.new_dict}

Buttons

The remainder of our client code will handle screen events. Screen events occur when a user takes action on the UI like clicking a button or changing text in a text box.

You can find Events in the Container Properties. Go back to the Design view, look to the right side of the screen where you see Toolbox, then scroll to the bottom of the panel.

Depending on the screen component you’ve selected you you’ll see different options like Show, Hide, Click, and Change.

Anvil Documentation on Components and Events

Submit Changes

On the Design view select your Submit Changes button, scroll down on the right-side panel to the Container Properties section, click the blue button next to click. This will open the split view and create the function button_submit_click.

def button_submit_click(self, **event_args):
    """This method is called when the button is clicked"""
    pass
https://extension-documentation.s3.amazonaws.com/tutorials/salesforce/image038.png

In the button submit click function we will add a call to the update_opportunity server function we created passing the self.opp_ID and self.new_dict as parameters.

If the return from the server function is True, we want clear our updates so we have a clean slate for any other changes.

Else, we’d like to create an alert letting the user know they haven’t made any changes. Alerts generate a pop-up with content on the user’s screen. Will we add a simple alert with some text.

Anvil Documentation on Basic Components

Anvil Documentation on Alerts

def button_submit_click(self, **event_args):
    """This method is called when the button is clicked"""
    response = anvil.server.call('update_opportunity',self.opp_ID,self.new_dict)
    if response:
      self.clear_changes()
    else:
      alert('Please make a change before submitting.')

Pick from the Drop-Down

On the Design view select your Opportunity Stage drop-down, scroll down on the right-side panel to the Container Properties section, click the blue button next to change.

This will open the split view and create the function drop_down_stage_change.

Assign the selected value from the drop down to the StageName variable in our new_dict and refresh the data bindings.

Anvil Documentation on Basic Components

def drop_down_stage_change(self, **event_args):
    """This method is called when an item is selected"""
    self.new_dict['StageName'] = self.drop_down_stage.selected_value
    self.refresh_data_bindings()

Change the Close Date

On the Design view select your Close Date date picker, scroll down on the right-side panel to the Container Properties section, click the blue button next to change.

This will open the split view and create the function date_picker_1_change.

Assign the date from the date picker to the CloseDate variable in our new_dict, convert it to iso format, then refresh the data bindings.

Anvil Documentation on Basic Components

def date_picker_1_change(self, **event_args):
    """This method is called when the selected date changes"""
    self.new_dict['CloseDate'] = self.date_picker_1.date.isoformat()
    self.refresh_data_bindings()

Enter an Amount

On the Design view select your Amount text box, scroll down on the right-side panel to the Container Properties section, click the blue button next to change.

This will open the split view and create the function text_box_amount_change. Go back to the Design view then Container Properties then copy past text_box_amount_change into the pressed_enter field as well.

Assign the selected value from the text box to the Amount variable in our new_dict, convert the text to an Integer.

Assign the same integer to the opp_amount variable.

Refresh the data bindings.

Anvil Documentation on Basic Components

def text_box_amount_change(self, **event_args):
    """This method is called when the text in this text box is edited"""
    self.new_dict['Amount'] = int(self.text_box_amount.text)
    self.opp_amount = int(self.text_box_amount.text)
    self.refresh_data_bindings()

Your form code should now look like this:

 1from ._anvil_designer import TB_FormTemplate
 2from anvil import *
 3from anvil import tableau
 4import anvil.tables as tables
 5import anvil.tables.query as q
 6from anvil.tables import app_tables
 7import anvil.server
 8
 9from trexjacket import api
10dashboard = api.get_dashboard()
11
12class TB_Form(TB_FormTemplate):
13  def __init__(self, **properties):
14    self.reset()
15    self.new_dict = {
16        'StageName': None,
17        'CloseDate': None,
18        'Amount': None
19    }
20    oppstages = anvil.server.call('opportunity_stages')
21    self.drop_down_stage.items = oppstages
22    dashboard.register_event_handler('selection_changed', self.my_form_event_handler)
23    self.init_components(**properties)
24
25  def reset(self):
26      self.opp_name = None
27      self.opp_ID = None
28      self.opp_stage = None
29      self.opp_date = None
30      self.opp_amount = None
31
32  def my_form_event_handler(self, event):
33    selected_value = event.get_selected_marks()
34    if selected_value:
35      try:
36        self.opp_name = selected_value[0]['Name']
37        self.opp_ID = selected_value[0]['Opportunity ID']
38        self.opportunity = anvil.server.call('get_opportunity',selected_value[0]['Opportunity ID'])
39        self.opp_stage = self.opportunity['StageName']
40        self.opp_date = self.opportunity['CloseDate']
41        self.opp_amount = self.opportunity['Amount']
42      except KeyError:
43        pass
44    else:
45      self.reset()
46    self.refresh_data_bindings()
47
48  def clear_changes(self):
49    self.new_dict = {x: None for x in self.new_dict}
50
51  def button_submit_click(self, **event_args):
52      response = anvil.server.call('update_opportunity',self.opp_ID,self.new_dict)
53      if response:
54        self.clear_changes()
55        self.refresh_data_bindings()
56      else:
57        alert('Please make a change before submitting.')
58
59  def drop_down_stage_change(self, **event_args):
60      self.new_dict['StageName'] = self.drop_down_stage.selected_value
61      self.refresh_data_bindings()
62
63  def date_picker_1_change(self, **event_args):
64      self.new_dict['CloseDate'] = self.date_picker_1.date.isoformat()
65      self.refresh_data_bindings()
66
67  def text_box_amount_change(self, **event_args):
68      self.new_dict['Amount'] = int(self.text_box_amount.text)
69      self.opp_amount = int(self.text_box_amount.text)