Source: fields/basic/CheckBoxField.js

(function($) {

    var Alpaca = $.alpaca;

    Alpaca.Fields.CheckBoxField = Alpaca.ControlField.extend(
        /**
         * @lends Alpaca.Fields.CheckBoxField.prototype
         */
        {
            /**
             * @see Alpaca.Field#getFieldType
             */
            getFieldType: function() {
                return "checkbox";
            },

            /**
             * @see Alpaca.Field#setup
             */
            setup: function() {

                var self = this;

                self.base();

                if (typeof(self.options.multiple) == "undefined")
                {
                    if (self.schema.type === "array")
                    {
                        self.options.multiple = true;
                    }
                    else if (typeof(self.schema["enum"]) !== "undefined")
                    {
                        self.options.multiple = true;
                    }
                }

                if (self.options.multiple)
                {
                    // multiple mode

                    self.checkboxOptions = [];

                    // if we have enum values, copy them into checkbox options
                    if (self.getEnum())
                    {
                        // sort the enumerated values
                        self.sortEnum();

                        var optionLabels = self.getOptionLabels();

                        $.each(self.getEnum(), function (index, value) {

                            var text = value;
                            if (optionLabels)
                            {
                                if (!Alpaca.isEmpty(optionLabels[index]))
                                {
                                    text = optionLabels[index];
                                }
                                else if (!Alpaca.isEmpty(optionLabels[value]))
                                {
                                    text = optionLabels[value];
                                }
                            }

                            self.checkboxOptions.push({
                                "value": value,
                                "text": text
                            });
                        });
                    }

                    // if they provided "datasource", we copy to "dataSource"
                    if (self.options.datasource && !self.options.dataSource) {
                        self.options.dataSource = self.options.datasource;
                        delete self.options.datasource;
                    }

                    // we optionally allow the data source return values to override the schema and options
                    if (typeof(self.options.useDataSourceAsEnum) === "undefined")
                    {
                        self.options.useDataSourceAsEnum = true;
                    }
                }
                else
                {
                    // single mode

                    if (!this.options.rightLabel) {
                        this.options.rightLabel = "";
                    }
                }
            },

            prepareControlModel: function(callback)
            {
                var self = this;

                this.base(function(model) {

                    if (self.checkboxOptions)
                    {
                        model.checkboxOptions = self.checkboxOptions;
                    }

                    callback(model);
                });
            },

            /**
             * @OVERRIDE
             */
            getEnum: function()
            {
                var values = this.base();
                if (!values)
                {
                    if (this.schema && this.schema.items && this.schema.items.enum)
                    {
                        values = this.schema.items.enum;
                    }
                }

                return values;
            },

            /**
             * @OVERRIDE
             */
            getOptionLabels: function()
            {
                var values = this.base();
                if (!values)
                {
                    if (this.options && this.options.items && this.options.items.optionLabels)
                    {
                        values = this.options.items.optionLabels;
                    }
                }

                return values;
            },

            /**
             * Handler for the event that the checkbox is clicked.
             *
             * @param e Event.
             */
            onClick: function(e)
            {
                this.refreshValidationState();
            },

            /**
             * @see Alpaca.ControlField#beforeRenderControl
             */
            beforeRenderControl: function(model, callback)
            {
                var self = this;

                this.base(model, function() {

                    if (self.options.dataSource)
                    {
                        // switch to multiple mode
                        self.options.multiple = true;

                        if (!self.checkboxOptions) {
                            model.checkboxOptions = self.checkboxOptions = [];
                        }

                        // clear the array
                        self.checkboxOptions.length = 0;

                        self.invokeDataSource(self.checkboxOptions, model, function(err) {

                            if (self.options.useDataSourceAsEnum)
                            {
                                // now build out the enum and optionLabels
                                var _enum = [];
                                var _optionLabels = [];
                                for (var i = 0; i < self.checkboxOptions.length; i++)
                                {
                                    _enum.push(self.checkboxOptions[i].value);
                                    _optionLabels.push(self.checkboxOptions[i].text);
                                }

                                self.setEnum(_enum);
                                self.setOptionLabels(_optionLabels);
                            }

                            callback();
                        });
                    }
                    else
                    {
                        callback();
                    }

                });
            },


            /**
             * @see Alpaca.ControlField#postRender
             */
            postRender: function(callback) {

                var self = this;

                this.base(function() {

                    // do this little trick so that if we have a default value, it gets set during first render
                    // this causes the checked state of the control to update
                    if (self.data && typeof(self.data) !== "undefined")
                    {
                        self.setValue(self.data);
                    }

                    // for multiple mode, mark values
                    if (self.options.multiple)
                    {
                        // none checked
                        $(self.getFieldEl()).find("input:checkbox").prop("checked", false);

                        if (self.data)
                        {
                            var dataArray = self.data;
                            if (typeof(self.data) === "string")
                            {
                                dataArray = self.data.split(",");
                                for (var a = 0; a < dataArray.length; a++)
                                {
                                    dataArray[a] = $.trim(dataArray[a]);
                                }
                            }

                            for (var k in dataArray)
                            {
                                $(self.getFieldEl()).find("input:checkbox[data-checkbox-value=\"" + dataArray[k] + "\"]").prop("checked", true);
                            }
                        }
                    }

                    // single mode

                    // whenever the state of one of our input:checkbox controls is changed (either via a click or programmatically),
                    // we signal to the top-level field to fire up a change
                    //
                    // this allows the dependency system to recalculate and such
                    //
                    $(self.getFieldEl()).find("input:checkbox").change(function(evt) {
                        self.triggerWithPropagation("change");
                    });

                    callback();
                });
            },

            /**
             * @see Alpaca.Field#getValue
             */
            getControlValue: function()
            {
                var self = this;

                var value = null;

                if (!self.options.multiple)
                {
                    // single scalar value
                    var input = $(self.getFieldEl()).find("input");
                    if (input.length > 0)
                    {
                        value = Alpaca.checked($(input[0]));
                    }
                    else
                    {
                        value = false;
                    }
                }
                else
                {
                    // multiple values
                    var values = [];
                    for (var i = 0; i < self.checkboxOptions.length; i++)
                    {
                        var inputField = $(self.getFieldEl()).find("input[data-checkbox-index='" + i + "']");
                        if (Alpaca.checked(inputField))
                        {
                            var v = $(inputField).attr("data-checkbox-value");
                            values.push(v);
                        }
                    }

                    // determine how we're going to hand this value back

                    // if type == "array", we just hand back the array
                    // if type == "string", we build a comma-delimited list
                    if (self.schema.type === "array")
                    {
                        value = values;
                    }
                    else if (self.schema.type === "string")
                    {
                        value = values.join(",");
                    }
                }

                return value;
            },

            /**
             * @see Alpaca.Field#setValue
             */
            setValue: function(value)
            {
                var self = this;

                // value can be a boolean, string ("true"), string ("a,b,c") or an array of values

                var applyScalarValue = function(value)
                {
                    if (Alpaca.isString(value)) {
                        value = (value === "true");
                    }

                    var input = $(self.getFieldEl()).find("input");
                    if (input.length > 0)
                    {
                        Alpaca.checked($(input[0]), value);
                    }
                };

                var applyMultiValue = function(values)
                {
                    // allow for comma-delimited strings
                    if (typeof(values) === "string")
                    {
                        values = values.split(",");
                    }

                    // trim things to remove any excess white space
                    for (var i = 0; i < values.length; i++)
                    {
                        values[i] = Alpaca.trim(values[i]);
                    }

                    // walk through values and assign into appropriate inputs
                    Alpaca.checked($(self.getFieldEl()).find("input[data-checkbox-value]"), false);
                    for (var j = 0; j < values.length; j++)
                    {
                        var input = $(self.getFieldEl()).find("input[data-checkbox-value=\"" + values[j] + "\"]");
                        if (input.length > 0)
                        {
                            Alpaca.checked($(input[0]), value);
                        }
                    }
                };

                var applied = false;

                if (!self.options.multiple)
                {
                    // single value mode

                    // boolean
                    if (typeof(value) === "boolean")
                    {
                        applyScalarValue(value);
                        applied = true;
                    }
                    else if (typeof(value) === "string")
                    {
                        applyScalarValue(value);
                        applied = true;
                    }
                }
                else
                {
                    // multiple value mode

                    if (typeof(value) === "string")
                    {
                        applyMultiValue(value);
                        applied = true;
                    }
                    else if (Alpaca.isArray(value))
                    {
                        applyMultiValue(value);
                        applied = true;
                    }
                }

                if (!applied && value)
                {
                    Alpaca.logError("CheckboxField cannot set value for schema.type=" + self.schema.type + " and value=" + value);
                }

                // be sure to call into base method
                this.base(value);
            },

            /**
             * Validate against enum property in the case that the checkbox field is in multiple mode.
             *
             * @returns {Boolean} True if the element value is part of the enum list, false otherwise.
             */
            _validateEnum: function()
            {
                var self = this;

                if (!self.options.multiple)
                {
                    return true;
                }

                var val = self.getValue();
                if (!self.isRequired() && Alpaca.isValEmpty(val))
                {
                    return true;
                }

                // if val is a string, convert to array
                if (typeof(val) === "string")
                {
                    val = val.split(",");
                }

                return Alpaca.anyEquality(val, self.getEnum());
            },

            /**
             * @see Alpaca.Field#disable
             */
            disable: function()
            {
                $(this.control).find("input").each(function() {
                    $(this).disabled = true;
                    $(this).prop("disabled", true);
                });
            },

            /**
             * @see Alpaca.Field#enable
             */
            enable: function()
            {
                $(this.control).find("input").each(function() {
                    $(this).disabled = false;
                    $(this).prop("disabled", false);
                });
            },

            /**
             * @see Alpaca.Field#getType
             */
            getType: function() {
                return "boolean";
            },


            /* builder_helpers */

            /**
             * @see Alpaca.Field#getTitle
             */
            getTitle: function() {
                return "Checkbox Field";
            },

            /**
             * @see Alpaca.Field#getDescription
             */
            getDescription: function() {
                return "Checkbox Field for boolean (true/false), string ('true', 'false' or comma-delimited string of values) or data array.";
            },

            /**
             * @private
             * @see Alpaca.ControlField#getSchemaOfOptions
             */
            getSchemaOfOptions: function() {
                return Alpaca.merge(this.base(), {
                    "properties": {
                        "rightLabel": {
                            "title": "Option Label",
                            "description": "Optional right-hand side label for single checkbox field.",
                            "type": "string"
                        },
                        "multiple": {
                            "title": "Multiple",
                            "description": "Whether to render multiple checkboxes for multi-valued type (such as an array or a comma-delimited string)",
                            "type": "boolean"
                        },
                        "dataSource": {
                            "title": "Option DataSource",
                            "description": "Data source for generating list of options.  This can be a string or a function.  If a string, it is considered to be a URI to a service that produces a object containing key/value pairs or an array of elements of structure {'text': '', 'value': ''}.  This can also be a function that is called to produce the same list.",
                            "type": "string"
                        },
                        "useDataSourceAsEnum": {
                            "title": "Use Data Source as Enumerated Values",
                            "description": "Whether to constrain the field's schema enum property to the values that come back from the data source.",
                            "type": "boolean",
                            "default": true
                        }
                    }
                });
            },

            /**
             * @private
             * @see Alpaca.ControlField#getOptionsForOptions
             */
            getOptionsForOptions: function() {
                return Alpaca.merge(this.base(), {
                    "fields": {
                        "rightLabel": {
                            "type": "text"
                        },
                        "multiple": {
                            "type": "checkbox"
                        },
                        "dataSource": {
                            "type": "text"
                        }
                    }
                });
            }

            /* end_builder_helpers */

        });

    Alpaca.registerFieldClass("checkbox", Alpaca.Fields.CheckBoxField);
    Alpaca.registerDefaultSchemaFieldMapping("boolean", "checkbox");

})(jQuery);