Webcomponents need meta data to be able to function in a (form) designer and runtime environment.

On the web are some specifications arising like:

http://www.openajax.org/member/wiki/OpenAjax_Metadata_1.0_Specification_Widget_Overview (thanks to Paul for pointing us to this one)

At time of writing these seem overly complex when using angular directives...

Definition

An simple meta data specification can be expressed in json format. (spec definition file .spec)

The specification defines:

name: String simple name of the component
displayName: String more descriptive name that is shown in the designer
libraries: Array of js/css references that needs to be included for this component,
palette_icon: A reference to the icon shown in designer 
definition: A reference to the js file of this component
model: {
  propertyName: type description, optional default value
},
handlers: {
  functionName: function type
},
api: {
  functionName: signature description
},
types: {
   typename: {
     model: {
        propertyName: type description
	 }
   }
}

A WebComponent specifies all its properties under the model, all the events under handlers and api has the javascript api the webcomponent has that the server can call.

Types are defining internal types that are used by this webcomponent for one or more of its properties.  (for example a tabpanel component has a tab type that describes one tab inside the tabpanel). They have the same support as under model:{}, there is no support for api or handlers on those types again because they are just container objects that push data to the webcomponent from the server.

Besides that the type description can be a simple (property) types like:

 

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'}

Example

As an example we could have a textfield that has a definition like this

name: 'svy-textfield',
displayName: 'Text input',
definition: 'servoydefault/textfield/textfield.js',
libraries: ['servoydefault/textfield/formatting.js','//netdna.bootstrapcdn.com/font-awesome/3.2.1/css/font-awesome.css'],
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'}]
	}
}

In this example 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'


The field component template html will be expressed like:

<svy-textfield 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:

 

So the javascript file of the bean component can put those into its scope, every webcomponent is a angular module of its own, with at least 1 directive that is describing the component itself. So the module and the one directive should be named like the bean (camel case notation so an svy- prefix will result in svyName). The bean module should also include the servoy module so that the bean can use the various servoy directives (starting with svy)

 

angular.module('svyTextfield',['servoy']).directive('svyTextfield', function() {  
    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.