Webcomponents need meta data to be able to function in a (form) designer
On the web are some specs arising like:
http://www.openajax.org/member/wiki/OpenAjax_Metadata_1.0_Specification_Widget_Overview (thanks to Paul for pointing us to this one)
But i think this overly complex when using angular directives... so we created a spec definition file (.spec) with the following properties
name: String simple name of the component displayName: String more descriptive name that is shown in the designer definition: A reference to the js file of this component model: { propertyName: type description }, handlers: { functionName: function type }, api: { functionName: signature description }, types: { typename: { model: { propertyName: type description } } }
A WebComponent specifies all its properties under the model . handlers all the events and api has the javascript api the webcomponent has that the server can call.
Types are internal complex object that describe a type that can be used as in a properties type description (for example a tabpanel component has a tab type that describes one tab inside the tabpanel) besides that the type description can be a simple type like:
string: A plain string property
tagstring: A string that can have tags inside it (so it will be processed by servoy to have tags replaced before it gets to the client)
color: A color property (#FFFFFF)
point: A point representation \{x:10,y:20\}
dimension: A dimension representation \{width:100,height:200\}
insets:
font:
border:
boolean: true/false
scrollbars:
byte:
double: A floating point number
float: A floating point number
int: A number
long: A number
short: A number
values: Fixed values can have real/display values.
dataprovider: Servoy maps this on a record or scope variable, This can be a complex object: \{ 'type':'dataprovider', 'ondatachange': \{ 'onchange':'onDataChangeMethodID', 'callback':'onDataChangeCallback'\}\} if support for ondatachange is needed.
valuelist: Servoy maps this on a valuelist referene
form: This property will hold a url point to a form (like a tab in a tabpanel)
format: format property, this must be specified with a complex object like: \{'for':'dataProviderID' ,'type':'format'\} so that we know which dataprovider property must be used to map this format property on
relation: Servoy maps this on a relation reference
media:
date: A date type property
The function description in the handlers section can be just "function" or a more complex type describing the arguments and return type {parameters: [{start: 'int'}, {end: 'int'}], returns: 'string'}
The signature description should also be like the complex function type, describing the arguments and return type {parameters: [{start: 'int'}, {end: 'int'}], returns: 'string'}
As an example we could have a datatextfield that has a definition like this
name: 'datatextfield', displayName: 'Text input', definition: 'servoydefault/datatextfield/datatextfield.js', model: { toolTipText: 'string', background: 'color', dataProviderID: { 'type':'dataprovider', 'ondatachange': { 'onchange':'onDataChangeMethodID', 'callback':'onDataChangeCallback'}}, format: {'for':'dataProviderID' ,'type':'format'}, // value will be just the format string as a whole }, handlers: { onDataChangeMethodID: 'function', }, api: { requestFocus: { }, getSelectedText: { returns: 'string' }, setSelection: { parameters: [{start: 'int'}, {end: 'int'}] } }
So it has a simple properties like toolTipText (a string) and background (a color) and a dataproviderID property that is of type 'dataprovider' which has an ondatachange that points to the handler onDataChangeMethodID function and the textfield wants to have a callback function called that is called 'onDataChangeCallback'
That field component will be expressed in the form html like this
<datatextfield name="dataFieldFormatNumber" svy-model="model.dataFieldFormatNumber" svy-api="api.dataFieldFormatNumber" svy-handlers="handlers.dataFieldFormatNumber" svy-apply="handlers.dataFieldFormatNumber.svy_apply" svy-servoyApi="handlers.dataFieldFormatNumber.svy_servoyApi"/>
which is expanded by angular to real html. So every webcomponent gets a number of objects pushed to the instance:
- model: This has all the properties set in the designer or at runtime (if they are data driven like a dataprovider property), it reflects the model object defined in the spec.
- handlers: This has all the event functions that are set in the designer like "onclick" or "ondatachange"
- api: On this object the webcomponents needs to add the api calls that the server can call. Like a requestFocus function.
- apply: This is a special method that a webcomponent can use to let the server know a property (of the model) is changed. So the value can be applied also on the server (pushed into the record), If a component has fields that uses ng-model then the directive "svy-autoapply" can be used in the html template instead of coding it out in the webcomponent javascript file.
- servoyApi: This is a component that holds some special servoy api functions to call, currently "setFormVisibility(form,visible,[relation]);
So the javascript file of the bean component can put those into its scope:
servoyModule.directive('datatextfield', function($servoy) { return { restrict: 'E', transclude: true, scope: { model: "=svyModel", api: "=svyApi" }, controller: function($scope, $element, $attrs) { // fill in the api defined in the spec file $scope.api.onDataChangeCallback = function(event, returnval) { if(!returnval) { $element[0].focus(); } }, $scope.api.requestFocus = function() { $element[0].focus() }, $scope.api.getSelectedText = function() { var elem = $element[0]; return elem.value.substr(elem.selectionStart, elem.selectionEnd - elem.selectionStart); } $scope.api.setSelection = function(start, end) { var elem = $element[0]; if (elem.createTextRange) { var selRange = elem.createTextRange(); selRange.collapse(true); selRange.moveStart('character', start); selRange.moveEnd('character', end); selRange.select(); elem.focus(); } else if (elem.setSelectionRange) { elem.focus(); elem.setSelectionRange(start, end); } else if (typeof elem.selectionStart != 'undefined') { elem.selectionStart = start; elem.selectionEnd = end; elem.focus(); } } }, templateUrl: 'servoydefault/datatextfield/datatextfield.html', replace: true }; })
The model and api objects are stored in its own scope and then inside the controller function the various api calls are implemented besides the onDataChangeCallback function used as a callback of the ondatachange for a dataprovider.
The template then looks like this:
<input type="text" style="width:100%; height:100%; background-color:{{model.background}};" ng-model="model.dataProviderID" title="{{model.toolTipText}}" svy-autoapply svy-format="model.format"/>
Where the various properties are then taken from the model for specific html tags.
A handler call to the server does get a promise back (http://docs.angularjs.org/api/ng.$q) where the webcomponent could register a callback on so that an event that executes on the server can return a value to the webcomponents call.