I wasn't aware of Felxjson, but Metawidget and similar projects were certainly an inspiration for the dynamic GUI / object CRUD stuff.
I'm sure many more possibilities will become apparent as the idea is applied to different problem domains. On 4 July 2011 11:55, Superstring Media <[email protected]> wrote: > Some of the applications of your idea seem to be inclusive of functions in > other APIs like Flexjson's Transformers or Metawidget's dynamic UIs. > All really great applications coming from a single enhancement, sounds > excellent indeed. > > http://flexjson.sourceforge.net > http://metawidget.org > > Thom > > > On 2011-07-02, at 4:36 PM, Chris Bartlett wrote: > >> Firstly, my apologies for the length of this email (and if there are >> lots of typos). It might be easier to just jump to the example BXML >> fragments which demonstrate possibilities of the proposed change, and >> then skip back to the more wordy stuff. >> >> I will post some proof of concept code in the morning if there is any >> interest in this. >> >> >> * Notes >> The examples are meant to demonstrate ideas, so might seem a little >> contrived. >> Although Transformables are designed to be triggered by >> BXMLSerializer, they can just as easily be used programmatically. >> The transform() method could be provided with a specific classloader >> by BXMLSerializer if required >> >> >> * References >> http://pivot.apache.org/tutorials/bxml-primer.html >> Specifically the section titled 'Class Instances' >> >> http://svn.apache.org/repos/asf/pivot/trunk/core/src/org/apache/pivot/beans/BXMLSerializer.java >> >> >> * Summary >> Make a backwardly compatible change to BXMLSerializer to enable >> support for a simple 'Transformable<T>' interface. >> >> public interface Transformable<T> { >> public Collection<T> transform(); >> } >> >> The interface would have a single method named 'transform()' which >> would return a java.util.Collection<T>. >> >> A bean implementing the interface would be constructed as normal by >> BXMLSerializer, but would have its 'transform()' method called when >> the bean would usually be returned (if it was the root element) or >> added to the parent object somehow (if it had a parent). >> >> If the bean is the root element, the Collection would be returned as >> the result of the BXMLSerializer.readObject(InputStream) method; >> >> If the bean is not the root element, BXMLSerializer would the iterate >> over the items in the returned Collection and process them in exactly >> the same way as the original bean would have been processed. >> >> i.e. If the original bean was going to be added to a Sequence defined >> by a DefaultProperty, then each of the items in the Collection would >> be added to the same Sequence instead. >> >> i.e. If the original bean was going to be set as the value for a >> writeable property, each of the items in the Collection would be set >> as values for the same writeable property, in the order defined by the >> Collection's iterator) >> >> >> * What difference would that make? >> This simple change would provide flexibility by allowing a special >> type of 'Class Instance' (an implementation of Transformable) to >> decide how many items to return (and of what types) when it is >> 'created', rather than exactly 1 object as currently happens. >> >> >> * How could that help me? >> It essentially turns Transformable objects into macros that can >> perform arbitrary tasks. >> >> >> >> Example 1 - User defined, simplified API for creating object graphs >> >> This is the original problem that lead to the proposed idea. >> Being able to create simplified APIs for Pivot Components means that >> designers could be provided with a much smaller and simpler 'toolkit' >> to work with. Having a smaller API would also be helpful for people >> who add in auto-complete functionality into their editor tools. >> Even just the ability to refer to a Component by a different name can >> be very useful, especially when there are similarly named widgets in >> various UI frameworks. >> >> <!-- This would create and populate a layout without needing to know >> how it was being implemented behind the scenes --> >> <ThreeColumnLayout> >> <leftColumn> >> ... >> </leftColumn> >> <middleColumn> >> ... >> </middleColumn> >> <rightColumn> >> ... >> </rightColumn> >> </ThreeColumnLayout> >> >> >> Here is a more complete example... >> >> TablePaneBuilder implements Transformable<TablePane> and has 'columns' >> and 'components' properties. The latter being a Sequence<Component> >> annotated as the DefaultProperty. Its 'transform()' method returns a >> newly created TablePane with the requested number of columns, and >> sufficient rows to populate the cells using the supplied Components. >> >> @DefaultProperty("components") >> public class TablePaneBuilder implements Transformable<TablePane> { >> >> private final List<Component> components = new ArrayList<Component>(); >> private int columns = 1; >> >> public Sequence<Component> getComponents() { >> return components; >> } >> >> public int getColumns() { >> return columns; >> } >> public void setColumns(int columns) { >> if (columns < 1) { >> throw new IllegalArgumentException(); >> } >> this.columns = columns; >> } >> >> @Override >> public Collection<TablePane> transform() { >> final TablePane tablePane = new TablePane(); >> for (int i = 0; i < columns; i++) { >> tablePane.getColumns().add(new Column()); >> } >> TablePane.Row row = null; >> for (Component component : components) { >> if (row == null || row.getLength() == columns) { >> row = new TablePane.Row(); >> tablePane.getRows().add(row); >> } >> row.add(component); >> } >> return Arrays.asList(tablePane); >> } >> } >> >> Usage in BXML >> <TablePaneBuilder columns="2" > >> <!-- Row 1 --> >> <wtk:Label text="Label 1" /> >> <wtk:Label text="Label 2" /> >> >> <!-- Row 2 --> >> <wtk:PushButton buttonData="PushButton 1" /> >> <wtk:PushButton buttonData="PushButton 2" /> >> >> <!-- Row 3 --> >> <wtk:TextInput text="TextInput 1" /> >> <wtk:TextInput text="TextInput 2" /> >> </TablePaneBuilder> >> >> >> >> Example 2 - Ability to instantiate classes which do not have a no-arg >> constructor >> >> public final class UnfriendlyPOJO { >> public final int mandatory; >> public UnfriendlyPOJO(int mandatory) { >> this.mandatory = mandatory; >> } >> } >> >> public class UnfriendlyPOJOBuilder implements Transformable<UnfriendlyPOJO> { >> private int mandatory; >> public int getMandatory() { >> return mandatory; >> } >> public void setMandatory(int mandatory) { >> this.mandatory = mandatory; >> } >> >> @Override >> public Collection<UnfriendlyPOJO> transform() { >> return Arrays.asList(new UnfriendlyPOJO(mandatory)); >> } >> } >> >> >> <UnfriendlyPOJOBuilder mandatory="99" /> >> >> >> >> Example 3 - Can be used to hide attribute ordering restrictions >> >> This BXML will work, as the selectedIndex is being set once the >> ListView has data >> <wtk:ListView listData="['One', 'Two', 'Three']" selectedIndex="0"/> >> >> But this BXML will fail, as the selectedIndex is being set before the >> ListView has data >> <wtk:ListView selectedIndex="0" listData="['One', 'Two', 'Three']"/> >> >> >> @DefaultProperty("listView") >> public class ListViewBuilder implements Transformable<ListView> { >> private final ListView listView = new ListView(); >> private int selectedIndex; >> private List<?> listData; >> >> public int getSelectedIndex() { >> return selectedIndex; >> } >> public void setSelectedIndex(int selectedIndex) { >> this.selectedIndex = selectedIndex; >> } >> >> public void setListData(String listData) { >> try { >> this.listData = JSONSerializer.parseList(listData); >> } catch (SerializationException exception) { >> throw new IllegalArgumentException(exception); >> } >> } >> >> @Override >> public Collection<ListView> transform() { >> listView.setListData(listData); >> // Ensure that the index selection happens after the list population >> listView.setSelectedIndex(selectedIndex); >> return Arrays.asList(listView); >> } >> } >> >> <ListViewBuilder selectedIndex="0" listData="['One', 'Two', 'Three']" /> >> >> >> >> Example 4 - Provide similar functionality to named styles only for properties >> >> The same could essentially be achieved by subclassing a Component and >> hard coding some properties into its constructor. >> Using a Transformable is another option, and could also be used with >> final classes. >> >> @DefaultProperty("textInput") >> public class PasswordInput implements Transformable<TextInput> { >> private TextInput textInput = null; >> public void setTextInput(TextInput textInput) { >> this.textInput = textInput; >> } >> >> @Override >> public Collection<TextInput> transform() { >> if (textInput == null) { >> textInput = new TextInput(); >> } >> textInput.setPassword(true); >> textInput.setPrompt("Enter password"); >> return Arrays.asList(textInput); >> } >> } >> >> <!-- Creates and returns a new TextInput --> >> <PasswordInput /> >> <!-- Uses the supplied TextInput --> >> <PasswordInput> >> <wtk:TextInput tooltipText="Using supplied TextInput" text="secret" /> >> </PasswordInput> >> >> >> This idea can be taken further by a Transformable that 'clones' >> specified objects (0..n times). >> It would create new instances based on the supplied source objects, >> and then iterate over the source's properties and styles while setting >> the same values on the newly created 'clone'. (I knocked up a working >> example of this in about 20 mins) >> >> >> >> Example 5 - Transformable that returns an empty Collection >> >> This could be used to conditionally include sections of BXML file >> based on any arbitrary logic. >> (Result of a webquery, result of a JVM scripting function, JVM >> version, availability of classes on the classpath, Operating System, >> user name etc) >> If the critera is met, the Transformable would return a Collection >> containing the BXML, otherwise just an empty Collection. >> >> <WindowsOS> >> <WindowsWidget /> >> <bxml:reference id="$commonToAllOperatingSystems"/> >> ... >> </WindowsOS> >> <MacOS> >> <MacWidget /> >> <bxml:reference id="$commonToAllOperatingSystems"/> >> ... >> </MacOS> >> >> >> >> Example 6 - Enhanced BXML include >> >> <!-- Include all files that match the regular expression or wildcard >> syntax, retrieving them asynchronously --> >> <Include async="true" regex="/path/to/bxml/files/widget[5-7].bxml" /> >> >> or even >> >> <Async> >> <Include regex="/path/to/bxml/files/widget[5-7].bxml" /> >> </Async> >> >> The Async would queue a callback to populate the same position with >> data once it had been retrieved from a potentially slow source. >> (This is just an example of the sorts of options which are made >> available by this change - not a proposal to create such a new >> 'Includer') >> >> >> >> Example 7 - Dynamic UI creation (think automatic CRUD forms for a POJO/bean) >> >> <!-- Show a 'person' bean in a form --> >> <FormBuilder editable="true" source="$person" fieldOrder="['id', >> 'title', 'firstName', 'lastName', 'age']" /> >> or >> <!-- Show a 'person' bean in a name/value TableView where the values >> are editable --> >> <TableViewBuilder editable="true" source="$person" fieldOrder="['id', >> 'title', 'firstName', 'lastName', 'age']" /> >> >> (These examples are for generic XXXBuilders, but they could obviously >> have internal knowledge of certain types.) >> >> >> >> Example 8 - Reuseable and sharable >> >> Due to the simplicity of the interface, Transformables can easily be >> shared among Pivot developers. >> This could lead to an online repository of reuseable components (even >> just wiki pages of source). >> >> There have been many mailing list questions about how to achieve >> particular layouts. >> Common layouts could be implemented as Transformables and mailed to >> the list for easy reuse. >> >> (I suppose Transformables could be considered similar to a JSP tag library) >> >> >> Chris > >
