Skip to main content
FlowForma was designed with flexibility in mind, so that no one should be limited by what it offers out-of-the box. This is where custom questions come into play. Questions are used to collect data entered by users. FlowForma provides set of out-of-the-box options like lookups, yes/no, calculated or simple text questions, but if you require a choice field to display values from an external system, then you need a custom question. They are composed by a pair of two files, one .js file where its logic resides, and one .html file that defines the question’s look/controls and also it’s management screen and how/what will be displayed on Flow Designer.

Step-by-step guide

The JS file:
//@ sourceURL=autonumber.js
/// <reference path="~/SharePointRoot/Template/Layouts/FlowForma/js/lib/handlebars/handlebars-v2.0.0.js" />
/// <reference path="~/SharePointRoot/Template/Layouts/FlowForma/js/lib/jquery/jquery-1.9.1.intellisense.js"/>
/// <reference path="~/SharePointRoot/Template/Layouts/FlowForma/js/FlowForma.js" />
/// <reference path="~/SharePointRoot/Template/Layouts/FlowForma/js/lib/kendo/kendo.all.js" />

$.extend(FlowForma.Questions.Widgets, {

    autonumber: function(container, question, DisplayMode, step) {

        var editModeInit = function() {
            //#region Variable init
            var autoNumberControl = $(container).find('input.autonumber');
            //#endregion

            //#region Loading values
            var definition = $.parseJSON(question.Definition);

            if (question.DisplayAnswer || question.InternalAnswer) {
                autoNumberControl.val(question.DisplayAnswer);
            } else {

                question.InternalAnswer = "loading";
                var clientContext = new SP.ClientContext(definition.List.WebUrl);
                var list = clientContext.get_web().get_lists().getByTitle(definition.List.ListTitle);
                var itemCreateInfo = new SP.ListItemCreationInformation();
                var item = list.addItem(itemCreateInfo);

                item.update();

                var queryLoadSuccess = function() {

                    autoNumberControl.val(item.get_id());
                    question.InternalAnswer = null;
                    question.DisplayAnswer = String(item.get_id());
                    $(question).trigger(FlowForma.Events.Question.Updated, []);
                };

                var queryLoadFailed = function(sender, args) {
                    alert('Request failed. ' + args.get_message() + '\n' + args.get_stackTrace());
                };

                clientContext.executeQueryAsync(queryLoadSuccess, queryLoadFailed);
            }

            if (!question.Enabled) {
                autoNumberControl.prop('disabled', true);
            }
            //#endregion

            //#region Event handlers
            autoNumberControl.on('change', function(e) {
                question.InternalAnswer = null;
                question.DisplayAnswer = $(this).val();
                $(question).trigger(FlowForma.Events.Question.Updated, []);
            });

            //On form validating
            $(FlowForma.FormContext.Form).on(FlowForma.Events.Form.Validating, validatingHandler);

            //On form saving
            $(FlowForma.FormContext.Form).on(FlowForma.Events.Form.Saving, savingHandler);
            //#endregion

            //#region Functions
            //#endregion
        };

        function savingHandler(e, args) {

            if (question.PublishToFieldEnabled && question.PublishField) {

                var subQuestion = $(container).data().role == 'subquestion';

                args.properties.push({
                    field: question.PublishField,
                    value: question.DisplayAnswer,
                    repeating: subQuestion
                });
            }
        };

        function validatingHandler() {

            var autoNumberControl = $(container).find('input.autonumber');

            if (question.Enabled && question.Required && !autoNumberControl.val()) {

                FlowForma.LogDebug("Question " + question.Id + " from step " + step.Title + " " + step.Id + " is invalid");
                FlowForma.FormContext.Errors.push(new FlowForma.ErrorMessage(question.Title + " is required", step, question, container));
            }
            $(question).trigger(FlowForma.Events.Question.Validated);
        }

        this.destroy = function() {

            $(FlowForma.FormContext.Form).unbind(FlowForma.Events.Form.Validating, validatingHandler);
            $(FlowForma.FormContext.Form).unbind(FlowForma.Events.Form.Saving, savingHandler);
        };

        var displayModeInit = function() {};
        var createModeInit = function() {

            //#region Variable init
            var definition = $.parseJSON(question.Definition);
            var listPickerContainer = $(container).find('#listPicker');
            var listPicker;
            //#endregion

            //#region Load values
            if (definition) {

                listItem = definition.List;
                listPicker = FlowForma.Widgets.ExistingListPicker(listPickerContainer, definition.List, null);
                //listControlChange();
            } else {

                listPicker = FlowForma.Widgets.ExistingListPicker(listPickerContainer, null, null);
            }
            //#endregion

            //#region Event handlers

            listPicker.OnCallBack(function(ListObject) {

                listItem = ListObject;
                //listControlChange();
                var errorField = $('.listErrorMessage');
                errorField.css('display', 'none');
            });

            //On Saving
            $(question).on(FlowForma.Events.Question.Saving, function(e, args) {

                var errorField;

                if (!listItem) {

                    args.IsValid = false;
                    args.Errors.push('Please fill all fields');
                    errorField = $('.listErrorMessage');
                    errorField.css('display', 'block');
                    errorField.text('List is required');

                }

                if (args.IsValid) {

                    //question creating saving event args format { IsValid: true, Errors: [] };

                    var definition = {

                        List: listItem
                    };

                    question.Definition = JSON.stringify(definition);

                }
            });

            //#endregion

            //#region Functions
            function isRepeatingTable(question) {
                return (question.Type == 'repeatingtable');
            }
            //#endregion
        };

        //#region Initialization
        if (DisplayMode == FlowForma.DisplayModes.EDIT) {

            editModeInit();
        }

        if (DisplayMode == FlowForma.DisplayModes.CREATE) {

            createModeInit();

        }

        if (DisplayMode == FlowForma.DisplayModes.DISPLAY) {

            displayModeInit();

        }
        //#endregion
    }
});
  1. A question is defined by two main elements: the editModeInit function, which is called whenever the question is to be displayed on the Form, and the CreateModeInit function, called when the question is being created/edited on the FlowDesigner
  2. DisplayAnswer and InternalAnswer are the main containers for the question’s value; DisplayAnswer is what the user will see, and InternalAnswer is some inner value, usually not relevant to the user, but useful to, for example, identify a lookup internal value. As another example, a date with a particular format, may be saved on DisplayAnswer, but a canonical value for the same date is kept on InternalAnswer. In any case, DisplayAnswer is the property that matters most; InternalAnswer may or may not have a value, depending on the situation
The HTML file: The HTML determines how the question will look when it’s being displayed to the user, and on the FlowDesigner.
<script type="text/x-handlebars-template" id="CreateTemplate">
    <table border="0" cellpadding="0" cellspacing="0" style="width: 100%">
        <tr>
            <td class="flowforma-question-label">
                <h3 class="flowforma-question-header">
                    <label>Get information from list <span class="asterix">*</span></label>
                </h3>
            </td>
            <td class="flowforma-question-control">
                <div id="listPicker" />
                <span class="listErrorMessage" style="display: none; color: #f00"></span>
            </td>
        </tr>
        <tr>
            <td colspan="2">
                <span id="lblNotes"></span>
            </td>
        </tr>
    </table>
</script>

<script type="text/x-handlebars-template" id="DisplayTemplate">
    {{#if ShowTitle}}
        <td class="step-label-cell singlelineoftext-title">{{Title}}
            {{#if Description}}
                <span class="k-icon k-i-note" title="{{Description}}" data-role="tooltip" data-auto-hide="false" data-position="right" data-show-on="click"></span>
            {{/if}}
        </td>
        <td class="step-control-cell singlelineoftext-label">{{DisplayAnswer}}</td>
        {{else}}
        <td colspan="2" class="step-control-cell singlelineoftext-label">{{DisplayAnswer}}</td>
    {{/if}}
</script>

<script type="text/x-handlebars-template" id="EditTemplate">
    {{#if ShowTitle}}
        <td class="step-label-cell singlelineoftext-title">{{Title}}
            {{#if Description}}
                <span class="k-icon k-i-note" title="{{Description}}" data-role="tooltip" data-auto-hide="false" data-position="right" data-show-on="click"></span>
            {{/if}}
        </td>
        <td class="step-control-cell singlelineoftext-control"><input type="text" class="autonumber" name="text" id="question-{{Id}}" value="{{DisplayAnswer}}" /></td>
        {{else}}
        <td colspan="2" class="step-control-cell singlelineoftext-control"><input type="text" class="autonumber" name="text" id="question-{{Id}}" value=" " /></td>
    {{/if}}
</script>

<html xmlns:mso="urn:schemas-microsoft-com:office:office" xmlns:msdt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882"><head>

<!--[if gte mso 9]><xml>
<mso:CustomDocumentProperties>
<mso:FF_AssetType msdt:dt="string"></mso:FF_AssetType>
<mso:FF_Enabled msdt:dt="string">1</mso:FF_Enabled>
</mso:CustomDocumentProperties>
</xml><![endif]-->
<title></title></head>
  1. There are 3 different templates for different scenarios:
    1. CreateTemplate, is used on FlowDesigner
    2. DisplayTemplate, is used on the Form, for read-only purposes
    3. EditTemplate, is used on the Form, for editing purposes
  2. Within each it is possible to use placeholders for question properties like , , or

Registering the question

  1. In order for FlowForma to be aware of your question, it needs to be registered. Here’s how to do it:
  2. Both the .js and the .html files need to be placed on the …/FlowFormaAssets/Templates/Questions folder; the only way to access it, it by appending the path to the your site’s address, on the browser.
    1. If your site’s address is https://acme/sites/hr/flowforma, you can access the FlowFormaAssets folder through https://acme/sites/hr/flowforma**/flowformaassets**
  3. Once the .js and the .html files have been copied, the Manifest file, located …/FlowFormaAssets/Templates, needs to be edited, and it should look like similar to this
    {
         "Version": "4.0",
         "QuestionTypes": [{
              "Type": "autonumber",
              "Title": "Auto Number",
              "FileName": "questions/autonumber.html"
    }], "RuleActions": [] }
    
  4. Your question definition goes into the QuestionTypes array; the following need to be present:
    1. Type: name of the rule that is extended to flowforma rules
    2. Title: title that will be displayed in the flow
    3. FileName: html file name
  5. The order by which the questions - and you can have several, separated by ”,” - are added determines the order in which they are displayed on the Flow Designer
  6. Once you reopen the Flow Designer, your question will be ready to be used

Further information

Check out the AutoNumber question as a reference - autonumber.zip