Interface ReifyForDevelopers


public interface ReifyForDevelopers

Reify for Developers

What is Reify?

Reify is a web-based visual application builder that enables non-developers to build Smart GWT screens, and even build complete applications.

Reify creates standard Smart GWT Component XML screen definitions, DataSource definitions, and XML event handler and workflow declarations. This means you can take a Reify application and extend it with standard Smart GWT coding techniques.

You can also use multiple Reify projects as part of a single, larger application. This approach - called hybrid development - lets non-programmers build the simpler parts of your application in a visual tool, while developers build other parts with code, as usual.

Why Hybrid Development works

Normally, it's not realistic for a developer to maintain or extend code that was generated by a visual tool. However, Reify's hybrid development model actually works, because:

1. Reify generates 100% declarative XML, never procedural code
No programmer would ever want to try to maintain or extend code generated by a visual tool. Reify is different: everything Reify produces is concise XML declarations, and is just standard use of the Smart GWT framework. You can look up any setting you see used in a Reify-generated screen in the Smart GWT documentation. We've also put a great deal of effort into ensuring that the XML declarations produced by Reify are concise and self-explanatory.
2. DataSources define a clean and clear boundary between UI and backend concerns
Within the Reify visual tool, designers work with MockDataSources, which have the same fields and behavior as real DataSources, but use sample data. Because Smart GWT's UI components know nothing about the actual implementation of a DataSource's operations, the MockDataSources can be replaced with real DataSources, and the screen works the same.
3. Reify screens are encapsulated and reusable
Screens have no global variables, and all the behavior of the screen is confined to within the screen. So you don't need to worry about naive designers breaking some other part of your application: DataSource operations are the only global interaction. And, you can create the same screen more than once, even concurrently, so designers can build things like a tab that appears multiple times with different data.
The Hybrid Development workflow

With hybrid development, non-programmers (such as product managers) use Reify to design screens, sometimes enlisting the help of developers for more complex tasks, and then developers incorporate those screens into a standard Smart GWT application.

If you have an existing Smart GWT application, you can quickly get your existing DataSources into Reify so that designers start out with DataSource definitions that already match the production design (see "Uploading existing DataSources to Reify" below - we provide a special tool).

While designers work on the UI, developers can get to work immediately on backend concerns, or on screens that are too complex to build visually. As designers begin to produce screens, if those screens will require some hand-coded aspects, developers can work on those aspects in parallel, even while the designer is further refining or extending the screen within Reify (see the sections on adding custom behavior below).

Hybrid development creates a far more efficient development process:

  • Simpler screens and simpler applications don't require developers to build
  • Far less room for miscommunication, since designers build interactive screens instead of throw-away mockups
  • End users can try out the design without needing developers to build the whole application first (a.k.a. User Acceptance Testing)
  • Applications are easier to maintain, since the simpler screens remain editable in the Reify visual tool

In addition, because it's always possible to export your Reify projects and keep going in an IDE environment, you can start any kind of project with Reify - you don't have to limit your use of Reify to only projects that can be built completely in a visual tool.

Loading Reify Projects

An Reify project consists of:

  1. a project file (projectName.proj.xml) which lists all the screens and DataSources involved in the project
  2. one or more screen files (screenName.ui.xml) which have Component XML screen definitions
  3. one or more DataSource files (dataSourceId.ds.xml) which have DataSource definitions

You can load a Reify project from Reify.com (or from a Reify Onsite server) with just a call to Reify.loadProject(), like so:

  
  LoadProjectSettings settings = new LoadProjectSettings();  
  settings.setUserName(reifyUserName);  
  settings.setPassword(reifyPassword); 
  Reify.loadProject(projectName, new LoadProjectCallback() {  
     @Override
     public void execute(Project project, Project[] projects, RPCResponse rpcResponse) {
         Canvas screen = project.createScreen((project.getScreenNames()[0]));  
         // screen is now a Canvas - not drawn - put it somewhere via addMember() / addChild() / whatever 
     }
  }, settings); 
  
  
This is the quickest way to try out a Reify screen when you are working in a standard Smart GWT project, and if you later download the Reify project and add its files to your Smart GWT project, you just change from Reify.loadProject() to RPCManager.loadProject() (although you can also deploy with live loading of projects - see Live Editable Applications below).

