Build a Form Engine Data Source

What is a Data Source

Crafter Studio form controls should be written in a way that makes them independent of the data they allow the user to select so that they can be (re)used across a wide range of data sets. To accomplish this objective we use a data source pattern where by the form control widget code is concerned with rendering and facilitating the data capture/selection process but delegates the retrieval of the content to a separate swappable component interface known as a data source.

Content Type Editor

Form Engine data sources are #5 in the image above.

Out of the box data sources are:

Datasource
Description
Form Data Sources - Shared Content
Details are in the Shared Content Data Source page.
Form Data Sources - Embedded Content
Details are in the Embedded Content Data Source page.
Form Data Sources - Child Content
Form Data Sources - Image Uploaded From Desktop
Form Data Sources - Image From Repository
Details are in the Image from Repository Data Source page.
Form Data Sources - File Uploaded From Desktop
Form Data Sources - File Browse
Details are in the File Browse Data Source page.
Form Data Sources - CMIS Repo
Details are in the CMIS Repository Data Source page.
Form Data Sources - CMIS Image From Repository
Form Data Sources - CMIS Video From Repository
Form Data Sources - CMIS Upload
Details are in the CMIS File Upload Data Source page.
Form Data Sources - CMIS Image Upload to Repository
Form Data Sources - CMIS Video Upload to Repository
Form Data Sources - WebDAV Repo
Form Data Sources - WebDAV Image Repo
Form Data Sources - WebDAV Video Repo
Form Data Sources - WebDAV Upload
Details are in the WebDAV File Upload Data Source page.
Form Data Sources - WebDAV Image Upload
Details are in the WebDAV Image Upload Data Source page.
Form Data Sources - WebDAV Video Upload
Details are in the WebDAV Video Upload Data Source page.
Form Data Sources - S3 Repo
Details are in the S3 Repository Data Source page.
Form Data Sources - S3 Image Repo
Details are in the Image from S3 Repository Data Source page.
Form Data Sources - S3 Video Repo
Details are in the Video from S3 Repository Data Source page.
Form Data Sources - S3 Upload
Details are in the S3 File Upload Data Source page.
Form Data Sources - S3 Image Upload
Details are in the S3 Image Upload Data Source page.
Form Data Sources - S3 Video Upload
Details are in the S3 Video Upload Data Source page.
Form Data Sources - Video Upload then Transcode from S3 Repo
Form Data Sources - Video Uploaded From Desktop
Form Data Sources - Video From Repository
Details are in the Video from Repository Data Source page.
Form Data Sources - Static Key Value Pairs
Details are in the Static Key Value Pairs Data Source page.
Form Data Sources - Site Component
Details are in the Site Component Data Source page.

The anatomy of a Data Source Plugin

Data Sources consist of (at a minimum)

  • A single javascript file which implements the data source interface.

    • The file name of the data source is important as the system uses a convention whereby the JS file name and the data source name in the configuration must be the same.

    • The module name must also be the same as the data source name with cstudio-forms-controls- prepended to the front of it Ex: cstudio-forms-controls-configured-list

  • Configuration in a Crafter Studio project to make that data source available for use.

Data Source Interface

Data Source Interface

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
    /**
     * Constructor: Where .X is substituted with your class name
     */
    CStudioForms.Datasources.ConfiguredList = CStudioForms.Datasources.X ||
    function(id, form, properties, constraints)  {
    }

    /**
     * Extension of the base class
     */
    YAHOO.extend(CStudioForms.Datasources.X, CStudioForms.CStudioFormDatasource, {

            /**
             * Return a user friendly name for the data source (will show up in content type builder UX
             */
            getLabel: function() {  },

            /**
             * return a string that represents the type of data returned by the data source
             * This is often of type "item"
             */
            getInterface: function() { },

            /**
             * return a string that represents the kind of data source (this is the same as the file name)
            getName: function() { },

            /**
             * return a list of properties supported by the data source.
             * properties is an array of objects with the following structure { label: "", name: "", type: "" }
             */
            getSupportedProperties: function() { },

            /**
             * method responsible for getting the actual values.  Caller must pass callback which meets interface:
             * { success: function(list) {}, failure: function(exception) }
             */
            getList: function(cb) { }
    });

Coding an example

Our example is a data source that gets values from an XML file stored in the repository. This is a simple data source that allows administrators to define common taxonomies or lists and then to re-use those across many forms without having to redefine them every time.

Data Source Code

Form Engine Control Example

Location /STUDIO-WAR/default-site/static-assets/components/cstudio-forms/data-sources/configured-list.js

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
    /**
     * Constructor.  This data source can take time to retrieve the content from the repository.
     * For this reason when a caller asks for data we look to see if the data has already been returned.
     * If not we register the request to call back later. Otherwise, we returned the cached data.
     * The constructor initializes the data source and then immediately starts working on retrieving and caching the data.
     * Once the data is returned waiting controls are called back.
     */
    CStudioForms.Datasources.ConfiguredList = CStudioForms.Datasources.ConfiguredList ||
    function(id, form, properties, constraints)  {
            this.id = id;
            this.form = form;
            this.properties = properties;
            this.constraints = constraints;
            this.callbacks = [];
            var _self = this;

            for(var i=0; i<properties.length; i++) {
                    var property = properties[i]
                    if(property.name == "listName") {
                            var cb = {
                                    success: function(config) {
                                            var values = config.values;
                                            if(!values.length) {
                                                    values = [ values.value ];
                                            }

                                            _self.list = values;

                                            for(var j=0; j<_self.callbacks.length; j++) {
                                                    _self.callbacks[j].success(values);
                                            }
                                    },
                                    failure: function() {
                                    }
                            };

                            CStudioAuthoring.Service.lookupConfigurtion(
                                            CStudioAuthoringContext.site,
                                            "/form-control-config/configured-lists/" + property.value + ".xml",
                                            cb);

                    }
            }

            return this;
    }

    /**
     * extend the base class and override required methods
     */
    YAHOO.extend(CStudioForms.Datasources.ConfiguredList, CStudioForms.CStudioFormDatasource, {

        getLabel: function() {
            return "Configured List of Values";
        },

            getInterface: function() {
                    return "item";
            },

            getName: function() {
                    return "configured-list";
            },

            getSupportedProperties: function() {
                    return [
                            { label: "List Name", name: "listName", type: "string" }
                    ];
            },


            /**
             * if the list is cached return it otherwise register the request for a callback when it is available
             */
            getList: function(cb) {
                    if(!this.list) {
                            this.callbacks[this.callbacks.length] = cb;
                    }
                    else {
                            cb.success(this.list);
                    }
            },


    });

    CStudioAuthoring.Module.moduleLoaded("cstudio-forms-controls-configured-list", CStudioForms.Datasources.ConfiguredList);

Configuring the Data source to show up in Crafter Studio

Add the datasources name to the list of data sources in the content type editor

Location (In Repository) SITENAME/config/studio/administration/site-config-tools.xml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
    <config>
            <tools>
                    <tool>
                            <name>content-types</name>
                            <label>Content Types</label>
                            <controls>
                                    ...
                            </controls>
                            <datasources>
                                    ...
                                    <datasource>video-desktop-upload</datasource>
                                    <datasource>configured-list</datasource>
                            </datasources>
                            ...
                    </tool>
                    <!--tool>...</tool -->
            </tools>
    </config>

Complex Example that uses AJAX to get data from external source:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
    CStudioForms.Datasources.ConfiguredList = CStudioForms.Datasources.ConfiguredList ||
    function(id, form, properties, constraints)  {
           this.id = id;
           this.form = form;
           this.properties = properties;
           this.constraints = constraints;
        this.callbacks = [];
        var _self = this;

        for(var i=0; i<properties.length; i++) {
            var property = properties[i]
            if(property.name == "listName") {
                var cb = {
                    success: function(config) {
                        var values = config.values;
                        if(!values.length) {
                            values = [ values.value ];
                        }

                        _self.list = values;

                        for(var j=0; j<_self.callbacks.length; j++) {
                            _self.callbacks[j].success(values);
                        }
                    },
                    failure: function() {
                    }
                };

                CStudioAuthoring.Service.lookupConfigurtion(
                        CStudioAuthoringContext.site,
                        "/form-control-config/configured-lists/" + property.value + ".xml",
                        cb);

            }
        }

        return this;
    }
    YAHOO.extend(CStudioForms.Datasources.ConfiguredList, CStudioForms.CStudioFormDatasource, {
        getLabel: function() {
            return "Configured List of Values";
        },
           getInterface: function() {
               return "item";
           },
           /*
         * Datasource controllers don't have direct access to the properties controls, only to their properties and their values.
         * Because the property control (dropdown) and the dataType property share the property value, the dataType value must stay
         * as an array of objects where each object corresponds to each one of the options of the control. In order to know exactly
         * which of the options in the control is currently selected, we loop through all of the objects in the dataType value
         * and check their selected value.
         */
        getDataType : function getDataType () {
            var val = null;
            this.properties.forEach( function(prop) {
                if (prop.name == "dataType") {
                    // return the value of the option currently selected
                    prop.value.forEach( function(opt) {
                        if (opt.selected) {
                            val = opt.value;
                        }
                    });
                }
            });
            return val;
        },
        getName: function() {
            return "configured-list";
        },

        getSupportedProperties: function() {
            return [{
                label: "Data Type",
                name: "dataType",
                type: "dropdown",
                defaultValue: [{ // Update this array if the dropdown options need to be updated
                    value: "value",
                    label: "",
                    selected: true
                }, {
                    value: "value_s",
                    label: "String",
                    selected: false
                }, {
                    value: "value_i",
                    label: "Integer",
                    selected: false
                }, {
                    value: "value_f",
                    label: "Float",
                    selected: false
                }, {
                    value: "value_dt",
                    label: "Date",
                    selected: false
                }, {
                    value: "value_html",
                    label: "HTML",
                    selected: false
                }]
            }, {
                label: "List Name",
                name: "listName",
                type: "string"
            }];
        },
        getSupportedConstraints: function() {
            return [
                { label: "Required", name: "required", type: "boolean" }
            ];
        },

        getList: function(cb) {
            if(!this.list) {
                this.callbacks[this.callbacks.length] = cb;
            }
            else {
                cb.success(this.list);
            }
        },

    });
    CStudioAuthoring.Module.moduleLoaded("cstudio-forms-controls-configured-list", CStudioForms.Datasources.ConfiguredList);

Summary

A good place to start is by looking at the control you want to use, for example the video picker.

Location /STUDIO-WAR/default-site/static-assets/components/cstudio-forms/controls/video-picker.js

When you want to build a data source, there is a method called get interface. This method tells the system what the data source can help with. So using the same example, a video upload returns video and thus the video picker can use that data source.

Location /STUDIO-WAR/default-site/static-assets/components/cstudio-forms/data-sources/video-desktop-upload.js

If you want to create a new datasource for the video picker, you basically copy and paste a similar datasource, then change the object class name, label and interface. Then in the project go to the the administration panel and change the configuration to load the new javascript file.