alpaca
Version:
Alpaca provides the easiest and fastest way to generate interactive forms for the web and mobile devices. It runs simply as HTML5 or more elaborately using Bootstrap, jQuery Mobile or jQuery UI. Alpaca uses Handlebars to process JSON schema and provide
539 lines (456 loc) • 18.5 kB
JavaScript
(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);