If the Smart GWT page where you load your project already has DataSources with the same IDs as the MockDataSources defined in the Reify project, the DataSources in the page will be used instead, and the MockDataSources ignored. In this way, you can easily test out a Reify screen working with real DataSources, even as it continues to be refined in the Reify visual tool, using equivalent MockDataSources. You can turn on automatic verification of loaded DataSources, to make sure they match your local DataSources (no fields have been removed, for example) - see the Verifying Screens sample for an example of doing this.

If the Reify screen has MockDataSources that don't exist in the local page, those will be used, where they will behave like clientOnly DataSources, mimicking a real DataSource but using using sample data embedded in the MockDataSource file itself, with all changes lost on page reload.

If you don't want to use live loading, you can alternatively:

  1. Use Project > Export menu in the Reify UI to obtain a .zip of your project's files
  2. Use special Maven commands to pull project files live from a Reify server into any Maven-managed project

Note finally that if your project has multiple screens and you only want to load part of it, you can of course use the screen (.ui.xml) and DataSource (.ds.xml) files from your project on their own; they do not depend on the project file (.proj.xml).

Deploying a Reify Project Separately

If you've built a complete application in Reify, and for whatever reason you have decided to deploy on your own, just change the project to use SQL storage:

  1. For all of your DataSources, change the outer tag to <SQLDataSource>
  2. use the Admin Console tool to configure a connection to a SQL database, and create tables from the DataSources
  3. load the project via adding a <script> tag that uses the ProjectLoaderServlet to load your project

.. and your project is now ready to deploy.

Hybrid development style: mixing Reify projects and hand-coding

It's easy to use Reify for just the simpler parts of a complex application. For example, the start screen of a complex application might need to be hand-coded, but from there, other screens might be Reify projects or hand-coded, in any mix. Even a hand-coded screen might use a Reify project for a pop-up dialog or wizard. All that's necessary to do this is to load the Reify project into your existing application, and use RPCManager.createScreen() at the point where you want to introduce Reify-created screens.

Remember that while your Reify project is built and tested as a full-screen application, there's no requirement that it takes over the screen when embedded into a larger application. For example, say you have a hand-coded application where the main screen consists of a SplitPane with a tree on the left, which controls what components are shown in the detailPane on the right. You've loaded a Reify project with a screen called "leadDetails". If you wanted to place a Reify-created screen in just the right pane, you can just do this:

      mySplitPane.setDetailPane(RPCManager.createScreen("leadDetails"));
  
Similarly, imagine you are building a modal window in Reify, such as a wizard or a pop-up dialog. There's no reason for the Window component itself to appear in the Reify project, since you can just do this:
    
     Window myWindow = new Window();
     myWindow.addItem(RPCManager.createScreen("reifyScreenName"));
     ... other properties ...
  
Leaving the Window component out of the Reify project gives you a little more room to work in the editor, and may be a more natural split in your application. Alternatively, you may want the Window in the Reify project so that previewers see something closer to the actual appearance.

Either approach is fine; the key point is that in the Hybrid Development approach, you can place the boundary between your Reify screens and your custom code anywhere you want. You can use a Reify screen for the contents of a window, or a tab, or even just the lower half of a tab, if there is a hand-coded component that needs to appear up top. This makes it possible to use Reify for a much greater proportion of your application than may at first be obvious.

Hybrid Development: adding custom behavior while leaving Reify resources unchanged

If you leave the files retrieved from Reify unchanged, you can continue to modify them visually and collaboratively in the Reify tool, and pull updates into your project at any time. Smart GWT and Reify give you multiple ways to add custom behavior to a Reify screen without changing the original screen XML:

  1. you can add event handlers to components in a loaded screen
        
      ListGrid mainGrid = (ListGrid)RPCManager.createScreen("screenName");
      mainGrid.addRecordClickHandler(new RecordClickHandler() {
          @Override
          public void onRecordClick(RecordClickEvent event) {
              myApp.loadRelatedImages(event.getRecord());
          }
      });
      

  2. you can swap in custom subclasses for standard components. For example, say you have a custom subclass of ListGrid that you want to use everywhere in your application - you can use CreateScreenSettings.classSubstitutions to do that:

      CreateScreenSettings settings = new CreateScreenSettings();
      settings.setClassSubstitutions(new HashMap() {{ put("ListGrid", "MyCustomListGrid"); }});
      Canvas screen = RPCManager.createScreen("screenName", settings);
      
    .. alternatively, you can use CreateScreenSettings.componentSubstitutions to change the classes used for individual components by the component's ID:

      CreateScreenSettings settings = new CreateScreenSettings();
      settings.setComponentSubstitutions(new HashMap() {{ put("mainGrid", myCustomListGrid); }});
      Canvas screen = RPCManager.createScreen("screenName", settings);
      
  3. you can also inject components into the loaded screen, such as a custom component you've written

      Canvas screen = RPCManager.createScreen("screenName");
      Layout mainLayout = (Layout)screen.getByLocalId("mainLayout");
      mainLayout.addMember(customDisplayComponent);
      

Using the above techniques, you can keep large portions of even a complex application in declarative, Reify-editable files. This makes it much easier to make changes, and collaborate and iterate on possible designs.

You can easily verify whether resources loaded from Reify have been changed to be incompatible with your custom logic - just use LoadProjectSettings.verifyComponents to declare the local IDs and types of the components you expect to be present in the screen, and a very clear error will be reported if someone changes a screen in an incompatible fashion. See Verifying Screens sample for an example of doing this.

Uploading existing DataSources to Reify

If you have existing DataSources in hand-coded applications, the quickest way to get them into Reify is to go to the Admin or Developer Console and do a Reify format export from the DataSources tab. First open a section for the DataSource and then set the export format picker to "Reify DataSource upload format". Finally, within Reify, you can create a new DataSource by importing that XML in the DS Wizard.

You can also export multiple DataSources at once by clicking on the "Reify Export" button at the top of the DataSource tab in the DataSource List section header. This special syntax consists of multiple DataSources each in "Reify format" XML. If you need a programmatic solution, you can generate the same output with the Reify.getMockDS() and Reify.showMockDS() APIs, which allow you to provide criteria to get a specific set of sample data, in case that's important.

If you give the DataSource you create in Reify the same ID as the real DataSource in your project, then when the Reify project is exported and added to an existing hand-coded project, the real DataSource will automatically take the place of the MockDataSource you create in Reify.

Note that Reify does not offer direct upload of existing .ds.xml files, because of the security implications of features such as <customSQL> and Server\n Scripting. Even aside from security concerns, an existing .ds.xml can have dependencies on other parts of your environment, such as custom SimpleTypes, validators that invoke server Java code, or custom tags.

When you export to Reify-format XML and import as a MockDataSource, Reify automatically determines field types, including distinguishing between "time", "date", and "datetime" fields, as well as between "int" and "float" fields, so the MockDataSource is a perfect stand-in for your real DataSource for design purposes. Standard validators are included in the exported field definitions by default.

Consider trimming the sample data down to 50-100 rows before creating the MockDataSource. Any additional data won't do anything other than slightly slow down the tool, as sample data in Reify is stored inside the .ds.xml file and not in a SQL database or other production-capable data store. If you export in Reify format from the DataSources tab, the logic there automatically chooses a low data volume and intelligently selects records so as to preserve relations. For further details, seen Reify.getMockDS(), the API on which the DataSources tab's "Reify Export" button is built.

Providing initial data to Reify screens

Because Reify-created portions of your application use the same DataSources as the rest of your application, they already have access to the same data. However, typically there is some context that the screen needs to do its job, such as the ID of the record the screen is intended to display or edit.

It's always possible to simply reach into the Reify screen after you've created it, and populate components with the necessary data. For example, if you create a Reify screen for editing an Order and its OrderItems, you could do something like this:

   Canvas myScreen = RPCManager.createScreen("reifyScreenName");
   myScreen.getByLocalId("headerLabel").setContents("Order #" + orderId);
   Criteria criteria = new Criteria();
   criteria.addCriteria("orderId", orderId);
   ((DynamicForm)myScreen.getByLocalId("orderForm")).fetchData(criteria);
   ((ListGrid)myScreen.getByLocalId("orderItemsGrid")).fetchData(criteria);
  

