Capturing Form component attributes into a data structure

Good question.

I’m not yet sure if it’s possible, but since instantiated Forms (e.g. MyForm(…)) are instantiated Python, I was imagining using Python’s marshal, json or pickle Module (or something else) to serialize it to a Simple Object column.

First, though, I need to architect how to apply the same Form differently for content creators versus survey takers in an elegant way (… besides adding additional components and hiding / unhiding them depending on the role the Form is being used in: ie, content creator versus survey taker).

Ideally it would be:

  1. Content creator adds content to a Form (with additional components visible to a content creator but invisible to the survey taker). Maybe Data Bindings can eliminate the need for said additional components.
  2. Pickle the completed Form to a Data Table as is.
  3. Retrieve that Form from the Data Table, Unpickle it and, render it with components specific to survey taking view visible (and components specific to creator creation view invisible). And again, maybe Data Bindings can eliminate the need for said additional components.

95%, and preferably 100%, of the components would be the same for each “view”. Because I really want a WYSIWYG approach, and maybe Data Bindings can help this goal.

I hope that helps. :slight_smile:

PS: Picking the entire SubForm might be a bit much to ask (and may fail because it’s a larger and more complex data-structure), but if Data Bindings can capture Form component attribute values, then perhaps I can serialize those via Data Bindings capture. I’ll begin to read on Data Bindings shortly. I have to get my AM coffee first. LoL Thank you all in advance!

I don’t fully understand the big picture of what you are trying to do, but whether you create the form in the IDE or dynamically from code, serializing/deserialising it could give you half working objects, maybe with the correct html, but I’m afraid Anvil will not be able to work with it. Events, databinding and other magics may not not work (I haven’t tried, just a feeling).

If the forms are designed with the IDE, then all you need to do is pick the right form and play with databinding. You will store something like {'form': 'FormAbc', 'value1': 123, 'value2': 'xyz'}.

If the forms are generated dynamically, then you can add details about what components are to be placed in the form.
Something like:

[
    {'type': 'checkbox', 'text': 'Click me!', 'checked': True},
    {'type': 'radiobutton', 'text': 'No, click me!', 'selected': True},
]

Then you can add the required components at run time. This way Anvil creates the components first, then renders them to html. After creating each component you can also add event listeners and do whatever you like.

You can see an example of dynamic creation of components based on a list of dictionaries on the InputBox: Input_box() and alert2()


EDIT
Maybe you can use the input_box itself to show different input forms. I am using it in all my apps now :slight_smile: (obviously only if (1) it covers your use cases and (2) it looks good enough for your app)

2 Likes

I’ve done this in a different context, and I’ll echo the other advice, serialized forms are not what you want. In the simplest case, have all the answer components be present on the form, and make one of them visible based on the data given by the content creator. For more complex cases, as @stefano.menci says, create the components/sub forms dynamically.

1 Like

Good morning again @stefano.menci @jshaffstall

Okay, I won’t serialize the Forms (I didn’t like the whole form idea anyway, as I alluded). However, I will be serializing component attributes populated by content creators, which are the simplest of data types (True, False, int, str, etc) and shouldn’t pose a problem.

The SubForm’s components, look & feel, UI/UX, will never change even one bit (that’s not the content creators role). They are complete as I have designed them.

So, I’ll just do:

  • For content creator: Instantiate SubForm1(…); unhide only components material to the content creator; let them complete it; and finally capture the relevant attributes of the now-populated SubForm1() instance (not the whole Form) into a Data Table.

Then, do the reverse (unwind this) for the servey taker:

  • For Survey Taker: Read in attributes dict() from the Data Table; Instantiate a SubForm1(…) (initially all or most components are hidden); populate that instance’s content-related attributes from said dict(); and finally, unhide only those components material to the servey taker; and off the servey taker runs.

I’ll know how which “render path” to take (content creator versus survey taker) based on eMail login (the User table has an is_SurveyTaker boolean column).

Again, there are no dynamic Forms (I’ve designed them and they won’t change). Only component attributes values change and whether or not to make them visible.

So I think I see how to approach this (as I just described).

I just need to read up on Data Bindings (which I’m doing now). That seems useful as described by @meredydd and described by @stefano.menci

Thank you for the guidance! :hugs:

1 Like

Hello. Is there a something like a RichText Box, but that accepts user input of Markdown content and renders it as such while being input? (Similar to a Jupyter Notebook Markdown Cellthough this doesn’t involve Jupyter - just using that as an example).

Perhaps this will help?

1 Like

Thank you. Looks interesting. I’ll forego this particular solution right now (I’m already down too many rabbit holes at the moment). For now I’ll stick to stock components. :slight_smile:

What I do in similar situations is provide the input box where they type their marked up content, and a preview box, where they see what it will look like. I found that far easier than trying to get a WYSIWYG editor working the way I wanted.

1 Like

:laughing: This is exactly what I was doing right now: Two boxes vertically stacked in the same card. The top box is a plain TextArea with placeholder instruction to type or paste in their Markdown content (… I also included a URL to a free online Markdown editor - StackEdit); and then immediately below that is the RichText box for grabbing the above content and inserting it there for previewing. Is that basically what you did, too? (Confirming). Thank you.

PS: It requires a bit more event handling (to copy over the text), hiding and unhiding, etc, but at least I understant it.

That’s basically what I did, yes. My upper editing area was a Code Mirror instance with toolbar buttons to make it easier for the content creators (I was having them use bbcode, and the toolbar buttons automatically insert the matching tags), but the concept’s the same.

1 Like

I’m wondering if I should try the RichText box’s change event, or its lost-focus event, or provide a click-to-preview button (last resort because it’s another component to manage). I haven’t yet tried the change event anywhere, so I don’t know if it fires just for an initial character input of a RichText box, or for every character as they type (which would be cool, but could be a performance drag; or maybe not). The latter (fires for each character change) makes more sense.

I was supposed to be working on Data Bindings and Form attribute capturing (i.e. subject of this question) when I ran into not having a Markdown TextArea component available. I’ll get back to that after solving this Markdown preview situation.

EDIT: I’ll just use a separate card component for this markdown composing need with a TextArea, RichText and Button (to click to update the preview). Then I’ll just hide / unhide the entire card. That’s easier.

I used the change event on the editor, and didn’t see any performance impact. It’s all client side, and the content involved wasn’t much more than a page or two of marked up text. The live preview was super convenient.

2 Likes

I have the same setup: a TextArea for the user to edit, its change event updates the content of a RichText and does some validation checks and hides or shows and updates a Label when the text does not follow the correct guidelines.

2 Likes

@jshaffstall @stefano.menci The change event works beautifully and with no lag. Thank you for inputs.

1 Like

Hello!

I finally reached the point where I could use Data Binds, but still have questions after reading up a bit.

For a Form and each Component within it, how do each of their self.item[’ …’] relate to one another. Consider, for example, the tag attribute, which is available for a Form and virtually all Components within it …

If, say, for MyForm as well as for a Card component, a Drop-Down component, and a Nav-Link component within said MyForm, I Data-Bind their tag to self.item[‘foo’] – that is, to the identical dict() key string for each – how are each distinguished from one another? Does each have it’s own self.item[’…’]?

Understanding this will help me understand the data-structure and scope of self.item[…] (or for each self.item[…] if there are multiple in my contrived example) – crucially, so that I don’t clobber data. :sob:

Thank you in advance! :blush:

1 Like

The power of databinding is especially evident with repeating panels.

The very basic idea is:

  • You assign a list of items to a repeating panel: self.rp.items = [...]
  • Anvil uses the template form associated to the repeating panel self.rp to add a form instance per each item of self.rp.items, and assign that item to that form instance’s item.
  • The form instance has its own self.item automatically updated with its own item, whatever that item is

Typically the original list contains dictionary or dictionary like objects, like datatable rows or like any class that you may define as described in the “What to do” paragraph here.

Anvil helps you by pre-populating the expression in the databinding input box with self.item[] and tries to understand what makes sense inside the square brackets. For example, if it knows the type of the item contained in the list assigned to the repeating panel, it will auto complete with whatever keys are available for that dictionary. For example, if the list contains rows of a specific table, the autocompletion will suggest the column names.

Anvil suggests you should enter self.item[something], but that’s a python expression and you can put whatever you like. The only constraint is you shouldn’t use globals from other modules. Something like Globals.parameter_1 will not work. You will need to add self.parameter_1 = Globals.parameter_1 at the beginning of the the form.__init__, then use self.parameter_1 in the databinding.

I sometimes don’t even use self.item. For example, I am working now on a form that has def __init__(self, truck, **properties):, then I use self.truck.number, etc. in the databinding expressions, or self.truck in the databinding of the visible property of some components, so they are only visible if self.truck is defined.


I personally don’t like databinding because it’s just hidden python code, and I don’t like hidden python code. Creating a loop in the __init__ that takes care of assigning the values where they belong would do the same and would be more readable and searchable.

Said that, I use databinding all the times, because it’s so handy and easy to use.

I usd to never use databinding because errors in the code in the databinding expression used to be a nightmare to debug. They would just fail with very little feedback about the reason.

Then the errors started to show up as clickable links on the console (see here), so I started to use databinding.

Then that link stopped working (see here), but the information about the error is still there and it’s very helpful, so I still use databinding. A lot!

2 Likes

Hi @stefano.menci Thank you for the comprehensive answer and examples. I’ll re-read it once I get the hang of the basics. :slight_smile:

Let me isolate my question with something concrete. Let’ say we have this From design:

  • MyForm1 (contains):
    • card_component_enclosing_checkBoxes (contains):
      • chkBox_1DataBind :: checked → self.item[‘is_checked’]
      • chkBox_2DataBind :: checked → self.item[‘is_checked’]
      • chkBox_3DataBind :: checked → self.item[‘is_checked’]
      • chkBox_4DataBind :: checked → self.item[‘is_checked’]
      • chkBox_5DataBind :: checked → self.item[‘is_checked’]

I know there are various options, design-patterns, best-practices to inspect which chkBoxes are and aren’t checked. For instance, (forgetting about DataBinds for a moment – ignore them), this brute-force method can be done:

for component in self.card_component_enclosing_checkBoxes.get_components()
   if component.checked:
     [... execute code for a checked chkBox ...]
   else
     [... execute code for a not-checked chkBox ...]

I understand the above approach well.

So let’s go onto a second approach: DataBinds:

Using my design above, what is the correct expression to get to the self.item of each chkBox, so that I can inspect their respective dict(). Why? I keep getting blank dict() (i.e. { } ) back in my print() debug outputs when I execute print(someExpression); even though I was sure to tick chkBoxes.

What should someExpression be for each chkBox, based on the above design, so I don’t keep getting back: { }?

The fundamentals are what I’m after here because, apparently, I’m incorrectly indexing somewhere in my someExpression. :slight_smile:

Thank you!

There is no respective dict for each checkbox.

The form represents a document (or object or part or whatever).

self.item in the form is a dictionary describing that document.

Each component represents one (or more) property of the document.

For example, if self.item represents a truck:

# example of self.items
{
    'truck_number': '123',
    'is_closed': True,
    'has_shipped': False,
}

# examples of databindings
# databinding for the textbox self.truck_number
text    -> self.item['truck_number']

# databinding for the checkbox self.truck_is_closed
checked -> self.item['is_closed']

# databinding for the checkbox self.truck_has_shipped
visible -> self.item['is_closed']
checked -> self.item['has_shipped']

Or, if you use the object self.truck instead of the dictionary self.item:

# example of object self.truck
self.truck.number = '123'
self.truck.is_closed = True
self.truck.has_shipped = False

# examples of databindings
# databinding for the textbox self.truck_number
text    -> self.truck.truck_number

# databinding for the checkbox self.truck_is_closed
checked -> self.truck.is_closed

# databinding for the checkbox self.truck_has_shipped
visible -> self.truck.is_closed
checked -> self.truck.has_shipped

Then in your code you will have:

if self.truck_is_closed.checked:
    # manage closed truck
else:
    # manage open truck

But, since databinding can be bi-directional, you don’t even need to deal with the components. You can simply do:

if self.item['is_closed']:
    # manage closed truck
else:
    # manage open truck

# or:
if self.truck.is_closed:
    # manage closed truck
else:
    # manage open truck

I usually work with my own classes, not with dictionaries, so all I need to do is:

self.truck.update()

and, since the databinding is bi-directional and the properties have been updated, then the object attributes have been updated, so its update() method can do its job relying on fresh data.

This makes it easy to let the form do the form and my class manage the logic. The form defines the databinding between the input components and the attributes of my object, then calls one method to tell the object to process its new status, and the object does its job. This makes it very easy to test the business logic independently from the UI.

1 Like

This is a great example. I need to wrap my head around and keep re-reading it before moving on with my code.

Before moving on, should the following be: truck.is?

Okay, let me see if I captured what you conceptually explained. Are these correct as I echo them back? …

  1. There’s nothing exclusive about a Form’s self.item[''] dict() and the Data Bindings strategy. It’s simply a convenience provided for you by Anvil out-of-the-box (and in the Designer UI) for that purpose; and it being a dict() instance, was a reasonable choice because you can nest arbitrarily complex data-structures within it). Correct?

  2. If you do use self.item[''] (in lieu of -or- in addition to your own class instances), then you (a) mentally ascribe some Business Object meaning to self.item[''] (like a Truck, IoT device, or Student), and then (b) define appropriate dict() keys for it (i.e., define Business Object attributes), and finally (c) collect their respective values by binding said keys to specific component attributes scattered across your Form. Correct?

  3. In fact, self.item[''] alone may not be enough because you sometimes need to collect values for various Business Object instances. So, you instantiate your own custom Business Objects (as you illustrated), and bind them similarly. And the binding process here is, essentially, no different than for self.item['']. (except see below) Correct?

Are those three understandings Correct? Hopefully, yes.

Follow-up: For Item-3 above – let’s say it’s an IoT class – because the Designer UI has no component for it, the Data Bindings step must be done programmatically, Correct?

Thank you!

UPDATE: self.item appears to start life off as an identifier pointing to to a dict(), but (I think) can be replaced by any other data structure. Sorry @stefano.menci I know you’re typing a reply. Just had to insert that. :slight_smile: