Specification (.spec file)

Specification (.spec file)

Overview

A (WYSIWYG) form designer needs component meta data to be able to handle components. This meta data is expressed in a specification.

Definition and basic structure

A simple metadata specification is expressed in json format. (called the .spec definition file)

The specification defines:

  • identifier information (component name, icon, display name, ...)

Name convention: the provided string is of the form package name, followed by a dash sign ('-'), followed by the component name. The package name is the name of the package the component is part of, and component name is simply the name of the component. Both package name and component name should be written in lower-case.

  • dependencies (any extra js & css library dependencies that the component or service need to use)

  • definition of component structure, to support:

    • model properties

    • event handlers

    • callable APIs

  • additional/child property type info (information about custom types of data that can be used in model, api/handler parameters and return values)

In a json format: spec file definition

{
    "name": "packagename-componentname", // String
    "displayName": "more descriptive name that is shown in the designer", // String
    "version": the component version (integer)
    "icon": "A reference to the icon shown in designer" ,
    "preview": "A reference to the preview gif to be displayed" ,
 
    "definition": "A reference to the js file that implements this component's in the browser (NG1 ONLY)",
    "serverscript": "[optional] A reference to the js file that implements this component's server-side logic, if any.",
    "doc": "A reference to the js file that contains the jsdocs of the component api or model properties. (NG1 ONLY)",
    "group": true // default true, so the definition file can be grouped when creating the .war file for deployment
    "deprecated": "This component will be replaced in the next versions.", // (optional) some string to mark the component as deprecated - if it is deprecated
    "replacement": "package-compname", // (optional) in case of deprecation, developer will provide a quick fix for such components to be automatically changed to the given replacement component
                                       // (make sure they have compatible .spec defined; in most cases this is useful when moving components from a package to another package or when
                                       // rewriting a component but keeping it's contract unchanged)
    "libraries": [], // Array of js/css definitions (which are JSON objects with
                     // 'name'-the lib name, 'version'-the lib version, 'url'-the lib url,
                     // 'mimetype'-the lib mimetype, one of 'text/javascript' or 'text/css',
                     // 'group' - give false here when this lib dependency should not be grouped when exported
                     // as .war; default true) that need to be included for this component. (NG1 ONLY)
 
    "keywords": [], // Array of keywords used for searching components in the palette.
                    // For instance, for the calendar component some appropriate keywords that might be used are: "day", "month", "year"
 
    "categoryName": "Advanced", // category for form designer palette; only makes sense for components, not services

    "ng2Config": { // TiNG only describing the package how it is defined for TiNG
       "packageName": "The name of the projects package.json",
       "serviceName": "If it is a service this is the name of the Angular Service",
       "entryPoint": "The directory of the build output (/dist/xxx)",
       "moduleName": "The Module Name",
       "dependencies": {
           "csslibrary": ["A module path to a css lib that needs to be included;priority=5"]
        },
        "assets": [{
                    "glob": "Filter like: *.js",
                    "input": "The directory that should be include (most likely dir in node_modules package)",
                    "output": "The output dir where these files should be in (seem from the root of the WAR)"
              }]
    },   
    "model": {
        "property1Name": /* type description (optionally including optional default value and others) */,
        "property2Name": /* type description (optionally including optional default value and others) */
    },
 
    "handlers": {
        "handler1Name": { /* handler function type definition*/ },
        "handler2Name": { /* handler function type definition*/ }
    },
 
    "api": {
        "apiFunction1Name": { */ api function signature description json*/ },
        "apiFunction2Name": { */ api function signature description json*/ }
    },
 
    "internalApi" : {
        "internalApiFunction1Name": { */ internal api function signature description json*/ },
        "internalApiFunction2Name": { */ internal api function signature description json*/ }
    },
 
    "types": {
        "customType1Name": {
            "subProp1Name": /* type description" */,
            "subProp2Name": /* type description" */
        },
        "customType2Name": {
            "subProp1Name": /* type description" */,
            "subProp2Name": /* type description" */
       }
    }
}

An NG web component or ng service specifies all its properties in the model section of the .spec file, all the events under handlers section and the api section has the javascript callable api that the webcomponent exposes (that the server/solution can call).

Starting with 8.2 (before these were defined in "api") there is also the internalApi section that is better described in Server Side Scripting page (calls between client and server that are only meant for the code inside the component/service to use).

You can find an example .spec file below.

Grouping of library dependency options

The "group" property on the top level spec or in the libraries section tells Servoy if that the definition or library can be grouped or not; by default this is allowed. This is used when a WAR is generated by the WAR Exporter.

The "group" property The libraries which contain references to external files cannot be grouped because in the deployed applications the relative paths to those resources are lost, therefore the components will not work.

Such libraries are font-awesome.css or tinymce.js, they should always have "group": false in the specification file of the components that use them.

Deprecation support

A component/service/layout can be marked as deprecated by using the "deprecated" and/or "replacement" properties.

The deprecated components/layouts will not be shown anymore in the palette.

The deprecated services will not be shown anymore under the Solution Explorer plugins node. Markers will be added in case they are used in scripting.

The following combinations are possible:

"deprecated": "true" // a boolean as a string; this just means "deprecated" without any extra info (it is a string and not a boolean directly in case you want to provide more information about the deprecation; see below)
"deprecated": "This component will be removed in version X.", // some extra message explaining to the developer why it was deprecated or what to use instead

In the case when the "replacement" property is used (supported for component level deprecation only), the generated markers for the used deprecated components will also have a quick-fix.

"deprecated": "This component will be removed in version X.",//some extra message
"replacement": "packagename-compname"

Using only the "replacement" property will also mark the component as deprecated, and the generated marker will have a quick-fix.

"replacement": "packagename-compname"  

The "deprecated" property The "deprecated" property can also be used to deprecate service/component api or properties:

e.g. service function

"removeKL": {
    "returns": "boolean",
    "deprecated": "Please use removeKeyListener(...) instead.",
    "parameters": [ { "name": "callbackKey", "type": "string" } ]
 }

e.g. component property

"model": {
    "enabled" : {"type": "boolean", "default": true, "deprecated": "true"},
    ...
}

In this case, the deprecated property will not be shown in the properties view.

Model

An NG web component or ng service specifies all its properties and the type of each property in the model section of the .spec file.

Apis or handlers are not supported under "model" or "types" because these are just meant as container objects that transmit data/information between the component/service and the server.

For giving types of each property you can use any of the default provided property types that you can find here as well as any custom types defined in the "types" section. Arrays are also allowed (append "[]" to the type name).

Property types under model can be defined in two ways:

  • simply by specifying it's type:

"someTextProperty": "string"

with additional configuration options if you want/need to tell Servoy more about how this property should be treated (this is just a sample, all configuration options are optional and each one will be detailed later); "type" is mandatory.

"someTextProperty": { "type": "string", "tags": { "scope": "design" },
                                        "values": [ { "Predefined Text 1": "sample text 1" }, { "Predefined Text 2": "sample text 2" } ],
                                        "default": "nothing interesting" }

Array types

To define an array property just append "[]" to the type name.

"someTextProperties": "string[]" To specify configuration options for each element of the array you can do:

"someTextProperties": { "type": "string[]", "elementConfig" : {
                                                "tags": { "scope": "design" },
                                                "values": [ { "Predefined Text 1": "sample text 1" }, { "Predefined Text 2": "sample text 2" } ],
                                                "default": "nothing interesting" }
                                            } 

For more information on array types see the array property types page.

Custom types

types section defines custom internal types that can then be used by this web component/service for one or more of its properties in the model as well as for parameter and return value types defined in other sections. (for example a tabpanel component has a "tab" type that describes one tab inside the tab-panel). Custom type definitions each support for defining subproperties whatever is supported under model. For example:

"types": {
    "someCustomTypeName": {
        "name": "string",
        "containsForm": "form",
        "texts": "tagstring[]",
        "relationName": "relation",
        "active": "boolean"
    }
}

For more information on custom property types and how they work see the custom object property types page.

Configuration options

There is a set of standard configuration options and each specific property type (for more advanced types like "foundset", "dataprovider", ...) can have some extra configuration options that are detailed by each type.

Stardard tags are the ones that control data synchronization, tags and default/initial/predefined values.

Data synchronization

Data modifications are automatically propagated from server to client.

For performance (and security) reasons, data modifications from client to server are not propagated by default. To enable client-to-server data send, use the property's pushToServer config option. For example:

pushToServer example

"model": {
   "searchfield": { "type": "string", "pushToServer": "shallow" }
}

pushToServer can take the following values:

  • reject - this is the default; there is no data synchronization from client to server for this property; the server will throw an exception if updates are pushed from client to server on such properties (for both NG & Titanium client)

  • allow - the server allows data changes received from client for such a property;

    • NG1 client-side: it needs a manual trigger or a directive that triggers the send of changes for that property (in NG1, it's for dataprovider properties -> svy-apply (manual servoy api call) or svy-autoapply (directive));

    • Titanium client-side: it needs manual send-to-server; that means that component client side code needs to call an .emit(value) on the @Output (or, in case of services, ServoyPublicService.sendServiceChanges(...)) of the root property that is/contains the changed value (even if it intends to send only a subprop/element of the root property that only has ALLOW pushToServer). Before using .emit(...)/.sendServiceChanges(...), in the rare cases where you use an 'object' type in the .spec file for elements of custom arrays or sub-properties of typed custom objects, and the content of that value is a nested JSON, in order for the custom objects type/custom array type to 'see' the changes nested inside the JSON of the plain 'object' value (so for change by reference of the 'object' typed value it is not needed), you can use either ICustomObjectValue.markSubPropertyAsHavingDeepChanges(subPropertyName: string) or ICustomArrayValue.markElementAsHavingDeepChanges(index: number)

  • shallow - same as allow, but client-side it reacts to changes-by-reference as described below:

    • NG1: a shallow watch will be added automatically on the value (in the client) and changes-by-reference will automatically be sent to the server; the watch is based on object reference/primitive value (it is faster then 'deep', but in case of nested plain 'object' properties (with array/object JSON content) it will only detect and send changes automatically when the values are different by reference (even if they are actually 'equal' by content))

    • Titanium: client code will automatically react and send the changes to server ONLY when components/services change by reference the values inside typed custom objects/arrays/... (declared as such in the .spec file; for example inside a 'string[]' or 'myColumnObject'). But it DOES NOT work automatically for root properties that change by ref. Root property change-by-ref in Titanium currently always needs a manual trigger; see the description from 'allow' above. Changes nested inside untyped nested JSON values ('object' in .spec) need to be triggered manually as well, as they are not changes-by ref of that 'object' value. That can be done via:

      • if the deep untyped JSON that has changes is a root property, emit(value) on the component @Output (and in case of services via ServoyPublicService.sendServiceChanges())

      • if the deep untyped JSON that has changes is a subproperty of a typed custom object from .spec, ICustomObjectValue.markSubPropertyAsHavingDeepChanges(subPropertyName: string)

      • if the deep untyped JSON that has changes is an element of a custom array from .spec, ICustomArrayValue.markElementAsHavingDeepChanges(index: number)

  • deep - same as allow, but client-side it is meant to react at any changes of properties of type 'object' - even nested changes if it contains nested JSON - and for any change it detects (doesn't matter how little) it will send the whole value to the server:

    • NG1: a deep watch on the value (in the client) will automatically be added and changes (both by reference and nested inside the 'object') will automatically be sent to the server; the watch is based on object equality (compares old and new values of nested JSON values; it is slower then 'shallow', because it keeps and compares two copies of the nested JSON)

    • Titanium: as the newer angular versions do not have deep watches anymore (to detect nested changes in plain 'object' typed-in-spec JSON values), it will behave exactly as 'shallow', see description above.

Note that in Servoy, data-provider property changes are sent to server using svy-apply (servoy api call) and svy-autoapply (directive); for these to work properly, these properties should set pushToServer to allow. Otherwise those data provider properties will be read-only.

Information about how "pushToServer" works in particular with Array property type and with Custom object property type:

  • before Servoy version 2023.03: for nested properties through custom object or array types, the pushToServer of the root property will be enforced on all elements/sub-properties of that property.

  • starting with Servoy version 2023.03: pushToServer on elements/sub-properties is taken into account; if the root property is defined as "reject" then all nested elements/sub-properties will be reject just as before. Child elements/sub-properties can only restrict parent object/array's push to server level (so from anything -> reject only) or switch between allow/shallow/deep, and if the child doesn't specify a "pushToServer", it will inherit it from the parent. If no "pushToServer" is specified on any of the parents and child the default will be used: "reject".

When typed arrays and typed objects are used in the spec file. (for example a 'string[]', 'myColumnObject' or 'myColumnObject[]') - starting with 2023.03 - pushToServer defined values are combined as follows:

Typed Object/Array (so PARENT) defined pushToServer

Sub-property/Element (so CHILD) defined pushToServer

RESULTS IN CHILD being

unset