pui.BasicWidget and Bidirectional Custom Properties
Introduction
Profound UI Version 6 Fix Pack 12.0 adds a feature allowing custom widgets to fully and more easily integrate with Profound UI. Consequently, custom or 3rd party widgets from other frameworks can be made to work with Profound UI and be treated like native Profound UI widgets:
input data from custom widgets submits to the server-side program just like data from Profound UI widgets
the widgets can be rendered inside grids, and their values get sent back to the server-side program as subfile data
most Profound UI Widget APIs work with them.
Additionally, custom properties for custom widgets can be configured as bidirectional–that is, both input and output. In the past, only a custom widget's "value" property could be used for input. Now, any custom property can be use for input. Additionally, multiple properties can be used for input on a single custom widget.
The new pui.BasicWidget class simplifies the task of creating custom widgets without sacrificing any flexibility.
To use the new bidirectional properties in custom widgets, there are two API that now accept special arguments:
pui.widgets.add - the "constr", "render", and "getValue" options are added for pui.BasicWidget. The "render" and "getValue" options for pui.widgets.add may be sufficient for most needs. For more advanced use, the constructor of a subclass of pui.BasicWidget can be specified for "constr".
pui.addCustomProperty - the "bidirectional" and "showDesignValue" options are added for pui.BasicWidget
pui.BasicWidget
pui.BasicWidget is a class defined in Profound UI that integrates with the Profound UI framework allowing input and output and point-and-click design. pui.BasicWidget is designed for Object-Oriented-Programming so that code can be encapsulated and inherited for reuse. Custom widgets that subclass pui.BasicWidget can render not just in runtime, but in Visual and Node Designers for visual, WYSIWYG screen design.
When widgets are added to Profound UI using the "render" and "getValue" options to pui.widgets.add, the widgets automatically become instances of pui.BasicWidget. The render and getValue functions get assigned and bound to the pui.BasicWidget class so that the special word, "this", refers to the class instance.
In the call to pui.widgets.add there is no need to define a "propertySetters" option or a "globalPropertySetter" function when using pui.BasicWidget. In fact, pui.BasicWidget assigns its own "globalPropertySetter" in order to handle when Profound UI changes the widget by setting properties.
When a pui.BasicWidget is constructed a reference to it is placed in the DOM as a "widget" property inside the "pui" object. For example, you could reach a pui.BasicWidget by doing something like: var myWidget = getObj("MyWidgetId1").pui.widget;
Methods
These methods can be defined or overridden in child classes. Code that exists outside the pui.BasicWidget class should not normally call these functions directly, excepting "render". Instead, use Profound UI widget APIs to change property values when necessary.
render (required) - A function that Profound UI calls after all of the widget properties are set. Either pui.BasicWidget subclasses must define "render", or the "render" supplied to the call to pui.widgets.add must define it. If custom code calls the API, applyProperty to change a pui.BasicWidget property, then "render" should also be called to refresh the widget. Do not use applyProperty(..., "field type", ...) to redraw the widget, because calling "field type" will destroy and re-create the widget.
getValue (required) - A function that Profound UI calls to get the current value of a property. Argument: a string, property name; e.g. "value", "user defined data", "label". If this function is not defined in subclasses or in the call to pui.widgets.add, then the default behavior is: return evalProperty(propname); This function should extract the necessary information from a custom widget's DOM and return it as a string. The returned string later gets formatted according to the bound data type and format.
cleanup - A function that can be defined in sub-classes. "cleanup" is called when the widget is destroyed, after the screen has submitted and before a new screen renders. If the render function added any event listeners, then those listeners should be removed in "cleanup".
evalProperty - A function that evaluates the current value of a property as set by a Profound UI property. There is one String parameter, the property name being set. Using "evalProperty" is helpful in Genie Design Mode. "evalProperty" can handle properties of type, "script", set with "js: ...somecode...". Subclasses normally need not override "evalProperty", but if they do they must call the version in pui.BasicWidget.prototype first; e.g. before any other code do: pui.BasicWidget.prototype.evalProperty.call(this, propname);
handleEvent - A function that accepts an Event parameter and does "this.dom.modified = true;" if the event type is "input" or "change". In order for "handleEvent" to run, event listeners must be added in the "render" or "setProperty" functions like so: someCustomElement.addEventListener('change', this); or someCustomElement.addEventListener('input', this); . Sub-classes can override the super-class's "handleEvent", and there is no need to call the super-class "handleEvent". Note: "this.dom.modified = true;" must happen when a value has changed in the custom widget; otherwise, without the "modified" flag Profound UI will not send the widget data to the server-side program.
setProperty - A function that can be defined in subclasses of pui.BasicWidget. Before "setProperty" is called, pui.BasicWidget does some internal work with the property information. Afterward, setProperty is called whenever Profound UI sets a property on a widget. The first time setProperty runs is during the "field type" setter, after the pui.BasicWidget is constructed. The setProperty function accepts one parameter, an object that contains information normally passed to property setters, including these options:
propertyName - String. Name of the property being set; e.g. "field type", "id", "value"
design - Boolean. True when the page is Visual/Node Designer
properties - an object containing property names as keys and property values as values.
value - String. Value being currently assigned to the property.
Public Properties
These properties of pui.BasicWidget can be accessed in sub-classes and externally.
design - Boolean. True when the page is Visual or Node Designer.
dom - Element. A reference to the main DIV element that contains the custom element's DOM sub-tree.
widgetName - String. The unique name of the type of widget; e.g. existing widget names would be "grid", "chart", "icon", etc. The name is assigned by the constructor and is read-only.
Widget APIs Compatible with pui.BasicWidget
These widget APIs have been tested successfully or adapted to work with instances of or sub-classes of pui.BasicWidget:
applyProperty - This can be used for all properties. However, when applyProperty is passed the "field type" property on a pui.BasicWidget the widget gets destroyed and then constructed, losing existing property values. To refresh an instance of pui.BasicWidget call the public "render" method instead of re-setting "field type".
changeElementValue / pui.set - for instances of and sub-classes of pui.BasicWidget "changeElementValue" and "pui.set" simply call the "value" property setter; e.g. like applyProperty(..., "value", someValue);
getElementValue / get - Calls the widget's "getValue" function to get a value and formats that value as a string according to the bound formatting information, if possible.
getObj - returns the main DIV element containing the custom widget.
hideElement - sets the "visibility" style of the widget's main DIV to "hidden".
getInputFields - works as long as elements in the custom widget are not placed inside a Shadow DOM
pui.click - works for input elements of type, "button", and button elements that have an onclick listener defined.
Example
Below is an example of adding a widget and defining its behavior in a subclass of pui.BasicWidget (rather than in the "render" and "getValue" options supplied to pui.widgets.add).
// Begin Declaration of ColorPicker class.
/**
* Define the class constructor.
* @param {Object} parms Property name whose value should be fetched.
* @param {String} widgetName Name of widget as defined in the call to pui.widgets.add in the "name" property.
*/
ColorPicker = function(parms, widgetName){
// JS-properties assigned in the BasicWidget constructor are assigned to this object; e.g. "dom",
// "design", "widgetName", and other necessary variables. Always required.
pui.BasicWidget.call(this, parms, widgetName);
this.label = null;
this.input = null;
this.build = true;
};
// ColorPicker inherits pui.BasicWidget prototype methods.
ColorPicker.prototype = Object.create(pui.BasicWidget.prototype);
/**
* Implement a setProperty method. Setting certain properties flags "render" to rebuild the widget.
* @param {Object} parms Property-setter parameters.
*/
ColorPicker.prototype.setProperty = function(parms){
if (parms.propertyName == "field type"){
this.build = true; //The widget DOM will be cleared and rebuilt when the next "render" happens.
}
};
/**
* Draws the widget. Profound UI calls "render" after calling property-setters.
* Note: User-defined API calls to applyProperty do not automatically cause "render" to be called;
* it would need to be called manually.
*/
ColorPicker.prototype.render = function(){
// Get values of properties that were set by property-setters.
var value = this.evalProperty("color picker value");
var labelText = this.evalProperty("color picker label");
var disabled = this.evalProperty("disabled") == "true";
if (this.design){
var bgcolor = disabled ? "#aaa" : "#eee";
// Use a mock-up of the input so that dragging or selecting it will not popup the color picker.
this.dom.innerHTML = labelText+
" <div style=\"min-width:50px; min-height:16px; display:inline-block; background-color:"
+ bgcolor +"; border:outset 2px black;\">"+value+"</div>";
}
else {
// Build the widget if necessary.
if (this.build){
this.dom.innerHTML = "";
this.label = null;
this.input = null;
this.label = document.createElement("label");
var span = document.createElement("span");
this.label.appendChild( span );
this.dom.appendChild(this.label);
this.input = document.createElement("input");
this.label.appendChild(this.input);
this.input.addEventListener("change", this);
if (!pui.is_ie) this.input.type = "color"; //Internet Explorer gives errors for the type, "color".
this.input.style.width = "50px";
}
this.input.value = value;
this.input.disabled = disabled;
this.label.children[0].innerHTML = labelText;
}
this.build = false; //Calling "render" again will not rebuild the DOM.
};
/**
* Provide a way for Profound UI to get property values out of this widget.
* @param {String} propname Property name whose value should be fetched.
*/
ColorPicker.prototype.getValue = function(propname){
// Extract the "color picker value" from our custom DOM element and return it.
// The user may have changed the element value, so we must extract it here when asked to.
if (propname == "color picker value" && this.input) return this.input.value;
// Demonstrates that multiple properties can be bound on the same widget and submitted to server.
else if (propname == "color picker value2") return "#123456"; //sample data.
// Any other properties are returned as they were set in property setters.
return this.evalProperty(propname);
};
/**
* Implement a cleanup method when necessary.
*/
ColorPicker.prototype.cleanup = function(){
// Any event listeners added in render or elsewhere should be removed here.
if (this.input != null){
this.input.removeEventListener("input", this);
this.input.removeEventListener("change", this);
}
};
// end of ColorPicker class definition.
pui.widgets.add({
name: "color picker", //widget name - this is placed into the standard "field type" property
newId: "ColorPicker", //default id prefix - used to assign ID; e.g. ColorPicker2.
menuName: "Color Picker", //Name shown in the Properties Window when the widget is selected.
// Default properties for the widget
defaults: {
"width": "100px",
"height": "22px",
"color picker label": "Color:"
},
// Tells Profound UI to automatically create a property-setter that constructs the widget with this constructor.
constr: ColorPicker
});
pui.addCustomProperty({
name: "color picker label",
help: "A label for the button. Default is \"Color:\".",
controls: ["color picker"],
category: "Field Settings",
showDesignValue: true
});
// Note: this property demonstrates a bidirectional property not named "value" can be submitted to the server.
pui.addCustomProperty({
name: "color picker value",
help: "Value to test binding",
controls: ["color picker"],
category: "Field Settings",
showDesignValue: true,
bidirectional: true
});
// Note: this property demonstrates that multiple bound values can be used with a single custom widget.
pui.addCustomProperty({
name: "color picker value2",
help: "Value to test binding",
controls: ["color picker"],
category: "Field Settings",
showDesignValue: true,
bidirectional: true
});
// Add the widget to the "All Widgets" toolbox and Genie Design Mode toolbox.
pui.toolbox.add({
// Toolbox Category. Created if it does not exist.
category: "Custom Widgets",
widget: "color picker",
text: "Color Picker", //The name that appears in the toolbox
// The look of the drag/drop proxy as the user drags the widget element off the toolbox.
proxyHeight: 24,
proxyWidth: 102,
proxyHTML: "Color: <input type=\"color\" value=\"#8000ff\" />",
// Default properties for this instance of the widget. Required, even if empty.
defaults: {}
});
With that code, the widget can be used in Visual Designer:
In runtime Profound UI automatically fetches the values stored in the custom widget, formats them, and submits them to the server. The picture below shows a color-widget with multiple bound values that were submitted to the server, and a ColorPicker in a subfile grid–demonstrating that the bound "color picker value" inside a subfile grid can be automatically handled.