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:

 

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:

 

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.