« I Love You Wakanda…but I'm Breaking Up | Main | Coding Session #17: Coding is !@#$ Messy »

August 18, 2013

Multiple Instances of Forms

by Adrian McGilly
http://www.mcgilly.com/

Note: Guest post! If multiple window workflows seem like more work than they are worth—then this is the article for you. - David

Let's say you want to open multiple instances of the same form, the way Outlook lets you open as many Contacts or Appointments or Emails as you want. By default, Servoy can only display one runtime instance of a form at a time, but you can clone a form as often as you want and display the clones simultaneously in as many windows or tabpanels as you want. But what if your form has tabpanels? If all you do is make a clone of the main (top-level) form, but not the forms in its tabpanels (which I will call subforms) then when you try to display your original form and your clone at the same time, you'll find the tabpanels won't work in both forms, only in the most recently opened form. To get both forms to work, you would need to clone the subforms (and sub-subforms, etc.) and programmatically "place" the cloned subforms in the correct tabpanels of the clone. And even then you will still run into problems with any code that references any of the cloned subforms by name, because the cloned subforms will all have new names.

So how can you make your solution support multiple instances of forms that include tabpanels? This article shows you one technique for achieving this.

Terminology

For the purposes of this article we will call a form in a tabpanel a 'subform'. We will call the form that contains the tabpanel the 'parent', and we will call any top-level form and all of its subforms (including sub-subforms, etc.) a 'formset'.

3-windows

How to Support Multiple Instances of Forms that have Tabpanels

There are two pieces to solving this puzzle:

  • First we need a method that clones a form including all subforms.
  • Second, we need a technique that ensures that code within the formset that refers to cloned subforms continues to work properly even though the cloned subforms will have new names.

Step 1: Creating the Clones

The method below called createFormClone() will create a clone of an entire formset (i.e. a form and all its subforms). You give it the name of the top-level form you want to clone (formName) and the name you want to give to the clone (cloneName), and it creates the clone for you, inlcuding all subforms, no matter how many layers of embedded tabpanels you have on your form. It also returns the clone as a JSform object.

During this cloning process, the cloned main form and all of its subforms will be given new names. This is necessary because Servoy doesn't let you have two forms with the same name in the same solution. The new name generated by this method is the original name + underscore + a random number.

Here's an example. We start with a form called customers with a tabpanel on it called tabs. The tabpanel contains two tabs named: contact_tabs and orders_tab. The Contacts tab contains a form called contacts and the Orders tab contains a form called orders. Here is the form structure, showing in italics the name property of the forms, tabpanels and tabs involved:

Form: customers
	Tabpanel: tabs
		Tab 1: contacts_tab
			Form: contacts
		Tab 2 :orders_tab 
			Form: orders

Now we create a clone of that form by making the following call:

This will create a clone of the formset. It will name the top level form "customers_clone" but the clones of its two subforms will receive new names. The cloned formset will look like this:

Form: customers_clone
	Tabpanel: tabs
		Tab 1: contacts_tab
			Form: contacts_39986712
		Tab 2 :orders_tab 
			Form: orders_39986712

As you see, the method generated new names for the cloned subforms by appending a random number to the end of the original form names.

Here is a code sample showing how you would use this method to clone a form called customers twice, and then open up the original form and the two clones each in their own window:

This is a good start. But the fact that we now have new forms with new names presents some challenges. I'm going to assume that the formset respects basic principles of encapsulation, and that there are no direct references to any of the subforms coming from outside the formset. But even then, any code within the formset that references any form in the formset by name is now broken because it's going to reference the wrong form. For example suppose in the original customers fom there's a method that does any of these things:

In order to work correctly in the customers_clone1 form we don't want that code to act on the orders form, we would want it to act on orders_39986712.

We also have a problem if code in one of the cloned subforms references its parent form by name. For example if a method on the orders subform does this:

then when that method fires in the orders_39986712 clone, we want it to referencing the correct parent form customers_clone, not the original customers form.

So we need a way to maintain in these clones the concept of code encapsulation throught the cloned formset.

Step 2: Maintain Code Encapsulation Within Cloned Formsets

We need a way for code within the formset to refer to other forms in the formset without relying on their names. This could be code attached to a parent form referring to a subform (or sub-subform, etc). It could also be code in subforms refering to a parent (or grandparent, etc.) form. We'll take these two scenarios one at a time.

Step 2.1 : how to write cloneable code that refers to subforms

One thing that does NOT change in the clones is the names of the tabpanels and tabs. So if we had a generic way for forms to reference their subforms while relying only on the names of their enclosing tabpanels and tabs, then that code would continue to work in the clones. So what I've created, with the help of Patrick Talbot (Servoy power-developer and creator of ServoyForge.net), is a method called getTabForm() which references a form based on the names of the tabpanel and tab that contain it.

I'll show you some examples of how to use this method but first let's deal with our other scenario:

Step 2.2 : how to write cloneable code that refers to parent forms

We need a way for subforms to refer to their parents without relying on the parent form's name. Servoy's built-in controller class method controller.getFormContext() makes that easy. Here is a form method called getParentForm() which returns the JSForm object of the parent of the current form (i.e. the parent of the form to which the getParentForm() method is attached).

Step 2.3 : enclose these methods in a superform

Finally, make these methods easily available to all our forms by creating an extendable 'class'. Create a form called MWBase (short for MultiWindowBase) and attach these methods to that form. Then as you create new forms, make them extend the MWBase form. Remember to do this not only with the top-level form but with any subform within that top-level form. Presto, your formsets are now easily cloneable and 'multi-window' capable.

Examples

So now let's see how we would rewrite the code examples I provided above, which would fail in the cloned forms, so that they work correctly. For these examples we are assuming the same form structure as before, so here it is again:

Form: customers
	Tabpanel: tabs
			Tab 1: contacts_tab
						Form: contacts
			Tab 2 :orders_tab 
						Form: orders

Where before you did this:

now you would now do this:

Where before you did this:

now you would now do this:

Where before you did this:

now you would now do this:

Where before you did this:

now you would now do this:

If you need to refer to form that is embedded two tabpanels deep you can do that too. So let's say our original form structure was more complex, like this:

Form: customers
	Tabpanel: tabs
		Tab 1: contacts_tab
			Form: contacts
		Tab2 :orders_tab 
			Form: orders
				Tabpanel: order_details_tabs
					Tab 1: order_items_tab
						Form: order_items
					Tab 2: shipping_info_tab
						Form: shipping_info

And let's say that you need a method on the customers form to set the shipping_info form to read only. You could do this:

or, to make it a little more readable, you could do this:

Conversely, if from the shipping_info form you need to set the readOnly property of its parent form, i.e. the orders form, you could do this:

and if you needed to reference the readOnly propperty of its grandparent form, i.e. the customers form then you could do this:

Conclusion

I hope these techniques open some doors for developers wanting to support multiple instances of forms with tabpanels. If you need assistance implementing or expanding on these techniques don't hesitate to contact me.

Posted by David Workman on August 18, 2013 at 10:53 PM in Articles | Permalink

Comments

The comments to this entry are closed.