This works, but has some drawbacks:

  • it creates dependencies on specific component IDs within the screen
  • the screen can't easily be tested on its own - it needs the surrounding application to perform a full test
  • if additional components are added to the screen that also consume the same data, more code may need to be added to populate those components

Instead, a Reify screen can define screen inputs, which are data values that are expected to be provided from outside of the screen. When a screen has such inputs, you provide them via dataContext, like so:

   CreateScreenSettings globals = new CreateScreenSettings();
   DataContext dataContext = new DataContext();
   dataContext.addMapping("Order", orderRecord);
   globals.setDataContext(dataContext);
   Canvas myScreen = RPCManager.createScreen("reifyScreenName", globals);
  

This approach addresses all of the potential issues above, creating a clean & clear boundary between the Reify screen and the surrounding application.

dataContext doesn't have to be limited to just DataSource records; in addition to required data, a Reify screen may have settings that are allowed. For example, there might be a setting for whether the screen allows editing, or allows editing of specific fields. A Reify screen can just declare such settings as a DataSource, and set it as a screen input, and then code using the screen can pass settings via dataContext as well, for example:

   CreateScreenSettings globals = new CreateScreenSettings();
   DataContext dataContext = new DataContext();
   dataContext.addMapping("Order", orderRecord);
   dataContext.setAttribute("orderScreenConfiguration", 
           new HashMap() {{
               put("allowEditing", true);
               put("allowShipDateChanges", false);
           }} );
   globals.setDataContext(dataContext);
   Canvas myScreen = RPCManager.createScreen("reifyScreenName", globals);
  
Here, possible settings for the screen have been captured as a DataSource called orderScreenConfiguration (any name can be used).

Note that in this particular example, whether certain types of editing are allowed is controlled via external configuration, however, Reify screens can be dynamic in lots of ways: role-based access to specific operations or fields, mobile adaptation, or even data-driven behavior such as not allowing editing of an order that already has status:"shipped". Because of this, external configuration via dataContext is unusual, and you should carefully consider whether it is really necessary, as compared to just using the standard Reify environment to control screen behavior.

To see Screen Inputs and dataContext in action, take a look at the Screen Inputs sample.

Another pattern, called shuttle DataSources, can also be used to pass data to Reify screens - see the discussion towards the end of this document.

Detecting that a Reify screen is done

For many if not most scenarios of embedding Reify screens in a larger application, the user simply completes a task on the Reify-created screen and then navigates away, and may navigate back later and complete more tasks, so nothing special needs to be done. However, sometimes you need to know when a user has completed interacting with a Reify-created screen (such as a wizard), so that the containing application code can take the next step.

There are a few simple techniques for doing this:

  • watch for an event on some object in the Reify screen - for example, wait for the click event on a "Done" button
  • watch for Reify screen to hide itself, if that's what it does on completion. You can do this by adding a VisibilityChangedHandler
  • watch for a successful DataSource operation by adding a DataSourceDataChangedHandler
  • have the Reify screen write to a clientOnly DataSource when it completes. This is the same as the technique described above for providing initial data to a Reify screen, but in reverse. This is also useful if the Reify screen needs to pass data back to the main application, and you'd prefer not to retrieve that data by simply interrogating components in the Reify screen

Live Editable Applications

If, in your deployed application, you use Reify.loadProject() calls to load parts of your application live from Reify.com or a Reify OnSite server, that means that people can use Reify to edit the live application. This is extremely powerful, as it allows you to instantly respond to rapidly changing requirements (such as needing to add additional validation to a field). However, it's obviously also possible to break the live application this way, so use with caution.

SmartGWT provides tooling to detect potential mistakes with DataSources and Components on the client at runtime, and at build-time through Maven goals, Ant tasks, or a Java CLI. For best results, Isomorphic recommends using both approaches.

