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.
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:
- a project file (projectName.proj.xml) which lists all the screens and DataSources involved in the project
- one or more screen files (screenName.ui.xml) which have
Component XML
screen definitions - 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.
- Use Project > Export menu in the Reify UI to obtain a .zip of your project's files
- 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:
- For all of your DataSources, change the outer tag to <SQLDataSource>
- use the
Admin Console
tool to configure a connection to a SQL database, and create tables from the DataSources - 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:
- 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()); } });
- 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 useCreateScreenSettings.classSubstitutions
to do that:CreateScreenSettings settings = new CreateScreenSettings(); settings.setClassSubstitutions(new HashMap() {{ put("ListGrid", "MyCustomListGrid"); }}); Canvas screen = RPCManager.createScreen("screenName", settings);
.. alternatively, you can useCreateScreenSettings.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);
- 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-validateand 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: