Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.ovea.js.dynamic-form.dynamic-form.js Maven / Gradle / Ivy
/*
* Copyright (C) 2011 Ovea
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
if (window.ovea == undefined) {
window.ovea = {};
}
if (ovea.DynamicForm == undefined) {
(function($) {
var FormElement = function(dynaform, keys) {
this.dynaform = dynaform;
this.keys = keys || [];
};
FormElement.prototype = {
toString: function() {
return this.keys.toString();
},
value: function(key) {
var json = this.values();
if (json) {
return json[key || this.keys[0]];
}
},
values: function() {
if (this.keys.length) {
return this.dynaform.toObject(this.keys[0], {
selector: ':input:enabled[name]'
});
}
},
onChange: function(func) {
this.dynaform.bind(this.keys, 'question.changed', func);
return this;
},
onInvalid: function(func) {
this.dynaform.bind(this.keys, 'question.invalid', func);
return this;
},
onAllValid: function(func) {
var valid = {};
for (var i in this.keys) {
valid[this.keys[i]] = false;
}
var df = this.dynaform;
df.bind(this.keys, 'question.valid', function(evt) {
valid[evt.key] = true;
for (var id in valid) {
if (valid[id] === false && !df.merges[id].has('[df-excluded]').length) {
return;
}
}
func.apply(this, arguments);
});
df.bind(this.keys, 'question.invalid', function(evt) {
valid[evt.key] = false;
});
return this;
},
onValid: function(func) {
this.dynaform.bind(this.keys, 'question.valid', func);
return this;
},
validate: function() {
return this.dynaform.validate(this.keys);
},
isValid: function() {
return this.dynaform.isValid(this.keys);
}
};
ovea.DynamicForm = function(options) {
this.elements = {};
this.merges = {};
this.templates = {};
this.renderings = {};
this.conditions = {};
this.eventHandlers = {};
this.specValidators = {};
this.validators = {};
this._renderingCount = 0;
this.options = $.extend({
specification: {},
choiceLimit: 4,
htmlGroup: '
'
}, options || {});
if (typeof this.options.specification == 'string') {
var self = this;
$.ajax({
url: self.options.specification,
dataType: 'json',
async: false,
cache: false,
success: function(spec) {
self.options.specification = spec;
},
error: function(xhr, error) {
if (console && console.error) {
console.error(error + " - url = " + self.options.specification, arguments);
}
}
});
}
/**
* TEMPLATES
**/
/* simple elements */
this.addTemplate('text', '{key}
');
this.addTemplate('range', '{key}
');
this.addTemplate('choice-radio-open', '{key}_other
');
this.addTemplate('choice-checkbox-open', '{key}_other
');
this.addTemplate('choice-list-open', '
');
/* containers*/
this.addTemplate('choice-list', '');
this.addTemplate('choice-flat', '');
this.addTemplate('choice-radio', '');
this.addTemplate('choice-checkbox', '');
/* contained elements */
this.addTemplate('option', '{value} ');
this.addTemplate('radio', ' ');
this.addTemplate('checkbox', ' ');
this.addTemplate('select', ' ');
/* for tables */
this.addTemplate(['choice-table', 'yesno-table'], '');
this.addTemplate('choice-table-header', ' ');
this.addTemplate('choice-table-row', '{key} ');
/**
* RENDERERS
**/
this.addRendering('*', function (data, template) {
if (template) {
return template.merge(data);
}
});
this.addRendering('question', function (data, template) {
var html;
try {
this.trigger(data.key, 'question.rendering.started', data);
html = this.renderBody(data, data.spec.type);
if (html) {
this.setupConditions(html, data);
return html;
}
} finally {
this.trigger(data.key, 'question.rendering.finished', {
data: data,
html: html ? html.data : undefined
});
}
});
this.addRendering(['choice', 'yesno'], function (data, template) {
if (data.spec.values === undefined && data.spec.type == 'yesno') {
data.spec.values = [true, false];
}
if (data.spec.values !== undefined) {
return this.renderBody(data, data.spec.values.length > this.options.choiceLimit ? 'choice-list' : 'choice-flat');
}
});
this.addRendering(['choice-radio', 'choice-checkbox'], function (data, template) {
var list = [];
for (var c in data.spec.values) {
var d = {
spec: data.spec,
key: data.key,
value: data.spec.values[c],
index: c
};
var el = template.merge(d);
el.container.append(this.renderBody(d, data.spec.unique ? 'radio' : 'checkbox').data);
list.push(el);
}
return list;
});
this.addRendering(['radio', 'checkbox'], function (data, template) {
var html = template.merge(data);
var el = html.data.filter('input');
if (!el.length) {
el = html.data.find('input');
}
if (data.spec.selected && $.inArray(data.value, data.spec.selected) != -1) {
el.attr('checked', true);
}
var self = this;
el.click(function() {
self.trigger(data.key, 'question.changed', {
spec: data.spec,
value: self.$(data.key).value(),
element: $(this)
});
if (data.tableKey) {
self.trigger(data.tableKey, 'question.changed', {
spec: data.spec,
value: self.$(data.key).value(),
element: $(this)
});
}
});
return html;
});
this.addRendering(['text', 'range'], function (data, template) {
var html = template.merge(data);
var el = html.data.filter(':input');
if (!el.length) {
el = html.data.find(':input');
}
if (data.spec.maxlength !== undefined) {
el.attr('maxlength', data.spec.maxlength);
}
if (data.spec.value !== undefined) {
el.val(data.spec.value);
}
var self = this;
el.change(function() {
var val = self.$(data.key).value();
var old = $(this).data('value-old');
$(this).data('value-old', val);
if (val != old) {
self.trigger(data.key, 'question.changed', {
spec: data.spec,
value: val,
element: $(this)
});
if (data.tableKey) {
self.trigger(data.tableKey, 'question.changed', {
spec: data.spec,
value: val,
element: $(this)
});
}
}
});
return html;
});
this.addRendering('choice-flat', function (data, template) {
var html = template.merge(data);
var els = this.renderBody(data, data.spec.unique ? 'choice-radio' : 'choice-checkbox').data;
var other, other_field;
if (!data.spec.closed) {
other = this.renderBody(data, 'choice-flat-open');
other_field = other.data.find('input.other-value');
}
for (var i in els) {
html.container.append(els[i].data);
if (other_field) {
this.$(data.key).onChange(function(evt, data) {
var spec = this.specification(evt.key);
if (data.value == "_other_" || $.isArray(data.value) && $.inArray('_other_', data.value) != -1) {
other_field.removeClass('hide').addClass('show');
} else {
other_field.removeClass('show').addClass('hide');
}
});
}
}
if (other) {
html.container.append(other.data);
}
return html;
});
this.addRendering('choice-flat-open', function (data, template) {
var html = this.renderBody(data, data.spec.unique ? 'choice-radio-open' : 'choice-checkbox-open');
var other_field = html.data.find('input.other-value');
var el = html.data.filter('input.other-choice');
if (!el.length) {
el = html.data.find('input.other-choice');
}
if (!data.spec.selected || $.inArray("_other_", data.spec.selected) == -1) {
other_field.removeClass('show').addClass('hide');
} else {
el.attr('checked', true);
}
var self = this;
el.click(function() {
self.trigger(data.key, 'question.changed', {
spec: data.spec,
value: self.$(data.key).value(),
element: $(this)
});
if (data.tableKey) {
self.trigger(data.tableKey, 'question.changed', {
spec: data.spec,
value: self.$(data.key).value(),
element: $(this)
});
}
});
return html;
});
this.addRendering('choice-list', function (data, template) {
var html = template.merge(data);
html.container.append(this.renderBody(data, 'select').data);
var select = html.container.find('select');
var self = this;
select.change(function() {
var val = self.$(data.key).value();
var old = $(this).data('value-old');
$(this).data('value-old', val);
if (val != old) {
self.trigger(data.key, 'question.changed', {
spec: data.spec,
value: val,
element: $(this)
});
if (data.tableKey) {
self.trigger(data.tableKey, 'question.changed', {
spec: data.spec,
value: val,
element: $(this)
});
}
}
});
if (!data.spec.closed && !data.spec.multiple && $.inArray("_other_", data.spec.values) != -1) {
var other = this.renderBody(data, 'choice-list-open');
var other_field = other.data.find('input.other-value');
this.$(data.key).onChange(function(evt, data) {
if ("_other_" == select.val()) {
other_field.removeClass('hide').addClass('show');
} else {
other_field.removeClass('show').addClass('hide');
}
});
if (data.spec.selected && $.inArray("_other_", data.spec.selected) != -1) {
select.val("_other_");
}
html.container.append(other.data);
}
return html;
});
this.addRendering(['choice-list-open', 'choice-radio-open', 'choice-checkbox-open'], function (data, template) {
var self = this;
var html = template.merge(data);
var other_field = html.data.find('input.other-value');
other_field.val(data.spec.value || "");
other_field.change(function() {
var val = other_field.val();
var old = $(this).data('value-old');
$(this).data('value-old', val);
if (val != old) {
self.trigger(data.key + '_other', 'question.changed', {
spec: data.spec,
value: val,
element: $(this)
});
if (data.tableKey) {
self.trigger(data.tableKey, 'question.changed', {
spec: data.spec,
value: val,
element: $(this)
});
}
}
});
if (data.spec.maxlength !== undefined) {
other_field.attr('maxlength', data.spec.maxlength);
}
if (!data.spec.selected || $.inArray("_other_", data.spec.selected) == -1) {
other_field.removeClass('show').addClass('hide');
}
//this.options.specification[data.key + '_other'] = this.options.specification[data.key];
//this.options.specification[data.key + '_other']['_other_parent'] = data.key;
return html;
});
this.addRendering('select', function (data, template) {
var html = template.merge(data);
if (!data.spec.unique) {
html.data.filter('select').attr('multiple', 'true');
html.data.find('select').attr('multiple', 'true');
}
if (!data.spec.selected) {
html.container.append(this.templates['option'].merge({
key: data.key,
value: '',
index: -1
}).data);
}
var els = this.renderBody(data, 'option').data;
for (var i in els) {
html.container.append(els[i].data);
}
return html;
});
this.addRendering('option', function (data, template) {
var list = [];
for (var c in data.spec.values) {
var el = template.merge({
key: data.key,
value: data.spec.values[c],
index: c
});
if (data.spec.selected && $.inArray(data.spec.values[c], data.spec.selected) != -1) {
el.data.attr('selected', true);
}
list.push(el);
}
return list;
});
this.addRendering(['choice-table', 'yesno-table'], function (data, template) {
var table = template.merge(data);
var header = table.data.find('thead tr');
var tbody = table.data.find('tbody');
var titles = this.renderBody(data, 'choice-table-header').data;
for (var i in titles) {
header.append(titles[i].data);
}
if (data.spec.rows) {
for (var key in data.spec.rows) {
tbody.append(this.renderBody({
spec: data.spec,
tableKey: data.key,
key: data.key + "_" + key,
row: key
}, 'choice-table-row').data);
}
}
return table;
});
this.addRendering('choice-table-header', function (data, template) {
var titles = [];
if (data.spec.type == 'yesno-table' && data.spec.values == undefined) {
data.spec.values = [true, false];
}
for (var i in data.spec.values) {
titles.push(template.merge({
spec: data.spec,
key: data.key,
value: data.spec.values[i],
index: i
}));
}
return titles;
});
this.addRendering('choice-table-row', function (data, template) {
var html = template.merge(data);
if (data.spec.type == 'yesno-table' && data.spec.values == undefined) {
data.spec.values = [true, false];
}
for (var i in data.spec.values) {
var el = this.renderBody({
spec: data.spec,
key: data.key,
tableKey: data.tableKey,
value: data.spec.values[i],
row: data.row,
index: i
}, data.spec.unique ? 'radio' : 'checkbox');
if (data.spec.rows[data.row].selected && $.inArray(data.spec.values[i], data.spec.rows[data.row].selected) != -1) {
el.data.find('input').attr('checked', true);
el.data.filter('input').attr('checked', true);
}
html.container.append($(' ').append(el.data));
}
//console.log(data.key);
// internal handling of table row components
html.container.internalBinding = true;
if (!this.options.specification[data.key]) {
this.options.specification[data.key] = data.spec.rows[data.row];
for (var p in data.spec) {
if (p != 'rows') {
this.options.specification[data.key][p] = data.spec[p];
}
}
this.options.specification[data.key].type = 'choice-table-row';
this.options.specification[data.key].table = data.spec;
this.options.specification[data.key].tableKey = data.tableKey;
}
this.addRenderedQuestion(data.key, html.container);
if (data.spec.rows[data.row].condition) {
this.setupConditions(html, {
spec: {
condition: data.spec.rows[data.row].condition
},
key: data.key
});
}
return html;
});
/**
* CONDITIONS: returns true, false or nothing if invalid
**/
this.addCondition('in', function(spec, value) {
if (spec !== undefined) {
return $.inArray(value, spec) != -1;
}
});
this.addCondition('is', function(expected, value) {
if (expected !== undefined) {
return value == expected;
}
});
this.addCondition('not', function(spec, value) {
if (spec !== undefined) {
return $.inArray(value, spec) == -1;
}
});
this.addCondition('min', function(spec, value) {
if (!isNaN(spec) && !isNaN(value)) {
return spec <= value;
}
});
this.addCondition('max', function(spec, value) {
if (!isNaN(spec) && !isNaN(value)) {
return spec >= value;
}
});
/**
* VALIDATORS
**/
// type validators
this.specValidators['range'] = function(key, spec) {
if (spec.min == undefined) return 'Missing "min" attribute';
if (spec.max == undefined) return 'Missing "max" attribute';
};
// attribute validators
this.addValidator('required', function(key, spec, data, param, val) {
if (param == 'true' || param === true) {
if (val == undefined || $.trim(val) == '') {
return true;
}
}
});
this.addValidator('format', function(key, spec, data, param, val) {
if (param == 'int' && !/^\d+$/.test(val)
|| param == 'float' && !/^\d+([.,]?\d+)?$/.test(val)) {
return spec.type != 'choice' || data[key] == '_other_';
}
});
this.addValidator('closed', function(key, spec, data, param, val) {
if ((param == 'true' || param === true) && $.inArray(val, spec.values) == -1) {
return true;
}
});
this.addValidator('maxlength', function(key, spec, data, param, val) {
if (('' + val).length > param) {
return true;
}
});
this.addValidator('minlength', function(key, spec, data, param, val) {
if (('' + val).length < param) {
return true;
}
});
this.addValidator('min', function(key, spec, data, param, val) {
if (param != undefined && val < param) {
return true;
}
});
this.addValidator('max', function(key, spec, data, param, val) {
if (param != undefined && val > param) {
return true;
}
});
this.addValidator('unique', function(key, spec, data, param, val) {
if ($.isArray(val)) {
return true;
}
});
/*
* INIT
* */
for (var k in this.options.specification) {
var type = this.options.specification[k].type;
if (!type) {
throw new Error('Attribute "type" not defined in specification of element "' + k + '"');
}
if (this.specValidators[type]) {
var err = this.specValidators[type].call(this, k, this.options.specification[k]);
if (err) {
throw new Error('Element "' + k + '" of type "' + type + '" is not valid: ' + err);
}
}
if (this.options.specification[k].rows && this.specValidators[type + '-row']) {
for (var r in this.options.specification[k].rows) {
var err = this.specValidators[type + '-row'].call(this, k, this.options.specification[k], r, this.options.specification[k][r]);
if (err) {
throw new Error('Element "' + k + '" of type "' + type + '" has an invalid row "' + r + '": ' + err);
}
}
}
}
};
ovea.DynamicForm.prototype = {
toString: function() {
return 'DynamicForm';
},
addTemplate: function(name, template) {
if ($.isArray(name)) {
for (var i in name) {
this.addTemplate(name[i], template);
}
} else {
this.templates[name] = {
merge: function(data) {
var obj = {
data: $(template.replace(/\{([\w\.]*)\}/g, function (str, key) {
var keys = key.split("."), value = data[keys.shift()];
$.each(keys, function () {
value = value[this];
});
return (value === null || value === undefined) ? "" : value;
}))
};
var container = obj.data.filter('.container');
if (container && container.length) {
obj.container = container;
} else {
container = obj.data.find('.container');
if (container && container.length) {
obj.container = container;
} else {
obj.container = obj.data;
}
}
return obj;
}
}
}
},
setupConditions: function(html, data) {
if (data.spec.condition) {
var showHide = function() {
for (var key in data.spec.condition) {
var json = this.$(key).values();
for (var cond in data.spec.condition[key]) {
if (this.conditions[cond]) {
var ret = this.conditions[cond].call(this, data.spec.condition[key][cond], json[key], key, data.spec);
if (ret === false) {
this.exclude(html.data, true);
return;
} else if (ret !== true) {
return;
}
}
}
}
this.include(html.data, true);
};
for (var k in data.spec.condition) {
this.$(k).onChange($.proxy(showHide, this));
}
this.bind('*', 'rendering.finished', $.proxy(showHide, this));
}
},
addCondition: function(name, func) {
this.conditions[name] = function() {
return func.apply(this, arguments);
};
this.conditions[name].toString = function() {
return 'Condition[' + name + ']';
};
},
addValidator: function(name, func) {
this.validators[name] = function() {
return func.apply(this, arguments);
};
this.validators[name].toString = function() {
return 'Validator[' + name + ']';
};
},
addRendering: function(name, func) {
if ($.isArray(name)) {
for (var i in name) {
this.addRendering(name[i], func);
}
} else {
this.renderings[name] = function() {
return func.apply(this, arguments);
};
this.renderings[name].toString = function() {
return 'Rendering[' + name + ']';
};
}
},
bind: function(key, name, func) {
if ($.isArray(key)) {
for (var i in key) {
this.bind(key[i], name, func);
}
} else if ($.isArray(name)) {
for (var i in name) {
this.bind(key, name[i], func);
}
} else {
if (!this.eventHandlers[key]) {
this.eventHandlers[key] = {};
}
if (!this.eventHandlers[key][name]) {
this.eventHandlers[key][name] = [];
}
this.eventHandlers[key][name].push(func);
}
},
trigger: function(key, name, data) {
if (this.eventHandlers[key] && this.eventHandlers[key][name]) {
for (var i in this.eventHandlers[key][name]) {
this.eventHandlers[key][name][i].call(this, {
key: key === '*' && arguments[3] ? arguments[3].key : key,
name: name === '*' && arguments[3] ? arguments[3].name : name
}, data);
}
}
if (!arguments[3]) {
if (key === '*' && name !== '*' || key !== '*' && name === '*') {
this.trigger('*', '*', data, {key: key, name: name});
} else if (key !== '*' && name !== '*') {
this.trigger('*', name, data, {key: key, name: name});
this.trigger(key, '*', data, {key: key, name: name});
this.trigger('*', '*', data, {key: key, name: name});
}
}
},
specification: function(key, failIfMissing) {
if (failIfMissing) {
if (!key) {
throw new Error('Invalid key: ' + key);
}
var k = this.options.specification[key]
if (!k) {
throw new Error('Inexisting key: ' + key);
}
return k;
}
return key ? this.options.specification[key] : this.options.specification;
},
keys: function() {
var keys = [];
for (var key in this.options.specification) {
keys.push(key);
}
keys.sort();
return keys;
},
render: function(obj) {
// no-arg call, wrap itself
if (obj === undefined) {
return this.render(function() {
return this.render(this.keys());
});
}
// arg-call with function
if (typeof obj === "function") {
try {
this._renderingCount++;
this.trigger('*', 'rendering.started');
return obj.call(this);
} finally {
this._renderingCount--;
if (this._renderingCount < 0) {
this._renderingCount = 0;
}
this.trigger('*', 'rendering.finished');
}
}
if (this._renderingCount === 0) {
throw new Error('Please call dynaform.render() or dynaform.render(function() { ... })');
}
// arg call with othe type
var keys = obj || this.keys();
var group = $(this.options.htmlGroup);
for (var i in keys) {
var key = keys[i];
var spec = this.specification(key);
if (!spec) {
throw new Error('You asked to render element "' + key + '" but there is no entry in specification.');
}
var el = this.renderBody({spec: spec, key: key}, 'question');
if (el && el.hasOwnProperty('data')) {
el = el.data;
}
this.addRenderedQuestion(key, el);
group.append(el);
}
return group;
},
renderBody: function(data, type) {
var func = this.renderings[type] || this.renderings['*'];
//console.log('[renderBody] ' + type, arguments, func);
var obj = func.call(this, data, this.templates[type]);
if (obj) {
if (!obj.hasOwnProperty('data')) {
obj = {
data: obj
}
}
if (obj.data instanceof jQuery) {
var container = obj.data.filter('.container');
if (container && container.length) {
obj.container = container;
} else {
container = obj.data.find('.container');
if (container && container.length) {
obj.container = container;
} else {
obj.container = obj.data;
}
}
}
}
//console.log('==> ', obj);
return obj;
},
addRenderedQuestion: function(key, el) {
if (key && el) {
this.merges[key] = el instanceof jQuery ? el : $(el);
// ensure no duplicate ids are created
var ids = {};
var errs = [];
for (var id in this.merges) {
if (!this.merges[id].internalBinding) {
this.merges[id].find("*[id]").each(function() {
if (ids[this.id]) {
errs.push('Duplicate element ID ' + this.id + ' found.\n');
} else {
ids[this.id] = true;
}
});
}
}
if (errs.length) {
throw new Error(errs);
}
// data
el.attr('df-el', true).data('df-key', key);
// setup conditions
if (this.specification(key)) {
this.setupConditions({
data: this.merges[key],
container: this.merges[key]
}, {
spec: this.specification(key),
key: key
});
}
}
},
inputKeys: function(o, opts) {
var options = $.extend({
selector: ':input:enabled[df-valid][name]'
}, opts || {});
var obj = o, json = {};
if (!o) {
for (var key in this.merges) {
json = $.extend(json, this.inputKeys(this.merges[key], opts));
}
return json;
} else if ($.isArray(o)) {
for (var i in o) {
json = $.extend(json, this.inputKeys(o[i], opts));
}
return json;
} else if (typeof o == "string") {
obj = this.merges[o];
} else {
obj = $(obj);
}
if (!obj) {
return {};
}
var els = obj.find(options.selector);
if (!els.length) {
els = obj.filter(options.selector);
}
els.each(function() {
json[this.name] = true;
});
return json;
},
toObject: function(o, opts) {
var options = $.extend({
selector: ':input:enabled[df-valid][name]:not([df-excluded])'
}, opts || {});
var obj = o, json = {};
if (!o) {
for (var key in this.merges) {
json = $.extend(json, this.toObject(this.merges[key], opts));
}
return json;
} else if ($.isArray(o)) {
for (var i in o) {
json = $.extend(json, this.toObject(o[i], opts));
}
return json;
} else if (typeof o == "string") {
obj = this.merges[o];
} else {
obj = $(obj);
}
if (!obj) {
return {};
}
var els = obj.find(options.selector);
if (!els.length) {
els = obj.filter(options.selector);
}
var self = this;
$.each(els.serializeArray(), function() {
var val = this.value;
if (val !== undefined) {
var spec = self.specification(this.name);
if (spec) {
if (spec.format == 'int') {
var n = parseInt(val);
if (!isNaN(n)) {
val = n;
}
} else if (spec.format == 'float') {
var f = parseFloat(val);
if (!isNaN(f)) {
val = f;
}
} else if (spec.type == 'yesno' || (spec.table && spec.table.type == 'yesno-table')) {
val = val === 'true';
}
}
}
if (json[this.name] !== undefined) {
if (!json[this.name].push) {
json[this.name] = [json[this.name]];
}
json[this.name].push(val);
} else if (this.value !== '') {
json[this.name] = val;
}
});
// force array for multiple selects
els.filter('select[multiple]').each(function() {
if (json[this.name] !== undefined && !json[this.name].push) {
json[this.name] = [json[this.name]];
}
});
// force array for multiple checkboxes with same name
var counts = {};
els.filter(':checkbox').each(function() {
counts[this.name] = (counts[this.name] || 0) + 1
});
for (var name in counts) {
if (counts[name] > 1 && json[name] !== undefined && !json[name].push) {
json[name] = [json[name]];
}
}
return json;
},
fillForm: function(values) {
if (values) {
for (var key in values) {
//TODO: FIXME: improve selector
var inputs = $(':input[name=' + key + ']');
if (inputs.length) {
var input = inputs.get(0);
switch (input.nodeName.toLowerCase()) {
case 'select':
{
var vals = values[key];
if (!$.isArray(vals)) {
vals = [vals];
}
inputs.find('option').filter(
function() {
return $.inArray(this.value, vals) != -1;
}).attr('selected', true).trigger('click');
inputs.trigger('change');
break;
}
case 'input':
{
switch (input.type.toLowerCase()) {
case 'text':
{
inputs.val(values[key]).trigger('change');
break;
}
case 'radio':
{
inputs.filter('[value=' + values[key] + ']').attr('checked', true).trigger('click').trigger('change');
break;
}
case 'checkbox':
{
var vals = values[key];
if (!$.isArray(vals)) {
vals = [vals];
}
inputs.filter(
function() {
return $.inArray(this.value, vals) != -1;
}).attr('checked', true).trigger('click').trigger('change');
break;
}
}
}
}
}
}
}
},
$: function(obj) {
var keys = obj;
if (!$.isArray(obj)) {
keys = [];
for (var i in arguments) {
keys.push(arguments[i]);
}
}
return new FormElement(this, keys);
},
excludes: function(selector, hide) {
return this._exclude(selector, hide);
},
exclude: function(selector, hide) {
return this._exclude(selector, hide, true);
},
_exclude: function(selector, hide, force) {
var s = selector instanceof jQuery ? selector : $(selector);
var s1 = s.filter('[df-el], :input:enabled[name]').removeAttr('df-valid');
var s2 = s.find('[df-el], :input:enabled[name]').removeAttr('df-valid');
if (force) {
s1.attr('df-excluded', true);
s2.attr('df-excluded', true);
}
if (hide !== false) {
s.removeClass('show').addClass('hide');
}
},
includes: function(selector, show) {
return this._include(selector, show);
},
include: function(selector, show) {
return this._include(selector, show, true);
},
_include: function(selector, show, force) {
var s = selector instanceof jQuery ? selector : $(selector);
if (force) {
s.filter('[df-el],:input:enabled[name]').attr('df-valid', true).removeAttr('df-excluded');
s.find('[df-el],:input:enabled[name]').attr('df-valid', true).removeAttr('df-excluded');
} else {
s.filter('[df-el]:not([df-excluded]),:input:enabled[name]:not([df-excluded])').attr('df-valid', true);
s.find('[df-el]:not([df-excluded]),:input:enabled[name]:not([df-excluded])').attr('df-valid', true);
}
if (show !== false) {
s.removeClass('hide').addClass('show');
}
},
isValid: function(o) {
return this.validate(o, true);
},
validate: function(o, preventCallbacks) {
var json = {}, keys = {};
if (o == undefined) {
keys = this.inputKeys();
json = this.toObject();
} else if (o instanceof jQuery) {
json = this.toObject(o);
o.filter('[df-el][df-valid]').each(function() {
keys[$(this).data('df-key')] = true;
});
o.find('[df-el][df-valid]').each(function() {
keys[$(this).data('df-key')] = true;
});
} else if ($.isArray(o)) {
for (var i in o) {
keys[o[i]] = true;
}
json = this.toObject(o);
} else if (typeof o == 'string') {
keys[o] = true;
json = this.toObject(o);
} else {
throw new Error('unsupported type', o);
}
// removes excluded keys
for (var k in keys) {
if (this.merges[k] && this.merges[k].has('[df-excluded]').length) {
delete keys[k];
}
}
//console.log(keys);
//console.log(json);
function runValidator(validator, key, spec, json, preventCallbacks) {
if (this.validators[validator]) {
//console.log(arguments);
var val = json[key] == '_other_' ? json[key + '_other'] : json[key];
var err = this.validators[validator].call(this, key, spec, json, spec[validator], val);
if (err && spec.tableKey) {
this.specification(spec.tableKey).rows[key.substring(spec.tableKey.length + 1)]['_valid'] = false;
}
if (err && !preventCallbacks) {
this.trigger(key, 'question.invalid', {
spec: spec,
data: json,
element: this.merges[key],
validator: validator,
param: spec[validator],
err: err
});
}
return err;
}
}
var valid = true;
for (var key in keys) {
var spec = this.specification(key);
if (spec) {
var thisValid = true;
var tableValid = false;
if ($.inArray(spec.type, ['choice-table', 'yesno-table']) != -1) {
var subKeys = [];
for (var r in spec.rows) {
subKeys.push(key + '_' + r);
}
thisValid = tableValid = this.validate(subKeys, preventCallbacks);
} else {
if (spec['required'] && runValidator.call(this, 'required', key, spec, json, preventCallbacks)) {
thisValid = false;
}
if (thisValid) {
if (spec['format'] && runValidator.call(this, 'format', key, spec, json, preventCallbacks)) {
thisValid = false;
}
}
if (thisValid) {
for (var validator in spec) {
if (validator != 'required' && validator != 'format' && runValidator.call(this, validator, key, spec, json, preventCallbacks)) {
thisValid = false;
}
}
}
}
valid &= thisValid;
if (valid && !preventCallbacks) {
this.trigger(key, 'question.valid', {
spec: spec,
data: json,
element: this.merges[key]
});
if (spec.tableKey) {
this.specification(spec.tableKey).rows[key.substring(spec.tableKey.length + 1)]['_valid'] = true;
tableValid = true;
var rows = this.specification(spec.tableKey).rows;
for (var row in rows) {
if (!rows[row]['_valid']) {
tableValid = false;
break;
}
}
}
if (tableValid) {
this.trigger(spec.tableKey, 'question.valid', {
spec: spec.table,
data: json,
element: this.merges[spec.tableKey],
validator: validator,
param: spec[validator]
});
}
}
}
}
return valid;
}
}
})(jQuery);
}