By default, both Maven and Ant import utilities automatically check for common discrepancies on import, but this step may also be run independently at any time using reify-validate goal or ValidateTask tasks, respectively. For Maven, that could be as simple as something like this:

  <plugin>
      <groupId>com.isomorphic</groupId>
      <artifactId>isc-maven-plugin</artifactId>
      <version>1.4.3</version>
      <configuration>
        <dataSourcesDir>WEB-INF/ds/classic-models</dataSourcesDir>
      </configuration>
      <dependencies>
         <dependency>
             <groupId>com.isomorphic.extras</groupId>
             <artifactId>isomorphic-m2pluginextras</artifactId>
             <version>\${smartgwt.version}</version>
         </dependency>       
    </dependencies>
  </plugin> 
  
  mvn com.isomorphic:isc-maven-plugin:1.4.3:reify-validate
  
and for Ant:
  <target name="reify-validate" depends="reify-tasklibs">
      
      <taskdef name="reify-validate"
               classname="com.isomorphic.maven.mojo.reify.ValidateTask"
               classpathref="reify.classpath"/>
      <reify-validate datasourcesDir="WEB-INF/ds/classic-models" 
                      smartclientRuntimeDir="\${basedir}/war/isomorphic" />
  </target>   
  
  ant reify-validate
  

If, for any reason, one wanted access to the same feature outside of either Ant or Maven environments, a Java class is provided for invocation from command line, scripts, etc. Note that in this case, however, the classpath would need manual setup to include the isomorphic_m2pluginextras JAR and its dependencies, and you'll need to provide the full path to your application resources. Assuming the required JARs could all be found at tools/reify/lib, that might look something like this:

  java -cp :tools/reify/lib/* com.isomorphic.tools.ReifyDataSourceValidator 
       -r /Users/you/dev/your-application/war/isomorphic
       -d /Users/you/dev/your-application/war/WEB-INF/ds/classic-models
       -m /Users/you/dev/your-application/war/WEB-INF/ds/mock
  

Shuttle DataSources

Reify Screen Inputs and dataContext, covered above, is the recommended approach for providing data to a Reify screen. However, another pattern is to use shuttle DataSources.

Similar to the example given for dataContext above, where a special DataSource is created to represent settings for the screen, a designer using Reify creates a DataSource to represent the required input data for the screen, called a shuttle DataSource. That shuttle DataSource has exactly one record, which the Reify screen fetches at startup (typically using the drawn event) and uses to populate components and/or configure the screen.

You may find that a particular designer has built screens in this style rather than using Screen Inputs and dataContext. If so, it's easy to provide data to such a screen: you just create a single-record, clientOnly DataSource, and provide the input data for the screen as that DataSource's DataSource.cacheData.

For example, a Reify screen may be designed to load data related to a selected record from the "customer" DataSource. In the Reify project, a MockDataSource called "selectedCustomer" was created to represent the "customer" record whose data should be loaded. In your code that needs to create the Reify screen, you've got a variable currentCustomer that has the Record for the currently selected customer. To make the data available to the Reify project, you can just do this:

   DataSource selectedCustomer = new DataSource();
   selectedCustomer.setID("selectedCustomer");
   selectedCustomer.setInheritsFrom("customer");
   selectedCustomer.setClientOnly(true);
   selectedCustomer.setCacheData(currentCustomer);
   Canvas myScreen = RPCManager.createScreen("reifyScreenName");
   myScreen.draw();
  

Now the Reify screen can pull the data about the selected "customer" record from the "selectedCustomer" DataSource. The use of DataSource.inheritsFrom above helps to avoid duplicating field definitions, assuming the designer created their "selectedCustomer" DataSource as a field-compatible duplicate of the "customer" DataSource.

If the Reify screen is to be created multiple times for different customers, just use DataSource.setCacheData() to update the data in the selectedCustomer client-only DataSource, immediately before creating another instance of the screen

The shuttle DataSource pattern is a little worse than the use of Screen Inputs plus dataContext documented above:

  • you have a duplicate DataSource definition within Reify (selectedCustomer above)
  • instead of having components automatically populated with data from the dataContext, the Reify designer must do so manually, using a Workflow

Therefore, you should generally use the Screen Inputs pattern rather than shuttle DataSource, however, shuttle DataSources may still make sense in some edge cases.

See Also: