From 6c99960023c9d26ce540c077a129f52fc3f84dfd Mon Sep 17 00:00:00 2001 From: jkareliya Date: Fri, 17 Sep 2021 16:01:25 +0530 Subject: [PATCH 01/13] HUB-3750 added changes on bid adjustment option --- lib/building.js | 224 +++++++++++++++++ lib/formbuilder.js | 47 ++++ lib/globals.js | 35 +++ lib/modelBase.js | 417 ++++++++++++++++++++++++++++++++ lib/modelField.js | 528 +++++++++++++++++++++++++++++++++++++++++ lib/modelFieldDate.js | 44 ++++ lib/modelFieldImage.js | 101 ++++++++ lib/modelFieldTree.js | 43 ++++ lib/modelGroup.js | 409 +++++++++++++++++++++++++++++++ lib/modelOption.js | 31 +++ src/modelField.coffee | 97 ++++++-- src/modelOption.coffee | 3 +- 12 files changed, 1953 insertions(+), 26 deletions(-) create mode 100644 lib/building.js create mode 100644 lib/formbuilder.js create mode 100644 lib/globals.js create mode 100644 lib/modelBase.js create mode 100644 lib/modelField.js create mode 100644 lib/modelFieldDate.js create mode 100644 lib/modelFieldImage.js create mode 100644 lib/modelFieldTree.js create mode 100644 lib/modelGroup.js create mode 100644 lib/modelOption.js diff --git a/lib/building.js b/lib/building.js new file mode 100644 index 0000000..807a3e5 --- /dev/null +++ b/lib/building.js @@ -0,0 +1,224 @@ +var CoffeeScript, ModelGroup, Mustache, _, globals, jiff, throttledAlert, vm; + +CoffeeScript = require('coffee-script'); + +Mustache = require('mustache'); + +_ = require('underscore'); + +vm = require('vm'); + +jiff = require('jiff'); + +globals = require('./globals'); + +ModelGroup = require('./modelGroup'); + +globals = require('./globals'); + +if (typeof alert !== "undefined" && alert !== null) { + throttledAlert = _.throttle(alert, 500); +} + + +/* + An array of functions that can test a built model. + Model code may add tests to this array during build. The tests themselves will not be run at the time, but are + made avaiable via this export so processes can run the tests when appropriate. + Tests may modify the model state, so the model should be rebuilt prior to running each test. + */ + +exports.modelTests = []; + +exports.fromCode = function(code, data, element, imports, isImport) { + var assert, emit, newRoot, test; + data = (function() { + switch (typeof data) { + case 'object': + return jiff.clone(data); + case 'string': + return JSON.parse(data); + default: + return {}; + } + })(); + globals.runtime = false; + exports.modelTests = []; + test = function(func) { + return exports.modelTests.push(func); + }; + assert = function(bool, message) { + if (message == null) { + message = "A model test has failed"; + } + if (!bool) { + return globals.handleError(message); + } + }; + emit = function(name, context) { + if (element && $) { + return element.trigger($.Event(name, context)); + } + }; + newRoot = new ModelGroup(); + newRoot.recalculating = false; + newRoot.recalculateCycle = function() {}; + (function(root) { + var field, group, sandbox, validate; + field = newRoot.field.bind(newRoot); + group = newRoot.group.bind(newRoot); + root = newRoot.root; + validate = newRoot.validate; + if (typeof window === "undefined" || window === null) { + sandbox = { + field: field, + group: group, + root: root, + validate: validate, + data: data, + imports: imports, + test: test, + assert: assert, + Mustache: Mustache, + emit: emit, + _: _, + console: { + log: function() {}, + error: function() {} + }, + print: function() {} + }; + return vm.runInNewContext('"use strict";' + code, sandbox); + } else { + return eval('"use strict";' + code); + } + })(null); + newRoot.postBuild(); + globals.runtime = true; + newRoot.applyData(data); + newRoot.getChanges = exports.getChanges.bind(null, newRoot); + newRoot.setDirty(newRoot.id, 'multiple'); + newRoot.recalculateCycle = function() { + var results; + results = []; + while (!this.recalculating && this.dirty) { + this.recalculating = true; + this.recalculateRelativeProperties(); + results.push(this.recalculating = false); + } + return results; + }; + newRoot.recalculateCycle(); + newRoot.on('change:isValid', function() { + if (!isImport) { + return emit('validate', { + isValid: newRoot.isValid + }); + } + }); + newRoot.on('recalculate', function() { + if (!isImport) { + return emit('change'); + } + }); + newRoot.trigger('change:isValid'); + newRoot.trigger('recalculate'); + newRoot.styles = false; + return newRoot; +}; + +exports.fromCoffee = function(code, data, element, imports, isImport) { + return exports.fromCode(CoffeeScript.compile(code), data, element, imports, isImport); +}; + +exports.fromPackage = function(pkg, data, element) { + var buildModelWithRecursiveImports; + buildModelWithRecursiveImports = function(p, el, isImport) { + var buildImport, builtImports, f, form; + form = ((function() { + var i, len, ref, results; + ref = p.forms; + results = []; + for (i = 0, len = ref.length; i < len; i++) { + f = ref[i]; + if (f.formid === p.formid) { + results.push(f); + } + } + return results; + })())[0]; + if (form == null) { + return; + } + builtImports = {}; + buildImport = function(impObj) { + return builtImports[impObj.namespace] = buildModelWithRecursiveImports({ + formid: impObj.importformid, + data: data, + forms: p.forms + }, element, true); + }; + if (form.imports) { + form.imports.forEach(buildImport); + } + return exports.fromCoffee(form.model, data, el, builtImports, isImport); + }; + if (typeof pkg.formid === 'string') { + pkg.formid = parseInt(pkg.formid); + } + data = _.extend(pkg.data || {}, data || {}); + return buildModelWithRecursiveImports(pkg, element, false); +}; + +exports.getChanges = function(modelAfter, beforeData) { + var after, before, changedPath, changedPaths, changedPathsUniqObject, changedPathsUnique, changes, i, internalPatch, j, key, len, len1, modelBefore, outputPatch, p, path, val; + modelBefore = modelAfter.cloneModel(); + modelBefore.applyData(beforeData, true); + internalPatch = jiff.diff(modelBefore.buildOutputData(void 0, true), modelAfter.buildOutputData(void 0, true), { + invertible: false + }); + outputPatch = jiff.diff(modelBefore.buildOutputData(), modelAfter.buildOutputData(), { + invertible: false + }); + changedPaths = (function() { + var i, len, results; + results = []; + for (i = 0, len = internalPatch.length; i < len; i++) { + p = internalPatch[i]; + results.push(p.path.replace(/\/[0-9]+$/, '')); + } + return results; + })(); + changedPathsUniqObject = {}; + for (i = 0, len = changedPaths.length; i < len; i++) { + val = changedPaths[i]; + changedPathsUniqObject[val] = val; + } + changedPathsUnique = (function() { + var results; + results = []; + for (key in changedPathsUniqObject) { + results.push(key); + } + return results; + })(); + changes = []; + for (j = 0, len1 = changedPathsUnique.length; j < len1; j++) { + changedPath = changedPathsUnique[j]; + path = changedPath.slice(1); + before = modelBefore.child(path); + after = modelAfter.child(path); + if (!_.isEqual(before != null ? before.value : void 0, after != null ? after.value : void 0)) { + changes.push({ + name: changedPath, + title: after.title, + before: before.buildOutputData(void 0, true), + after: after.buildOutputData(void 0, true) + }); + } + } + return { + changes: changes, + patch: outputPatch + }; +}; diff --git a/lib/formbuilder.js b/lib/formbuilder.js new file mode 100644 index 0000000..664035c --- /dev/null +++ b/lib/formbuilder.js @@ -0,0 +1,47 @@ +var Backbone, ModelBase, building, globals; + +if (typeof window !== "undefined" && window !== null) { + window.formbuilder = exports; +} + +Backbone = require('backbone'); + +ModelBase = require('./modelBase'); + +building = require('./building'); + +globals = require('./globals'); + +exports.fromCode = building.fromCode; + +exports.fromCoffee = building.fromCoffee; + +exports.fromPackage = building.fromPackage; + +exports.getChanges = building.getChanges; + +exports.mergeData = globals.mergeData; + +exports.applyData = function(modelObject, inData, clear, purgeDefaults) { + return modelObject.applyData(inData, clear, purgeDefaults); +}; + +exports.buildOutputData = function(model) { + return model.buildOutputData(); +}; + +Object.defineProperty(exports, 'modelTests', { + get: function() { + return building.modelTests; + } +}); + +Object.defineProperty(exports, 'handleError', { + get: function() { + return globals.handleError; + }, + set: function(f) { + return globals.handleError = f; + }, + enumerable: true +}); diff --git a/lib/globals.js b/lib/globals.js new file mode 100644 index 0000000..d301218 --- /dev/null +++ b/lib/globals.js @@ -0,0 +1,35 @@ +module.exports = { + runtime: false, + handleError: function(err) { + if (!(err instanceof Error)) { + err = new Error(err); + } + throw err; + }, + makeErrorMessage: function(model, propName, err) { + var nameStack, node, stack; + stack = []; + node = model; + while (node.name != null) { + stack.push(node.name); + node = node.parent; + } + stack.reverse(); + nameStack = stack.join('.'); + return "The '" + propName + "' function belonging to the field named '" + nameStack + "' threw an error with the message '" + err.message + "'"; + }, + mergeData: function(a, b) { + var key, value; + if ((b != null ? b.constructor : void 0) === Object) { + for (key in b) { + value = b[key]; + if ((a[key] != null) && a[key].constructor === Object && (value != null ? value.constructor : void 0) === Object) { + module.exports.mergeData(a[key], value); + } else { + a[key] = value; + } + } + } + return a; + } +}; diff --git a/lib/modelBase.js b/lib/modelBase.js new file mode 100644 index 0000000..1a0ab51 --- /dev/null +++ b/lib/modelBase.js @@ -0,0 +1,417 @@ + +/* + * Attributes common to groups and fields. + */ +var Backbone, ModelBase, Mustache, _, getBoolOrFunctionResult, globals, moment, newid, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty, + indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + +Backbone = require('backbone'); + +_ = require('underscore'); + +globals = require('./globals'); + +moment = require('moment'); + +Mustache = require('mustache'); + +newid = (function() { + var incId; + incId = 0; + return function() { + incId++; + return "fbid_" + incId; + }; +})(); + + +/* Some properties may be booleans or functions that return booleans + Use this function to determine final boolean value. + prop - the property to evaluate, which may be something primitive or a function + deflt - the value to return if the property is undefined + */ + +getBoolOrFunctionResult = function(prop, deflt) { + if (deflt == null) { + deflt = true; + } + if (typeof prop === 'function') { + return !!prop(); + } + if (prop === void 0) { + return deflt; + } + return !!prop; +}; + +module.exports = ModelBase = (function(superClass) { + extend(ModelBase, superClass); + + function ModelBase() { + return ModelBase.__super__.constructor.apply(this, arguments); + } + + ModelBase.prototype.modelClassName = 'ModelBase'; + + ModelBase.prototype.initialize = function() { + var fn, key, ref, val; + this.setDefault('visible', true); + this.set('isVisible', true); + this.setDefault('disabled', false); + this.set('isDisabled', false); + this.setDefault('onChangePropertiesHandlers', []); + this.set('id', newid()); + this.setDefault('parent', void 0); + this.setDefault('root', void 0); + this.setDefault('name', this.get('title')); + this.setDefault('title', this.get('name')); + ref = this.attributes; + fn = (function(_this) { + return function(key) { + return Object.defineProperty(_this, key, { + get: function() { + return this.get(key); + }, + set: function(newValue) { + if ((this.get(key)) !== newValue) { + return this.set(key, newValue); + } + } + }); + }; + })(this); + for (key in ref) { + val = ref[key]; + fn(key); + } + this.bindPropFunctions('visible'); + this.bindPropFunctions('disabled'); + this.makePropArray('onChangePropertiesHandlers'); + this.bindPropFunctions('onChangePropertiesHandlers'); + return this.on('change', function() { + var ch, changeFunc, i, len, ref1; + if (!globals.runtime) { + return; + } + ref1 = this.onChangePropertiesHandlers; + for (i = 0, len = ref1.length; i < len; i++) { + changeFunc = ref1[i]; + changeFunc(); + } + ch = this.changedAttributes(); + if (ch === false) { + ch = 'multiple'; + } + this.root.setDirty(this.id, ch); + return this.root.recalculateCycle(); + }); + }; + + ModelBase.prototype.postBuild = function() {}; + + ModelBase.prototype.setDefault = function(field, val) { + if (this.get(field) == null) { + return this.set(field, val); + } + }; + + ModelBase.prototype.text = function(message) { + return this.field(message, { + type: 'info' + }); + }; + + ModelBase.prototype.bindPropFunction = function(propName, func) { + var model; + model = this; + return function() { + var err, message; + try { + if (this instanceof ModelBase) { + model = this; + } + return func.apply(model, arguments); + } catch (error) { + err = error; + message = globals.makeErrorMessage(model, propName, err); + return globals.handleError(message); + } + }; + }; + + ModelBase.prototype.bindPropFunctions = function(propName) { + var i, index, ref, results; + if (Array.isArray(this[propName])) { + results = []; + for (index = i = 0, ref = this[propName].length; 0 <= ref ? i < ref : i > ref; index = 0 <= ref ? ++i : --i) { + results.push(this[propName][index] = this.bindPropFunction(propName, this[propName][index])); + } + return results; + } else if (typeof this[propName] === 'function') { + return this.set(propName, this.bindPropFunction(propName, this[propName]), { + silent: true + }); + } + }; + + ModelBase.prototype.makePropArray = function(propName) { + if (!Array.isArray(this.get(propName))) { + return this.set(propName, [this.get(propName)]); + } + }; + + ModelBase.prototype.buildParamObject = function(params, paramPositions) { + var i, key, len, param, paramIndex, paramObject, ref, val; + paramObject = {}; + paramIndex = 0; + for (i = 0, len = params.length; i < len; i++) { + param = params[i]; + if (((ref = typeof param) === 'string' || ref === 'number' || ref === 'boolean') || Array.isArray(param)) { + paramObject[paramPositions[paramIndex++]] = param; + } else if (Object.prototype.toString.call(param) === '[object Object]') { + for (key in param) { + val = param[key]; + paramObject[key] = val; + } + } + } + paramObject.parent = this; + paramObject.root = this.root; + return paramObject; + }; + + ModelBase.prototype.dirty = ''; + + ModelBase.prototype.setDirty = function(id, whatChanged) { + var ch, drt, keys; + ch = typeof whatChanged === 'string' ? whatChanged : (keys = Object.keys(whatChanged), keys.length === 1 ? id + ":" + keys[0] : 'multiple'); + drt = this.dirty === ch || this.dirty === '' ? ch : "multiple"; + return this.dirty = drt; + }; + + ModelBase.prototype.setClean = function() { + return this.dirty = ''; + }; + + ModelBase.prototype.shouldCallTriggerFunctionFor = function(dirty, attrName) { + return dirty && dirty !== (this.id + ":" + attrName); + }; + + ModelBase.prototype.recalculateRelativeProperties = function() { + var dirty; + dirty = this.dirty; + this.setClean(); + if (this.shouldCallTriggerFunctionFor(dirty, 'isVisible')) { + this.isVisible = getBoolOrFunctionResult(this.visible); + } + if (this.shouldCallTriggerFunctionFor(dirty, 'isDisabled')) { + this.isDisabled = getBoolOrFunctionResult(this.disabled, false); + } + return this.trigger('recalculate'); + }; + + ModelBase.prototype.onChangeProperties = function(f, trigger) { + if (trigger == null) { + trigger = true; + } + this.onChangePropertiesHandlers.push(this.bindPropFunction('onChangeProperties', f)); + if (trigger) { + this.trigger('change'); + } + return this; + }; + + ModelBase.prototype.validate = { + required: function(value) { + if (value == null) { + value = this.value || ''; + } + if (((function() { + switch (typeof value) { + case 'number': + case 'boolean': + return false; + case 'string': + return value.length === 0; + case 'object': + return Object.keys(value).length === 0; + default: + return true; + } + })())) { + return "This field is required"; + } + }, + minLength: function(n) { + return function(value) { + if (value == null) { + value = this.value || ''; + } + if (value.length < n) { + return "Must be at least " + n + " characters long"; + } + }; + }, + maxLength: function(n) { + return function(value) { + if (value == null) { + value = this.value || ''; + } + if (value.length > n) { + return "Can be at most " + n + " characters long"; + } + }; + }, + number: function(value) { + if (value == null) { + value = this.value || ''; + } + if (isNaN(+value)) { + return "Must be an integer or decimal number. (ex. 42 or 1.618)"; + } + }, + date: function(value, format) { + if (value == null) { + value = this.value || ''; + } + if (format == null) { + format = this.format; + } + if (value === '') { + return; + } + if (!moment(value, format, true).isValid()) { + return "Not a valid date or does not match the format " + format; + } + }, + email: function(value) { + if (value == null) { + value = this.value || ''; + } + if (!value.match(/^[a-z0-9!\#$%&'*+\/=?^_`{|}~-]+(?:\.[a-z0-9!\#$%&'*+\/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/)) { + return "Must be a valid email"; + } + }, + url: function(value) { + if (value == null) { + value = this.value || ''; + } + if (!value.match(/^(([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)\#?(?:[\w]*))?$/)) { + return "Must be a URL"; + } + }, + dollars: function(value) { + if (value == null) { + value = this.value || ''; + } + if (!value.match(/^\$(\d+\.\d\d|\d+)$/)) { + return "Must be a dollar amount (ex. $3.99)"; + } + }, + minSelections: function(n) { + return function(value) { + if (value == null) { + value = this.value || ''; + } + if (value.length < n) { + return "Please select at least " + n + " options"; + } + }; + }, + maxSelections: function(n) { + return function(value) { + if (value == null) { + value = this.value || ''; + } + if (value.length > n) { + return "Please select at most " + n + " options"; + } + }; + }, + selectedIsVisible: function(field) { + var i, len, opt, ref; + if (field == null) { + field = this; + } + ref = field.options; + for (i = 0, len = ref.length; i < len; i++) { + opt = ref[i]; + if (opt.selected && !opt.isVisible) { + return "A selected option is not currently available. Please make a new choice from available options."; + } + } + }, + template: function() { + var e, template; + if (!this.template) { + return; + } + if (typeof this.template === 'object') { + template = this.template.value; + } else { + template = this.parent.child(this.template).value; + } + try { + Mustache.render(template, this.root.data); + } catch (error) { + e = error; + return "Template field does not contain valid Mustache"; + } + } + }; + + ModelBase.prototype.cloneModel = function(newRoot, constructor, excludeAttributes) { + var childClone, filteredAttributes, i, key, len, modelObj, myClone, newVal, ref, ref1, val; + if (newRoot == null) { + newRoot = this.root; + } + if (constructor == null) { + constructor = this.constructor; + } + if (excludeAttributes == null) { + excludeAttributes = []; + } + filteredAttributes = {}; + ref = this.attributes; + for (key in ref) { + val = ref[key]; + if (indexOf.call(excludeAttributes, key) < 0) { + filteredAttributes[key] = val; + } + } + myClone = new constructor(filteredAttributes); + ref1 = myClone.attributes; + for (key in ref1) { + val = ref1[key]; + if (key === 'root') { + myClone.set(key, newRoot); + } else if (val instanceof ModelBase && (key !== 'root' && key !== 'parent')) { + myClone.set(key, val.cloneModel(newRoot)); + } else if (Array.isArray(val)) { + newVal = []; + if (val[0] instanceof ModelBase && key !== 'value') { + for (i = 0, len = val.length; i < len; i++) { + modelObj = val[i]; + childClone = modelObj.cloneModel(newRoot); + if (childClone.parent === this) { + childClone.parent = myClone; + if (key === 'options' && childClone.selected) { + myClone.addOptionValue(childClone.value); + } + } + newVal.push(childClone); + } + } else { + newVal = _.clone(val); + } + myClone.set(key, newVal); + } + } + return myClone; + }; + + return ModelBase; + +})(Backbone.Model); diff --git a/lib/modelField.js b/lib/modelField.js new file mode 100644 index 0000000..c482d77 --- /dev/null +++ b/lib/modelField.js @@ -0,0 +1,528 @@ +var ModelBase, ModelField, ModelOption, Mustache, globals, jiff, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty, + slice = [].slice; + +ModelBase = require('./modelBase'); + +ModelOption = require('./modelOption'); + +globals = require('./globals'); + +Mustache = require('mustache'); + +jiff = require('jiff'); + + +/* + A ModelField represents a model object that render as a DOM field + NOTE: The following field types are subclasses: image, tree, date + */ + +module.exports = ModelField = (function(superClass) { + extend(ModelField, superClass); + + function ModelField() { + return ModelField.__super__.constructor.apply(this, arguments); + } + + ModelField.prototype.modelClassName = 'ModelField'; + + ModelField.prototype.initialize = function() { + var ref2, ref3; + this.setDefault('type', 'text'); + this.setDefault('options', []); + this.setDefault('value', (function() { + switch (this.get('type')) { + case 'multiselect': + return []; + case 'bool': + return false; + case 'info': + case 'button': + return void 0; + default: + return (this.get('defaultValue')) || ''; + } + }).call(this)); + this.setDefault('defaultValue', this.get('value')); + this.set('isValid', true); + this.setDefault('validators', []); + this.setDefault('onChangeHandlers', []); + this.setDefault('dynamicValue', null); + this.setDefault('template', null); + this.setDefault('autocomplete', null); + this.setDefault('beforeInput', function(val) { + return val; + }); + this.setDefault('beforeOutput', function(val) { + return val; + }); + ModelField.__super__.initialize.apply(this, arguments); + if ((ref2 = this.type) !== 'info' && ref2 !== 'text' && ref2 !== 'url' && ref2 !== 'email' && ref2 !== 'tel' && ref2 !== 'time' && ref2 !== 'date' && ref2 !== 'textarea' && ref2 !== 'bool' && ref2 !== 'tree' && ref2 !== 'color' && ref2 !== 'select' && ref2 !== 'multiselect' && ref2 !== 'image' && ref2 !== 'button' && ref2 !== 'number') { + return globals.handleError("Bad field type: " + this.type); + } + this.bindPropFunctions('dynamicValue'); + while ((Array.isArray(this.value)) && (this.type !== 'multiselect') && (this.type !== 'tree') && (this.type !== 'button')) { + this.value = this.value[0]; + } + if (typeof this.value === 'string' && (this.type === 'multiselect')) { + this.value = [this.value]; + } + if (this.type === 'bool' && typeof this.value !== 'bool') { + this.value = !!this.value; + } + this.makePropArray('validators'); + this.bindPropFunctions('validators'); + this.makePropArray('onChangeHandlers'); + this.bindPropFunctions('onChangeHandlers'); + if (this.optionsFrom != null) { + this.ensureSelectType(); + if ((this.optionsFrom.url == null) || (this.optionsFrom.parseResults == null)) { + return globals.handleError('When fetching options remotely, both url and parseResults properties are required'); + } + if (typeof ((ref3 = this.optionsFrom) != null ? ref3.url : void 0) === 'function') { + this.optionsFrom.url = this.bindPropFunction('optionsFrom.url', this.optionsFrom.url); + } + if (typeof this.optionsFrom.parseResults !== 'function') { + return globals.handleError('optionsFrom.parseResults must be a function'); + } + this.optionsFrom.parseResults = this.bindPropFunction('optionsFrom.parseResults', this.optionsFrom.parseResults); + } + this.updateOptionsSelected(); + this.on('change:value', function() { + var changeFunc, j, len1, ref4; + ref4 = this.onChangeHandlers; + for (j = 0, len1 = ref4.length; j < len1; j++) { + changeFunc = ref4[j]; + changeFunc(); + } + return this.updateOptionsSelected(); + }); + return this.on('change:type', function() { + if (this.type === 'multiselect') { + this.value = this.value.length > 0 ? [this.value] : []; + } else if (this.previousAttributes().type === 'multiselect') { + this.value = this.value.length > 0 ? this.value[0] : ''; + } + if (this.options.length > 0 && !this.isSelectType()) { + return this.type = 'select'; + } + }); + }; + + ModelField.prototype.getOptionsFrom = function() { + var ref2, url; + if (this.optionsFrom == null) { + return; + } + url = typeof this.optionsFrom.url === 'function' ? this.optionsFrom.url() : this.optionsFrom.url; + if (this.prevUrl === url) { + return; + } + this.prevUrl = url; + return typeof window !== "undefined" && window !== null ? (ref2 = window.formbuilderproxy) != null ? ref2.getFromProxy({ + url: url, + method: this.optionsFrom.method || 'get', + headerKey: this.optionsFrom.headerKey + }, (function(_this) { + return function(error, data) { + var j, len1, mappedResults, opt, results1; + if (error) { + return globals.handleError(globals.makeErrorMessage(_this, 'optionsFrom', error)); + } + mappedResults = _this.optionsFrom.parseResults(data); + if (!Array.isArray(mappedResults)) { + return globals.handleError('results of parseResults must be an array of option parameters'); + } + _this.options = []; + results1 = []; + for (j = 0, len1 = mappedResults.length; j < len1; j++) { + opt = mappedResults[j]; + results1.push(_this.option(opt)); + } + return results1; + }; + })(this)) : void 0 : void 0; + }; + + ModelField.prototype.validityMessage = void 0; + + ModelField.prototype.field = function() { + var obj, ref2; + obj = 1 <= arguments.length ? slice.call(arguments, 0) : []; + return (ref2 = this.parent).field.apply(ref2, obj); + }; + + ModelField.prototype.group = function() { + var obj, ref2; + obj = 1 <= arguments.length ? slice.call(arguments, 0) : []; + return (ref2 = this.parent).group.apply(ref2, obj); + }; + + ModelField.prototype.option = function() { + var newOption, nextOpts, opt, optionObject, optionParams; + optionParams = 1 <= arguments.length ? slice.call(arguments, 0) : []; + optionObject = this.buildParamObject(optionParams, ['title', 'value', 'selected', 'bidAdj', 'bidAdjFlag']); + this.ensureSelectType(); + nextOpts = (function() { + var j, len1, ref2, results1; + ref2 = this.options; + results1 = []; + for (j = 0, len1 = ref2.length; j < len1; j++) { + opt = ref2[j]; + if (opt.title !== optionObject.title) { + results1.push(opt); + } + } + return results1; + }).call(this); + newOption = new ModelOption(optionObject); + nextOpts.push(newOption); + this.options = nextOpts; + if (newOption.selected) { + this.addOptionValue(newOption.value); + } + return this; + }; + + ModelField.prototype.postBuild = function() { + this.defaultValue = this.value; + return this.updateOptionsSelected(); + }; + + ModelField.prototype.updateOptionsSelected = function() { + var bid, i, len, opt, ref, ref1, results; + ref = this.options; + results = []; + i = 0; + len = ref.length; + while (i < len) { + opt = ref[i]; + if ((ref1 = this.type) === 'multiselect' || ref1 === 'tree') { + bid = this.hasValue(opt.value); + if (bid.bidValue) { + opt.bidAdj = bid.bidValue.lastIndexOf('/') !== -1 ? bid.bidValue.split("/").pop() : this.bidAdj; + } + results.push(opt.selected = bid.selectStatus); + } else { + results.push(opt.selected = this.hasValue(opt.value)); + } + i++; + } + return results; + }; + + ModelField.prototype.isSelectType = function() { + var ref2; + return (ref2 = this.type) === 'select' || ref2 === 'multiselect' || ref2 === 'image' || ref2 === 'tree'; + }; + + ModelField.prototype.ensureSelectType = function() { + if (!this.isSelectType()) { + return this.type = 'select'; + } + }; + + ModelField.prototype.child = function(value) { + var j, len1, o, ref2; + if (Array.isArray(value)) { + value = value.shift(); + } + ref2 = this.options; + for (j = 0, len1 = ref2.length; j < len1; j++) { + o = ref2[j]; + if (o.value === value) { + return o; + } + } + }; + + ModelField.prototype.validator = function(func) { + this.validators.push(this.bindPropFunction('validator', func)); + this.trigger('change'); + return this; + }; + + ModelField.prototype.onChange = function(f) { + this.onChangeHandlers.push(this.bindPropFunction('onChange', f)); + this.trigger('change'); + return this; + }; + + ModelField.prototype.setDirty = function(id, whatChanged) { + var j, len1, opt, ref2; + ref2 = this.options; + for (j = 0, len1 = ref2.length; j < len1; j++) { + opt = ref2[j]; + opt.setDirty(id, whatChanged); + } + return ModelField.__super__.setDirty.call(this, id, whatChanged); + }; + + ModelField.prototype.setClean = function(all) { + var j, len1, opt, ref2, results1; + ModelField.__super__.setClean.apply(this, arguments); + if (all) { + ref2 = this.options; + results1 = []; + for (j = 0, len1 = ref2.length; j < len1; j++) { + opt = ref2[j]; + results1.push(opt.setClean(all)); + } + return results1; + } + }; + + ModelField.prototype.recalculateRelativeProperties = function() { + var dirty, j, k, len1, len2, opt, ref2, ref3, results1, validator, validityMessage, value; + dirty = this.dirty; + ModelField.__super__.recalculateRelativeProperties.apply(this, arguments); + if (this.shouldCallTriggerFunctionFor(dirty, 'isValid')) { + validityMessage = void 0; + if (this.template) { + validityMessage || (validityMessage = this.validate.template.call(this)); + } + if (this.type === 'number') { + validityMessage || (validityMessage = this.validate.number.call(this)); + } + if (!validityMessage) { + ref2 = this.validators; + for (j = 0, len1 = ref2.length; j < len1; j++) { + validator = ref2[j]; + if (typeof validator === 'function') { + validityMessage = validator.call(this); + } + if (typeof validityMessage === 'function') { + return globals.handleError("A validator on field '" + this.name + "' returned a function"); + } + if (validityMessage) { + break; + } + } + } + this.validityMessage = validityMessage; + this.set({ + isValid: validityMessage == null + }); + } + if (this.template && this.shouldCallTriggerFunctionFor(dirty, 'value')) { + this.renderTemplate(); + } else { + if (typeof this.dynamicValue === 'function' && this.shouldCallTriggerFunctionFor(dirty, 'value')) { + value = this.dynamicValue(); + if (typeof value === 'function') { + return globals.handleError("dynamicValue on field '" + this.name + "' returned a function"); + } + this.set('value', value); + } + } + if (this.shouldCallTriggerFunctionFor(dirty, 'options')) { + this.getOptionsFrom(); + } + ref3 = this.options; + results1 = []; + for (k = 0, len2 = ref3.length; k < len2; k++) { + opt = ref3[k]; + results1.push(opt.recalculateRelativeProperties()); + } + return results1; + }; + + ModelField.prototype.addOptionValue = function(val, bidAdj) { + var findMatch, ref; + findMatch = void 0; + ref = void 0; + if ((ref = this.type) === 'multiselect' || ref === 'tree') { + if (!Array.isArray(this.value)) { + this.value = [this.value]; + } + findMatch = this.value.findIndex(function(e) { + if (typeof e === 'string') { + return e.search(val) !== -1; + } else { + return e === val; + } + }); + if (findMatch !== -1) { + if (bidAdj) { + return this.value[findMatch] = val + '/' + bidAdj; + } + } else { + if (bidAdj) { + return this.value.push(val + '/' + bidAdj); + } else { + return this.value.push(val); + } + } + } else { + return this.value = val; + } + }; + + ModelField.prototype.removeOptionValue = function(val) { + var ref; + ref = void 0; + if ((ref = this.type) === 'multiselect' || ref === 'tree') { + return this.value = this.value.filter(function(e) { + if (typeof e === 'string') { + return e.search(val) === -1; + } else { + return e !== val; + } + }); + } else if (this.value === val) { + return this.value = ''; + } + }; + + ModelField.prototype.hasValue = function(val) { + var findMatch, ref; + findMatch = void 0; + ref = void 0; + if ((ref = this.type) === 'multiselect' || ref === 'tree') { + findMatch = this.value.findIndex(function(e) { + if (typeof e === 'string') { + return e.search(val) !== -1; + } else { + return e === val; + } + }); + if (findMatch !== -1) { + return { + 'bidValue': this.value[findMatch], + 'selectStatus': true + }; + } else { + return { + 'selectStatus': false + }; + } + } else { + return val === this.value; + } + }; + + ModelField.prototype.buildOutputData = function(_, skipBeforeOutput) { + var out, value; + value = (function() { + switch (this.type) { + case 'number': + out = +this.value; + if (isNaN(out)) { + return null; + } else { + return out; + } + break; + case 'info': + case 'button': + return void 0; + case 'bool': + return !!this.value; + default: + return this.value; + } + }).call(this); + if (skipBeforeOutput) { + return value; + } else { + return this.beforeOutput(value); + } + }; + + ModelField.prototype.clear = function(purgeDefaults) { + if (purgeDefaults == null) { + purgeDefaults = false; + } + if (purgeDefaults) { + return this.value = (function() { + switch (this.type) { + case 'multiselect': + return []; + case 'bool': + return false; + default: + return ''; + } + }).call(this); + } else { + return this.value = this.defaultValue; + } + }; + + ModelField.prototype.ensureValueInOptions = function() { + var existingOption, j, k, l, len1, len2, len3, o, ref2, ref3, ref4, results1, v; + if (!this.isSelectType()) { + return; + } + if (typeof this.value === 'string') { + ref2 = this.options; + for (j = 0, len1 = ref2.length; j < len1; j++) { + o = ref2[j]; + if (o.value === this.value) { + existingOption = o; + } + } + if (!existingOption) { + return this.option(this.value, { + selected: true + }); + } + } else if (Array.isArray(this.value)) { + ref3 = this.value; + results1 = []; + for (k = 0, len2 = ref3.length; k < len2; k++) { + v = ref3[k]; + existingOption = null; + ref4 = this.options; + for (l = 0, len3 = ref4.length; l < len3; l++) { + o = ref4[l]; + if (o.value === v) { + existingOption = o; + } + } + if (!existingOption) { + results1.push(this.option(v, { + selected: true + })); + } else { + results1.push(void 0); + } + } + return results1; + } + }; + + ModelField.prototype.applyData = function(inData, clear, purgeDefaults) { + if (clear == null) { + clear = false; + } + if (purgeDefaults == null) { + purgeDefaults = false; + } + if (clear) { + this.clear(purgeDefaults); + } + if (inData != null) { + return this.value = this.beforeInput(jiff.clone(inData)); + } + }; + + ModelField.prototype.renderTemplate = function() { + var template; + if (typeof this.template === 'object') { + template = this.template.value; + } else { + template = this.parent.child(this.template).value; + } + try { + return this.value = Mustache.render(template, this.root.data); + } catch (error1) { + + } + }; + + return ModelField; + +})(ModelBase); diff --git a/lib/modelFieldDate.js b/lib/modelFieldDate.js new file mode 100644 index 0000000..c8f3aa7 --- /dev/null +++ b/lib/modelFieldDate.js @@ -0,0 +1,44 @@ +var ModelField, ModelFieldDate, moment, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +ModelField = require('./modelField'); + +moment = require('moment'); + +module.exports = ModelFieldDate = (function(superClass) { + extend(ModelFieldDate, superClass); + + function ModelFieldDate() { + return ModelFieldDate.__super__.constructor.apply(this, arguments); + } + + ModelFieldDate.prototype.initialize = function() { + this.setDefault('format', 'M/D/YYYY'); + ModelFieldDate.__super__.initialize.apply(this, arguments); + return this.validator(this.validate.date); + }; + + ModelFieldDate.prototype.dateToString = function(date, format) { + if (date == null) { + date = this.value; + } + if (format == null) { + format = this.format; + } + return moment(date).format(format); + }; + + ModelFieldDate.prototype.stringToDate = function(str, format) { + if (str == null) { + str = this.value; + } + if (format == null) { + format = this.format; + } + return moment(str, format, true).toDate(); + }; + + return ModelFieldDate; + +})(ModelField); diff --git a/lib/modelFieldImage.js b/lib/modelFieldImage.js new file mode 100644 index 0000000..89f9a7f --- /dev/null +++ b/lib/modelFieldImage.js @@ -0,0 +1,101 @@ +var ModelField, ModelFieldImage, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty, + slice = [].slice; + +ModelField = require('./modelField'); + +module.exports = ModelFieldImage = (function(superClass) { + extend(ModelFieldImage, superClass); + + function ModelFieldImage() { + return ModelFieldImage.__super__.constructor.apply(this, arguments); + } + + ModelFieldImage.prototype.initialize = function() { + this.setDefault('value', {}); + this.setDefault('allowUpload', false); + this.setDefault('imagesPerPage', 4); + this.setDefault('minWidth', 0); + this.setDefault('maxWidth', 0); + this.setDefault('minHeight', 0); + this.setDefault('maxHeight', 0); + this.setDefault('minSize', 0); + this.setDefault('maxSize', 0); + this.set('optionsChanged', false); + return ModelFieldImage.__super__.initialize.apply(this, arguments); + }; + + ModelFieldImage.prototype.option = function() { + var optionObject, optionParams; + optionParams = 1 <= arguments.length ? slice.call(arguments, 0) : []; + optionObject = this.buildParamObject(optionParams, ['fileID', 'fileUrl', 'thumbnailUrl']); + if (optionObject.fileID == null) { + optionObject.fileID = optionObject.fileUrl; + } + if (optionObject.thumbnailUrl == null) { + optionObject.thumbnailUrl = optionObject.fileUrl; + } + optionObject.value = { + fileID: optionObject.fileID, + fileUrl: optionObject.fileUrl, + thumbnailUrl: optionObject.thumbnailUrl + }; + if (optionObject.title == null) { + optionObject.title = optionObject.fileID; + } + this.optionsChanged = true; + return ModelFieldImage.__super__.option.call(this, optionObject); + }; + + ModelFieldImage.prototype.child = function(fileID) { + var i, len, o, ref; + if (Array.isArray(fileID)) { + fileID = fileID.shift(); + } + if (typeof fileID === 'object') { + fileID = fileID.fileID; + } + ref = this.options; + for (i = 0, len = ref.length; i < len; i++) { + o = ref[i]; + if (o.fileID === fileID) { + return o; + } + } + }; + + ModelFieldImage.prototype.removeOptionValue = function(val) { + if (this.value.fileID === val.fileID) { + return this.value = {}; + } + }; + + ModelFieldImage.prototype.hasValue = function(val) { + return val.fileID === this.value.fileID && val.thumbnailUrl === this.value.thumbnailUrl && val.fileUrl === this.value.fileUrl; + }; + + ModelFieldImage.prototype.clear = function(purgeDefaults) { + if (purgeDefaults == null) { + purgeDefaults = false; + } + return this.value = purgeDefaults ? {} : this.defaultValue; + }; + + ModelFieldImage.prototype.ensureValueInOptions = function() { + var existingOption, i, len, o, ref; + ref = this.options; + for (i = 0, len = ref.length; i < len; i++) { + o = ref[i]; + if (o.attributes.fileID === this.value.fileID) { + existingOption = o; + } + } + if (!existingOption) { + return this.option(this.value); + } + }; + + return ModelFieldImage; + +})(ModelField); diff --git a/lib/modelFieldTree.js b/lib/modelFieldTree.js new file mode 100644 index 0000000..3008454 --- /dev/null +++ b/lib/modelFieldTree.js @@ -0,0 +1,43 @@ +var ModelField, ModelFieldTree, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty, + slice = [].slice; + +ModelField = require('./modelField'); + +module.exports = ModelFieldTree = (function(superClass) { + extend(ModelFieldTree, superClass); + + function ModelFieldTree() { + return ModelFieldTree.__super__.constructor.apply(this, arguments); + } + + ModelFieldTree.prototype.initialize = function() { + this.setDefault('value', []); + return ModelFieldTree.__super__.initialize.apply(this, arguments); + }; + + ModelFieldTree.prototype.option = function() { + var optionObject, optionParams; + optionParams = 1 <= arguments.length ? slice.call(arguments, 0) : []; + optionObject = this.buildParamObject(optionParams, ['path', 'value', 'selected']); + if (optionObject.value == null) { + optionObject.value = optionObject.id; + } + if (optionObject.value == null) { + optionObject.value = optionObject.path.join(' > '); + } + optionObject.title = optionObject.path.join('>'); + return ModelFieldTree.__super__.option.call(this, optionObject); + }; + + ModelFieldTree.prototype.clear = function(purgeDefaults) { + if (purgeDefaults == null) { + purgeDefaults = false; + } + return this.value = purgeDefaults ? [] : this.defaultValue; + }; + + return ModelFieldTree; + +})(ModelField); diff --git a/lib/modelGroup.js b/lib/modelGroup.js new file mode 100644 index 0000000..ea30836 --- /dev/null +++ b/lib/modelGroup.js @@ -0,0 +1,409 @@ +var ModelBase, ModelField, ModelFieldDate, ModelFieldImage, ModelFieldTree, ModelGroup, RepeatingModelGroup, globals, jiff, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty, + slice = [].slice; + +ModelBase = require('./modelBase'); + +ModelFieldImage = require('./modelFieldImage'); + +ModelFieldTree = require('./modelFieldTree'); + +ModelFieldDate = require('./modelFieldDate'); + +ModelField = require('./modelField'); + +globals = require('./globals'); + +jiff = require('jiff'); + + +/* + A ModelGroup is a model object that can contain any number of other groups and fields + */ + +module.exports = ModelGroup = (function(superClass) { + extend(ModelGroup, superClass); + + function ModelGroup() { + return ModelGroup.__super__.constructor.apply(this, arguments); + } + + ModelGroup.prototype.modelClassName = 'ModelGroup'; + + ModelGroup.prototype.initialize = function() { + this.setDefault('children', []); + this.setDefault('root', this); + this.set('isValid', true); + this.set('data', null); + this.setDefault('beforeInput', function(val) { + return val; + }); + this.setDefault('beforeOutput', function(val) { + return val; + }); + return ModelGroup.__super__.initialize.apply(this, arguments); + }; + + ModelGroup.prototype.postBuild = function() { + var child, i, len, ref, results; + ref = this.children; + results = []; + for (i = 0, len = ref.length; i < len; i++) { + child = ref[i]; + results.push(child.postBuild()); + } + return results; + }; + + ModelGroup.prototype.field = function() { + var fieldObject, fieldParams, fld; + fieldParams = 1 <= arguments.length ? slice.call(arguments, 0) : []; + fieldObject = this.buildParamObject(fieldParams, ['title', 'name', 'type', 'value']); + if (fieldObject.disabled == null) { + fieldObject.disabled = this.disabled; + } + fld = (function() { + switch (fieldObject.type) { + case 'image': + return new ModelFieldImage(fieldObject); + case 'tree': + return new ModelFieldTree(fieldObject); + case 'date': + return new ModelFieldDate(fieldObject); + default: + return new ModelField(fieldObject); + } + })(); + this.children.push(fld); + this.trigger('change'); + return fld; + }; + + ModelGroup.prototype.group = function() { + var groupObject, groupParams, grp, key, ref, val; + groupParams = 1 <= arguments.length ? slice.call(arguments, 0) : []; + grp = {}; + if (((ref = groupParams[0].constructor) != null ? ref.name : void 0) === 'ModelGroup') { + grp = groupParams[0].cloneModel(this.root); + groupParams.shift(); + groupObject = this.buildParamObject(groupParams, ['title', 'name', 'description']); + if (groupObject.name == null) { + groupObject.name = groupObject.title; + } + if (groupObject.title == null) { + groupObject.title = groupObject.name; + } + for (key in groupObject) { + val = groupObject[key]; + grp.set(key, val); + } + } else { + groupObject = this.buildParamObject(groupParams, ['title', 'name', 'description']); + if (groupObject.disabled == null) { + groupObject.disabled = this.disabled; + } + if (groupObject.repeating) { + grp = new RepeatingModelGroup(groupObject); + } else { + grp = new ModelGroup(groupObject); + } + } + this.children.push(grp); + this.trigger('change'); + return grp; + }; + + ModelGroup.prototype.child = function(path) { + var c, child, i, len, name, ref; + if (!(Array.isArray(path))) { + path = path.split(/[.\/]/); + } + name = path.shift(); + ref = this.children; + for (i = 0, len = ref.length; i < len; i++) { + c = ref[i]; + if (c.name === name) { + child = c; + } + } + if (path.length === 0) { + return child; + } else { + return child.child(path); + } + }; + + ModelGroup.prototype.setDirty = function(id, whatChanged) { + var child, i, len, ref; + ref = this.children; + for (i = 0, len = ref.length; i < len; i++) { + child = ref[i]; + child.setDirty(id, whatChanged); + } + return ModelGroup.__super__.setDirty.call(this, id, whatChanged); + }; + + ModelGroup.prototype.setClean = function(all) { + var child, i, len, ref, results; + ModelGroup.__super__.setClean.apply(this, arguments); + if (all) { + ref = this.children; + results = []; + for (i = 0, len = ref.length; i < len; i++) { + child = ref[i]; + results.push(child.setClean(all)); + } + return results; + } + }; + + ModelGroup.prototype.recalculateRelativeProperties = function(collection) { + var child, dirty, i, len, newValid; + if (collection == null) { + collection = this.children; + } + dirty = this.dirty; + ModelGroup.__super__.recalculateRelativeProperties.apply(this, arguments); + newValid = true; + for (i = 0, len = collection.length; i < len; i++) { + child = collection[i]; + child.recalculateRelativeProperties(); + newValid && (newValid = child.isValid); + } + return this.isValid = newValid; + }; + + ModelGroup.prototype.buildOutputData = function(group, skipBeforeOutput) { + var obj; + if (group == null) { + group = this; + } + obj = {}; + group.children.forEach(function(child) { + var childData; + childData = child.buildOutputData(void 0, skipBeforeOutput); + if (childData !== void 0) { + return obj[child.name] = childData; + } + }); + if (skipBeforeOutput) { + return obj; + } else { + return group.beforeOutput(obj); + } + }; + + ModelGroup.prototype.buildOutputDataString = function() { + return JSON.stringify(this.buildOutputData()); + }; + + ModelGroup.prototype.clear = function(purgeDefaults) { + var child, i, j, key, len, len1, ref, ref1, results; + if (purgeDefaults == null) { + purgeDefaults = false; + } + if (this.data) { + ref = Object.keys(this.data); + for (i = 0, len = ref.length; i < len; i++) { + key = ref[i]; + delete this.data[key]; + } + } + ref1 = this.children; + results = []; + for (j = 0, len1 = ref1.length; j < len1; j++) { + child = ref1[j]; + results.push(child.clear(purgeDefaults)); + } + return results; + }; + + ModelGroup.prototype.applyData = function(inData, clear, purgeDefaults) { + var finalInData, key, ref, results, value; + if (clear == null) { + clear = false; + } + if (purgeDefaults == null) { + purgeDefaults = false; + } + if (clear) { + this.clear(purgeDefaults); + } + finalInData = this.beforeInput(jiff.clone(inData)); + + /* + This section preserves a link to the initially applied data object and merges subsequent applies on top + of it in-place. This is necessary for two reasons. + First, the scope of the running model code also references the applied data through the 'data' variable. + Every applied data must be available even though the runtime is not re-evaluated each time. + Second, templated fields use this data as the input to their Mustache evaluation. See @renderTemplate() + */ + if (this.data) { + globals.mergeData(this.data, inData); + this.trigger('change'); + } else { + this.data = inData; + } + results = []; + for (key in finalInData) { + value = finalInData[key]; + results.push((ref = this.child(key)) != null ? ref.applyData(value) : void 0); + } + return results; + }; + + return ModelGroup; + +})(ModelBase); + + +/* + Encapsulates a group of form objects that can be added or removed to the form together multiple times + */ + +RepeatingModelGroup = (function(superClass) { + extend(RepeatingModelGroup, superClass); + + function RepeatingModelGroup() { + return RepeatingModelGroup.__super__.constructor.apply(this, arguments); + } + + RepeatingModelGroup.prototype.modelClassName = 'RepeatingModelGroup'; + + RepeatingModelGroup.prototype.initialize = function() { + this.setDefault('defaultValue', this.get('value') || []); + this.set('value', []); + return RepeatingModelGroup.__super__.initialize.apply(this, arguments); + }; + + RepeatingModelGroup.prototype.postBuild = function() { + var c, i, len, ref; + ref = this.children; + for (i = 0, len = ref.length; i < len; i++) { + c = ref[i]; + c.postBuild(); + } + return this.clear(); + }; + + RepeatingModelGroup.prototype.setDirty = function(id, whatChanged) { + var i, len, ref, val; + ref = this.value; + for (i = 0, len = ref.length; i < len; i++) { + val = ref[i]; + val.setDirty(id, whatChanged); + } + return RepeatingModelGroup.__super__.setDirty.call(this, id, whatChanged); + }; + + RepeatingModelGroup.prototype.setClean = function(all) { + var i, len, ref, results, val; + RepeatingModelGroup.__super__.setClean.apply(this, arguments); + if (all) { + ref = this.value; + results = []; + for (i = 0, len = ref.length; i < len; i++) { + val = ref[i]; + results.push(val.setClean(all)); + } + return results; + } + }; + + RepeatingModelGroup.prototype.recalculateRelativeProperties = function() { + return RepeatingModelGroup.__super__.recalculateRelativeProperties.call(this, this.value); + }; + + RepeatingModelGroup.prototype.buildOutputData = function(_, skipBeforeOutput) { + var tempOut; + tempOut = this.value.map(function(instance) { + return RepeatingModelGroup.__super__.buildOutputData.call(this, instance); + }); + if (skipBeforeOutput) { + return tempOut; + } else { + return this.beforeOutput(tempOut); + } + }; + + RepeatingModelGroup.prototype.clear = function(purgeDefaults) { + if (purgeDefaults == null) { + purgeDefaults = false; + } + this.value = []; + if (!purgeDefaults) { + if (this.defaultValue) { + return this.addEachSimpleObject(this.defaultValue); + } + } + }; + + RepeatingModelGroup.prototype.applyData = function(inData, clear, purgeDefaults) { + var finalInData; + if (clear == null) { + clear = false; + } + if (purgeDefaults == null) { + purgeDefaults = false; + } + finalInData = this.beforeInput(jiff.clone(inData)); + if (finalInData) { + this.value = []; + } else { + if (clear) { + this.clear(purgeDefaults); + } + } + return this.addEachSimpleObject(finalInData, clear, purgeDefaults); + }; + + RepeatingModelGroup.prototype.addEachSimpleObject = function(o, clear, purgeDefaults) { + var added, i, key, len, obj, results, value; + if (clear == null) { + clear = false; + } + if (purgeDefaults == null) { + purgeDefaults = false; + } + results = []; + for (i = 0, len = o.length; i < len; i++) { + obj = o[i]; + added = this.add(); + results.push((function() { + var ref, results1; + results1 = []; + for (key in obj) { + value = obj[key]; + results1.push((ref = added.child(key)) != null ? ref.applyData(value, clear, purgeDefaults) : void 0); + } + return results1; + })()); + } + return results; + }; + + RepeatingModelGroup.prototype.cloneModel = function(root, constructor) { + var clone, excludeAttributes; + excludeAttributes = (constructor != null ? constructor.name : void 0) === 'ModelGroup' ? ['value', 'beforeInput', 'beforeOutput', 'description'] : []; + clone = RepeatingModelGroup.__super__.cloneModel.call(this, root, constructor, excludeAttributes); + clone.title = ''; + return clone; + }; + + RepeatingModelGroup.prototype.add = function() { + var clone; + clone = this.cloneModel(this.root, ModelGroup); + this.value.push(clone); + this.trigger('change'); + return clone; + }; + + RepeatingModelGroup.prototype["delete"] = function(index) { + this.value.splice(index, 1); + return this.trigger('change'); + }; + + return RepeatingModelGroup; + +})(ModelGroup); diff --git a/lib/modelOption.js b/lib/modelOption.js new file mode 100644 index 0000000..17b689b --- /dev/null +++ b/lib/modelOption.js @@ -0,0 +1,31 @@ +var ModelBase, ModelOption, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +ModelBase = require('./modelBase'); + +module.exports = ModelOption = (function(superClass) { + extend(ModelOption, superClass); + + function ModelOption() { + return ModelOption.__super__.constructor.apply(this, arguments); + } + + ModelOption.prototype.initialize = function() { + this.setDefault('value', this.get('title')); + this.setDefault('title', this.get('value')); + this.setDefault('selected', false); + this.setDefault('path', []); + ModelOption.__super__.initialize.apply(this, arguments); + return this.on('change:selected', function() { + if (this.selected) { + return this.parent.addOptionValue(this.value, this.bidAdj); + } else { + return this.parent.removeOptionValue(this.value); + } + }); + }; + + return ModelOption; + +})(ModelBase); diff --git a/src/modelField.coffee b/src/modelField.coffee index 059853b..6fc6fef 100644 --- a/src/modelField.coffee +++ b/src/modelField.coffee @@ -116,7 +116,7 @@ module.exports = class ModelField extends ModelBase @parent.group obj... option: (optionParams...) -> - optionObject = @buildParamObject optionParams, ['title', 'value', 'selected'] + optionObject = @buildParamObject optionParams, ['title', 'value', 'selected', 'bidAdj', 'bidAdjFlag'] # when adding an option to a field, make sure it is a *select type @ensureSelectType() @@ -140,8 +140,22 @@ module.exports = class ModelField extends ModelBase @updateOptionsSelected() updateOptionsSelected: -> - for opt in @options - opt.selected = @hasValue opt.value + ref = @options + results = [] + i = 0 + len = ref.length + + while i < len + opt = ref[i] + if (ref1 = @type) == 'multiselect' or ref1 == 'tree' + bid = @hasValue(opt.value) + if bid.bidValue + opt.bidAdj = if bid.bidValue.lastIndexOf('/') != -1 then bid.bidValue.split("/").pop() else @bidAdj + results.push opt.selected = bid.selectStatus + else + results.push opt.selected = @hasValue(opt.value) + i++ + results # returns true if this type is one where a value is selected. Otherwise false isSelectType: -> @@ -222,28 +236,61 @@ module.exports = class ModelField extends ModelBase for opt in @options opt.recalculateRelativeProperties() - addOptionValue: (val) -> - if @type in ['multiselect','tree'] - unless Array.isArray @value - @value = [@value] - if not (val in @value) - @value.push val - else #single-select - @value = val + addOptionValue: (val, bidAdj) -> + findMatch = undefined + ref = undefined + if (ref = @type) == 'multiselect' or ref == 'tree' + if !Array.isArray(@value) + @value = [ @value ] + findMatch = @value.findIndex((e) -> + if typeof e == 'string' + e.search(val) != -1 + else + e == val + ) + if findMatch != -1 + if bidAdj + return @value[findMatch] = val + '/' + bidAdj + else + if bidAdj + return @value.push(val + '/' + bidAdj) + else + return @value.push(val) + else + return @value = val + return removeOptionValue: (val) -> - if @type in ['multiselect','tree'] - if val in @value - @value = @value.filter (v) -> v isnt val - else if @value is val #single-select - @value = '' - - #determine if the value is or contains the provided value. + ref = undefined + if (ref = @type) == 'multiselect' or ref == 'tree' + return @value = @value.filter((e) -> + if typeof e == 'string' + e.search(val) == -1 + else + e != val + ) + else if @value == val + return @value = '' + return hasValue: (val) -> - if @type in ['multiselect','tree'] - val in @value + findMatch = undefined + ref = undefined + if (ref = @type) == 'multiselect' or ref == 'tree' + findMatch = @value.findIndex((e) -> + if typeof e == 'string' + e.search(val) != -1 + else + e == val + ) + if findMatch != -1 + { + 'bidValue': @value[findMatch] + 'selectStatus': true + } + else + { 'selectStatus': false } else - val is @value + val == @value buildOutputData: (_, skipBeforeOutput) -> value = switch @type @@ -273,7 +320,7 @@ module.exports = class ModelField extends ModelBase @option @value, selected:true else if Array.isArray @value for v in @value - existingOption = null #required to clear out previously found values + existingOption = null existingOption = o for o in @options when o.value is v unless existingOption @option v, selected:true @@ -282,7 +329,8 @@ module.exports = class ModelField extends ModelBase @clear purgeDefaults if clear if inData? @value = @beforeInput jiff.clone inData - @ensureValueInOptions() + #HUB-2766 this is no longer necessary as we now have biding changing option + #@ensureValueInOptions() renderTemplate: () -> if typeof @template is 'object' @@ -291,5 +339,4 @@ module.exports = class ModelField extends ModelBase template = @parent.child(@template).value try @value = Mustache.render template, @root.data - catch #just don't crash. Validator will display error later. - + catch \ No newline at end of file diff --git a/src/modelOption.coffee b/src/modelOption.coffee index c713655..4aeba65 100644 --- a/src/modelOption.coffee +++ b/src/modelOption.coffee @@ -8,6 +8,7 @@ module.exports = class ModelOption extends ModelBase @setDefault 'title', @get 'value' # selected is used to set default value and also to store current value. @setDefault 'selected', false + # set default bid adjustment @setDefault 'path', [] #for tree. Might should move to subclass super @@ -15,6 +16,6 @@ module.exports = class ModelOption extends ModelBase # this change likely comes from parent value changing, so be careful not to infinitely recurse. @on 'change:selected', -> if @selected - @parent.addOptionValue @value + @parent.addOptionValue @value, @bidAdj else # not selected @parent.removeOptionValue @value \ No newline at end of file From 07d0ae232326fc4fcbbecebda23e32bad8f60203 Mon Sep 17 00:00:00 2001 From: jkareliya Date: Mon, 20 Sep 2021 16:48:32 +0530 Subject: [PATCH 02/13] HUB-3750 upgrade package version --- package.json | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index df294ba..b4d0d70 100644 --- a/package.json +++ b/package.json @@ -5,24 +5,24 @@ "author": "Balihoo", "main": "./formbuilder.js", "dependencies": { - "backbone": "1.2.1", - "coffee-script": "1.9.3", - "jiff": "0.7.2", + "backbone": "1.4.0", + "coffee-script": "1.12.7", + "jiff": "0.7.3", "moment": "^2.14.1", - "mustache": "balihoo-anewman/mustache.js", - "underscore": "1.8.3" + "mustache": "4.2.0", + "underscore": "1.13.1" }, "devDependencies": { - "coffeelint": "1.11.1", - "gulp": "^3.8.10", - "gulp-coffee": "^2.2.0", - "gulp-coffeelint": "^0.5.0", - "gulp-istanbul": "^0.5.0", - "gulp-mocha": "^2.0.0", - "istanbul": "^0.3.5", - "mocha": "^2.0.1", - "sinon": "^1.17.7", - "yargs": "^3.15.0" + "coffeelint": "2.1.0", + "gulp": "4.0.2", + "gulp-coffee": "3.0.3", + "gulp-coffeelint": "0.6.0", + "gulp-istanbul": "1.1.3", + "gulp-mocha": "8.0.0", + "istanbul": "0.4.5", + "mocha": "9.1.1", + "sinon": "11.1.2", + "yargs": "17.1.1" }, "repository": { "type": "git", From b5b8bf82afa1753e0cabb974d7d76ae0664574a1 Mon Sep 17 00:00:00 2001 From: jkareliya Date: Tue, 21 Sep 2021 15:00:27 +0530 Subject: [PATCH 03/13] HUB-3750 Upgrade package version --- gulpfile.coffee | 10 +- lib/building.js | 52 ++- lib/formbuilder.js | 7 + lib/globals.js | 7 +- lib/modelBase.js | 516 +++++++++++------------ lib/modelField.js | 843 +++++++++++++++++++------------------ lib/modelFieldDate.js | 46 +- lib/modelFieldImage.js | 68 ++- lib/modelFieldTree.js | 43 +- lib/modelGroup.js | 646 ++++++++++++++-------------- lib/modelOption.js | 34 +- package.json | 2 +- src/modelField.coffee | 9 +- src/modelFieldDate.coffee | 1 + src/modelFieldImage.coffee | 2 +- src/modelFieldTree.coffee | 2 +- src/modelGroup.coffee | 7 +- src/modelOption.coffee | 2 +- 18 files changed, 1127 insertions(+), 1170 deletions(-) diff --git a/gulpfile.coffee b/gulpfile.coffee index bc9b90f..89ec283 100644 --- a/gulpfile.coffee +++ b/gulpfile.coffee @@ -12,19 +12,21 @@ gulp.task 'lint', -> .pipe(coffeelint('./coffeelint.json')) .pipe(coffeelint.reporter()) -gulp.task 'compile', ['lint'], -> +gulp.task 'compile', gulp.series('lint', -> gulp.src(src) .pipe( coffee({bare:true}) .on 'error', console.log ) .pipe gulp.dest('lib') +) # runs all coffee tests in the test directory. # alternatively, specify --file to run a single file or alternate file pattern. -gulp.task 'test', ['compile'], -> +gulp.task 'test', gulp.series('compile', -> src = argv.file or 'test/**/*.coffee' gulp.src src .pipe mocha() - -gulp.task 'default', ['test'] \ No newline at end of file +) + +gulp.task 'default', gulp.series('test') \ No newline at end of file diff --git a/lib/building.js b/lib/building.js index 807a3e5..fefacde 100644 --- a/lib/building.js +++ b/lib/building.js @@ -20,24 +20,28 @@ if (typeof alert !== "undefined" && alert !== null) { throttledAlert = _.throttle(alert, 500); } - /* An array of functions that can test a built model. Model code may add tests to this array during build. The tests themselves will not be run at the time, but are made avaiable via this export so processes can run the tests when appropriate. Tests may modify the model state, so the model should be rebuilt prior to running each test. - */ - +*/ exports.modelTests = []; +// Creates a Model object from JS code. The executed code will execute in a +// root ModelGroup +// code - model code +// data - initialization data (optional). Object or stringified object +// element - jquery element for firing validation events (optional) +// imports - object mapping {varname : model object}. May be referenced in form code exports.fromCode = function(code, data, element, imports, isImport) { var assert, emit, newRoot, test; data = (function() { switch (typeof data) { case 'object': - return jiff.clone(data); + return jiff.clone(data); //copy it case 'string': - return JSON.parse(data); + return JSON.parse(data); // 'undefined', 'null', and other unsupported types default: return {}; } @@ -47,10 +51,7 @@ exports.fromCode = function(code, data, element, imports, isImport) { test = function(func) { return exports.modelTests.push(func); }; - assert = function(bool, message) { - if (message == null) { - message = "A model test has failed"; - } + assert = function(bool, message = "A model test has failed") { if (!bool) { return globals.handleError(message); } @@ -61,16 +62,18 @@ exports.fromCode = function(code, data, element, imports, isImport) { } }; newRoot = new ModelGroup(); + //dont recalculate until model is done creating newRoot.recalculating = false; newRoot.recalculateCycle = function() {}; - (function(root) { + (function(root) { //new scope for root variable name var field, group, sandbox, validate; field = newRoot.field.bind(newRoot); group = newRoot.group.bind(newRoot); root = newRoot.root; validate = newRoot.validate; + //running in a vm is safer, but slower. Let the browser do plain eval, but not server. if (typeof window === "undefined" || window === null) { - sandbox = { + sandbox = { //hooks available in form code field: field, group: group, root: root, @@ -82,7 +85,7 @@ exports.fromCode = function(code, data, element, imports, isImport) { Mustache: Mustache, emit: emit, _: _, - console: { + console: { //console functions don't break, but don't do anything log: function() {}, error: function() {} }, @@ -123,14 +126,28 @@ exports.fromCode = function(code, data, element, imports, isImport) { }); newRoot.trigger('change:isValid'); newRoot.trigger('recalculate'); - newRoot.styles = false; + newRoot.styles = false; //don't render with well, etc. return newRoot; }; +// CoffeeScript counterpart to fromCode. Compiles the given code to JS +// and passes it to fromCode. exports.fromCoffee = function(code, data, element, imports, isImport) { return exports.fromCode(CoffeeScript.compile(code), data, element, imports, isImport); }; +// Build a model from a package object, consisting of +// - formid (int or string) +// - forms (array of object). Each object contains +// - - formid (int) +// - - model (string) coffeescript model code +// - - imports (array of object). Each object contains +// - - - importformid (int) +// - - - namespace (string) +// - data (object, optional) +// data may also be supplied as the second parameter to the function. Data in this parameter +// will override any matching keys provided in the package data +// element to which to bind validation and change messages, also optional exports.fromPackage = function(pkg, data, element) { var buildModelWithRecursiveImports; buildModelWithRecursiveImports = function(p, el, isImport) { @@ -158,7 +175,7 @@ exports.fromPackage = function(pkg, data, element) { forms: p.forms }, element, true); }; - if (form.imports) { + if (form.imports) { //in case imports left off the package form.imports.forEach(buildImport); } return exports.fromCoffee(form.model, data, el, builtImports, isImport); @@ -166,6 +183,7 @@ exports.fromPackage = function(pkg, data, element) { if (typeof pkg.formid === 'string') { pkg.formid = parseInt(pkg.formid); } + //data could be in the package and/or as a separate parameter. Extend them together. data = _.extend(pkg.data || {}, data || {}); return buildModelWithRecursiveImports(pkg, element, false); }; @@ -174,12 +192,15 @@ exports.getChanges = function(modelAfter, beforeData) { var after, before, changedPath, changedPaths, changedPathsUniqObject, changedPathsUnique, changes, i, internalPatch, j, key, len, len1, modelBefore, outputPatch, p, path, val; modelBefore = modelAfter.cloneModel(); modelBefore.applyData(beforeData, true); + // This patch is parsed and used to generate the changes internalPatch = jiff.diff(modelBefore.buildOutputData(void 0, true), modelAfter.buildOutputData(void 0, true), { invertible: false }); + // This is the actual patch outputPatch = jiff.diff(modelBefore.buildOutputData(), modelAfter.buildOutputData(), { invertible: false }); + //array paths end in an index #. We only want the field, not the index of the value changedPaths = (function() { var i, len, results; results = []; @@ -189,6 +210,7 @@ exports.getChanges = function(modelAfter, beforeData) { } return results; })(); + //get distinct field names. Arrays for example might appear multiple times changedPathsUniqObject = {}; for (i = 0, len = changedPaths.length; i < len; i++) { val = changedPaths[i]; @@ -208,7 +230,7 @@ exports.getChanges = function(modelAfter, beforeData) { path = changedPath.slice(1); before = modelBefore.child(path); after = modelAfter.child(path); - if (!_.isEqual(before != null ? before.value : void 0, after != null ? after.value : void 0)) { + if (!_.isEqual(before != null ? before.value : void 0, after != null ? after.value : void 0)) { //deep equality for non-primitives changes.push({ name: changedPath, title: after.title, diff --git a/lib/formbuilder.js b/lib/formbuilder.js index 664035c..915d8b8 100644 --- a/lib/formbuilder.js +++ b/lib/formbuilder.js @@ -22,10 +22,12 @@ exports.getChanges = building.getChanges; exports.mergeData = globals.mergeData; +// Apply initialization data to the model. exports.applyData = function(modelObject, inData, clear, purgeDefaults) { return modelObject.applyData(inData, clear, purgeDefaults); }; +//Call this method before output data is needed. exports.buildOutputData = function(model) { return model.buildOutputData(); }; @@ -36,6 +38,11 @@ Object.defineProperty(exports, 'modelTests', { } }); + +// We want users to be able to set a new handleError function. Rather than setting this +// module's handleError function to the current value in global.handleError, we make the +// setter overwrite the function reference in globals rather than the function reference +// in this file. Object.defineProperty(exports, 'handleError', { get: function() { return globals.handleError; diff --git a/lib/globals.js b/lib/globals.js index d301218..3e87f92 100644 --- a/lib/globals.js +++ b/lib/globals.js @@ -1,5 +1,8 @@ +//Things that are shared among many components, but we don't necessarily want to include in the base class. module.exports = { runtime: false, + // Determine what to do in the case of any error, including during compile, build and dynamic function calls. + // Any client may overwrite this method to handle errors differently, for example displaying them to the user handleError: function(err) { if (!(err instanceof Error)) { err = new Error(err); @@ -16,8 +19,10 @@ module.exports = { } stack.reverse(); nameStack = stack.join('.'); - return "The '" + propName + "' function belonging to the field named '" + nameStack + "' threw an error with the message '" + err.message + "'"; + return `The '${propName}' function belonging to the field named '${nameStack}' threw an error with the message '${err.message}'`; }, + // Merge data objects together. + // Modifies and returns the first parameter mergeData: function(a, b) { var key, value; if ((b != null ? b.constructor : void 0) === Object) { diff --git a/lib/modelBase.js b/lib/modelBase.js index 1a0ab51..fdd0730 100644 --- a/lib/modelBase.js +++ b/lib/modelBase.js @@ -1,11 +1,13 @@ - -/* - * Attributes common to groups and fields. - */ + /* + * Attributes common to groups and fields. + */ + /* Some properties may be booleans or functions that return booleans + Use this function to determine final boolean value. + prop - the property to evaluate, which may be something primitive or a function + deflt - the value to return if the property is undefined + */ var Backbone, ModelBase, Mustache, _, getBoolOrFunctionResult, globals, moment, newid, - extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, - hasProp = {}.hasOwnProperty, - indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + indexOf = [].indexOf; Backbone = require('backbone'); @@ -17,26 +19,17 @@ moment = require('moment'); Mustache = require('mustache'); +// generate a new, unqiue identifier. Mostly good for label. newid = (function() { var incId; incId = 0; return function() { incId++; - return "fbid_" + incId; + return `fbid_${incId}`; }; })(); - -/* Some properties may be booleans or functions that return booleans - Use this function to determine final boolean value. - prop - the property to evaluate, which may be something primitive or a function - deflt - the value to return if the property is undefined - */ - -getBoolOrFunctionResult = function(prop, deflt) { - if (deflt == null) { - deflt = true; - } +getBoolOrFunctionResult = function(prop, deflt = true) { if (typeof prop === 'function') { return !!prop(); } @@ -46,295 +39,320 @@ getBoolOrFunctionResult = function(prop, deflt) { return !!prop; }; -module.exports = ModelBase = (function(superClass) { - extend(ModelBase, superClass); +module.exports = ModelBase = (function() { + class ModelBase extends Backbone.Model { + initialize() { + var key, ref, val; + this.setDefault('visible', true); + this.set('isVisible', true); + this.setDefault('disabled', false); + this.set('isDisabled', false); + this.setDefault('onChangePropertiesHandlers', []); + this.set('id', newid()); + this.setDefault('parent', void 0); + this.setDefault('root', void 0); + this.setDefault('name', this.get('title')); + this.setDefault('title', this.get('name')); + ref = this.attributes; + //add accessors for each name access instead of get/set + for (key in ref) { + val = ref[key]; + ((key) => { + return Object.defineProperty(this, key, { + get: function() { + return this.get(key); + }, + set: function(newValue) { + if ((this.get(key)) !== newValue) { //save an onChange event if value isnt different + return this.set(key, newValue); + } + } + }); + })(key); + } + this.bindPropFunctions('visible'); + this.bindPropFunctions('disabled'); + this.makePropArray('onChangePropertiesHandlers'); + this.bindPropFunctions('onChangePropertiesHandlers'); + // Other fields may need to update visibility, validity, etc when this field changes. + // Fire an event on change, and catch those events fired by others. + return this.on('change', function() { + var ch, changeFunc, i, len, ref1; + if (!globals.runtime) { + return; + } + ref1 = this.onChangePropertiesHandlers; + // model onChangePropertiesHandlers functions + for (i = 0, len = ref1.length; i < len; i++) { + changeFunc = ref1[i]; + changeFunc(); + } + ch = this.changedAttributes(); + if (ch === false) { //no changes, manual trigger meant to fire everything + ch = 'multiple'; + } + this.root.setDirty(this.id, ch); + return this.root.recalculateCycle(); + }); + } - function ModelBase() { - return ModelBase.__super__.constructor.apply(this, arguments); - } + postBuild() {} - ModelBase.prototype.modelClassName = 'ModelBase'; + setDefault(field, val) { + if (this.get(field) == null) { + return this.set(field, val); + } + } - ModelBase.prototype.initialize = function() { - var fn, key, ref, val; - this.setDefault('visible', true); - this.set('isVisible', true); - this.setDefault('disabled', false); - this.set('isDisabled', false); - this.setDefault('onChangePropertiesHandlers', []); - this.set('id', newid()); - this.setDefault('parent', void 0); - this.setDefault('root', void 0); - this.setDefault('name', this.get('title')); - this.setDefault('title', this.get('name')); - ref = this.attributes; - fn = (function(_this) { - return function(key) { - return Object.defineProperty(_this, key, { - get: function() { - return this.get(key); - }, - set: function(newValue) { - if ((this.get(key)) !== newValue) { - return this.set(key, newValue); - } + text(message) { + return this.field(message, { + type: 'info' + }); + } + + //note: doesn't set the variable locally, just creates a bound version of it + bindPropFunction(propName, func) { + var model; + model = this; + return function() { + var err, message; + try { + if (this instanceof ModelBase) { + model = this; } - }); + return func.apply(model, arguments); + } catch (error) { + err = error; + message = globals.makeErrorMessage(model, propName, err); + return globals.handleError(message); + } }; - })(this); - for (key in ref) { - val = ref[key]; - fn(key); } - this.bindPropFunctions('visible'); - this.bindPropFunctions('disabled'); - this.makePropArray('onChangePropertiesHandlers'); - this.bindPropFunctions('onChangePropertiesHandlers'); - return this.on('change', function() { - var ch, changeFunc, i, len, ref1; - if (!globals.runtime) { - return; + + // bind properties that are functions to this object's context. Single functions or arrays of functions + bindPropFunctions(propName) { + var i, index, ref, results; + if (Array.isArray(this[propName])) { + results = []; + for (index = i = 0, ref = this[propName].length; (0 <= ref ? i < ref : i > ref); index = 0 <= ref ? ++i : --i) { + results.push(this[propName][index] = this.bindPropFunction(propName, this[propName][index])); + } + return results; + } else if (typeof this[propName] === 'function') { + return this.set(propName, this.bindPropFunction(propName, this[propName]), { + silent: true + }); } - ref1 = this.onChangePropertiesHandlers; - for (i = 0, len = ref1.length; i < len; i++) { - changeFunc = ref1[i]; - changeFunc(); + } + + // ensure a property is array type, for when a single value is supplied where an array is needed. + makePropArray(propName) { + if (!Array.isArray(this.get(propName))) { + return this.set(propName, [this.get(propName)]); } - ch = this.changedAttributes(); - if (ch === false) { - ch = 'multiple'; + } + + // convert list of params, either object(s) or positional strings (or both), into an object + // and add a few common properties + // assumes always called by creator of child objects, and thus sets parent to this + buildParamObject(params, paramPositions) { + var i, key, len, param, paramIndex, paramObject, ref, val; + paramObject = {}; + paramIndex = 0; + for (i = 0, len = params.length; i < len; i++) { + param = params[i]; + if (((ref = typeof param) === 'string' || ref === 'number' || ref === 'boolean') || Array.isArray(param)) { + paramObject[paramPositions[paramIndex++]] = param; + } else if (Object.prototype.toString.call(param) === '[object Object]') { + for (key in param) { + val = param[key]; + paramObject[key] = val; + } + } } - this.root.setDirty(this.id, ch); - return this.root.recalculateCycle(); - }); - }; + paramObject.parent = this; //not a param, but common to everything that uses this method + paramObject.root = this.root; + return paramObject; + } - ModelBase.prototype.postBuild = function() {}; + // set the dirty flag according to an object with all current changes + // or, whatChanged could be a string to set as the dirty value + setDirty(id, whatChanged) { + var ch, drt, keys; + ch = typeof whatChanged === 'string' ? whatChanged : (keys = Object.keys(whatChanged), keys.length === 1 ? `${id}:${keys[0]}` : 'multiple'); + drt = this.dirty === ch || this.dirty === '' ? ch : "multiple"; + return this.dirty = drt; + } - ModelBase.prototype.setDefault = function(field, val) { - if (this.get(field) == null) { - return this.set(field, val); + setClean() { + return this.dirty = ''; } - }; - ModelBase.prototype.text = function(message) { - return this.field(message, { - type: 'info' - }); - }; + shouldCallTriggerFunctionFor(dirty, attrName) { + return dirty && dirty !== `${this.id}:${attrName}`; + } - ModelBase.prototype.bindPropFunction = function(propName, func) { - var model; - model = this; - return function() { - var err, message; - try { - if (this instanceof ModelBase) { - model = this; - } - return func.apply(model, arguments); - } catch (error) { - err = error; - message = globals.makeErrorMessage(model, propName, err); - return globals.handleError(message); + // Any local properties that may need to recalculate if a foreign field changes. + recalculateRelativeProperties() { + var dirty; + dirty = this.dirty; + this.setClean(); + // visibility + if (this.shouldCallTriggerFunctionFor(dirty, 'isVisible')) { + this.isVisible = getBoolOrFunctionResult(this.visible); } - }; - }; - - ModelBase.prototype.bindPropFunctions = function(propName) { - var i, index, ref, results; - if (Array.isArray(this[propName])) { - results = []; - for (index = i = 0, ref = this[propName].length; 0 <= ref ? i < ref : i > ref; index = 0 <= ref ? ++i : --i) { - results.push(this[propName][index] = this.bindPropFunction(propName, this[propName][index])); + + // disabled status + if (this.shouldCallTriggerFunctionFor(dirty, 'isDisabled')) { + this.isDisabled = getBoolOrFunctionResult(this.disabled, false); } - return results; - } else if (typeof this[propName] === 'function') { - return this.set(propName, this.bindPropFunction(propName, this[propName]), { - silent: true - }); + return this.trigger('recalculate'); } - }; - ModelBase.prototype.makePropArray = function(propName) { - if (!Array.isArray(this.get(propName))) { - return this.set(propName, [this.get(propName)]); + // Add a new change properties handler to this object. + // This change itself will trigger on change properties functions to run, including the just-added one! + // If this trigger is not desired, set the second property to false + onChangeProperties(f, trigger = true) { + this.onChangePropertiesHandlers.push(this.bindPropFunction('onChangeProperties', f)); + if (trigger) { + this.trigger('change'); + } + return this; } - }; - ModelBase.prototype.buildParamObject = function(params, paramPositions) { - var i, key, len, param, paramIndex, paramObject, ref, val; - paramObject = {}; - paramIndex = 0; - for (i = 0, len = params.length; i < len; i++) { - param = params[i]; - if (((ref = typeof param) === 'string' || ref === 'number' || ref === 'boolean') || Array.isArray(param)) { - paramObject[paramPositions[paramIndex++]] = param; - } else if (Object.prototype.toString.call(param) === '[object Object]') { - for (key in param) { - val = param[key]; - paramObject[key] = val; + //Deep copy this backbone model by creating a new one with the same attributes. + //Overwrite each root attribute with the new root in the cloning form. + cloneModel(newRoot = this.root, constructor = this.constructor, excludeAttributes = []) { + var childClone, filteredAttributes, i, key, len, modelObj, myClone, newVal, ref, ref1, val; + // first filter out undesired attributes from the clone + filteredAttributes = {}; + ref = this.attributes; + for (key in ref) { + val = ref[key]; + if (indexOf.call(excludeAttributes, key) < 0) { + filteredAttributes[key] = val; + } + } + + // now call the constructor with the desired attributes + myClone = new constructor(filteredAttributes); + ref1 = myClone.attributes; + //some attributes need to be deep copied + for (key in ref1) { + val = ref1[key]; + //attributes that are form model objects need to themselves be cloned + if (key === 'root') { + myClone.set(key, newRoot); + } else if (val instanceof ModelBase && (key !== 'root' && key !== 'parent')) { + myClone.set(key, val.cloneModel(newRoot)); + } else if (Array.isArray(val)) { + newVal = []; + //array of form model objects, each needs to be cloned. Don't clone value objects + if (val[0] instanceof ModelBase && key !== 'value') { + for (i = 0, len = val.length; i < len; i++) { + modelObj = val[i]; + childClone = modelObj.cloneModel(newRoot); + //and if children/options are cloned, update their parent to this new object + if (childClone.parent === this) { + childClone.parent = myClone; + if (key === 'options' && childClone.selected) { + myClone.addOptionValue(childClone.value); + } + } + newVal.push(childClone); + } + } else { + newVal = _.clone(val); + } + myClone.set(key, newVal); } } + return myClone; } - paramObject.parent = this; - paramObject.root = this.root; - return paramObject; - }; - - ModelBase.prototype.dirty = ''; - ModelBase.prototype.setDirty = function(id, whatChanged) { - var ch, drt, keys; - ch = typeof whatChanged === 'string' ? whatChanged : (keys = Object.keys(whatChanged), keys.length === 1 ? id + ":" + keys[0] : 'multiple'); - drt = this.dirty === ch || this.dirty === '' ? ch : "multiple"; - return this.dirty = drt; }; - ModelBase.prototype.setClean = function() { - return this.dirty = ''; - }; - - ModelBase.prototype.shouldCallTriggerFunctionFor = function(dirty, attrName) { - return dirty && dirty !== (this.id + ":" + attrName); - }; + ModelBase.prototype.modelClassName = 'ModelBase'; - ModelBase.prototype.recalculateRelativeProperties = function() { - var dirty; - dirty = this.dirty; - this.setClean(); - if (this.shouldCallTriggerFunctionFor(dirty, 'isVisible')) { - this.isVisible = getBoolOrFunctionResult(this.visible); - } - if (this.shouldCallTriggerFunctionFor(dirty, 'isDisabled')) { - this.isDisabled = getBoolOrFunctionResult(this.disabled, false); - } - return this.trigger('recalculate'); - }; - - ModelBase.prototype.onChangeProperties = function(f, trigger) { - if (trigger == null) { - trigger = true; - } - this.onChangePropertiesHandlers.push(this.bindPropFunction('onChangeProperties', f)); - if (trigger) { - this.trigger('change'); - } - return this; - }; + ModelBase.prototype.dirty = ''; //do as a local string not attribute so it is not included in @changed + // Built-in functions for checking validity. ModelBase.prototype.validate = { - required: function(value) { - if (value == null) { - value = this.value || ''; - } + required: function(value = this.value || '') { if (((function() { switch (typeof value) { case 'number': case 'boolean': - return false; + return false; //these types cannot be empty case 'string': return value.length === 0; case 'object': return Object.keys(value).length === 0; default: - return true; + return true; //null, undefined } })())) { return "This field is required"; } }, minLength: function(n) { - return function(value) { - if (value == null) { - value = this.value || ''; - } + return function(value = this.value || '') { if (value.length < n) { - return "Must be at least " + n + " characters long"; + return `Must be at least ${n} characters long`; } }; }, maxLength: function(n) { - return function(value) { - if (value == null) { - value = this.value || ''; - } + return function(value = this.value || '') { if (value.length > n) { - return "Can be at most " + n + " characters long"; + return `Can be at most ${n} characters long`; } }; }, - number: function(value) { - if (value == null) { - value = this.value || ''; - } + number: function(value = this.value || '') { if (isNaN(+value)) { return "Must be an integer or decimal number. (ex. 42 or 1.618)"; } }, - date: function(value, format) { - if (value == null) { - value = this.value || ''; - } - if (format == null) { - format = this.format; - } + date: function(value = this.value || '', format = this.format) { if (value === '') { return; } if (!moment(value, format, true).isValid()) { - return "Not a valid date or does not match the format " + format; + return `Not a valid date or does not match the format ${format}`; } }, - email: function(value) { - if (value == null) { - value = this.value || ''; - } + email: function(value = this.value || '') { if (!value.match(/^[a-z0-9!\#$%&'*+\/=?^_`{|}~-]+(?:\.[a-z0-9!\#$%&'*+\/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/)) { return "Must be a valid email"; } }, - url: function(value) { - if (value == null) { - value = this.value || ''; - } + url: function(value = this.value || '') { if (!value.match(/^(([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)\#?(?:[\w]*))?$/)) { return "Must be a URL"; } }, - dollars: function(value) { - if (value == null) { - value = this.value || ''; - } + dollars: function(value = this.value || '') { if (!value.match(/^\$(\d+\.\d\d|\d+)$/)) { return "Must be a dollar amount (ex. $3.99)"; } }, minSelections: function(n) { - return function(value) { - if (value == null) { - value = this.value || ''; - } + return function(value = this.value || '') { if (value.length < n) { - return "Please select at least " + n + " options"; + return `Please select at least ${n} options`; } }; }, maxSelections: function(n) { - return function(value) { - if (value == null) { - value = this.value || ''; - } + return function(value = this.value || '') { if (value.length > n) { - return "Please select at most " + n + " options"; + return `Please select at most ${n} options`; } }; }, - selectedIsVisible: function(field) { + selectedIsVisible: function(field = this) { var i, len, opt, ref; - if (field == null) { - field = this; - } ref = field.options; for (i = 0, len = ref.length; i < len; i++) { opt = ref[i]; @@ -343,7 +361,7 @@ module.exports = ModelBase = (function(superClass) { } } }, - template: function() { + template: function() { //ensure the template field contains valid mustache var e, template; if (!this.template) { return; @@ -362,56 +380,6 @@ module.exports = ModelBase = (function(superClass) { } }; - ModelBase.prototype.cloneModel = function(newRoot, constructor, excludeAttributes) { - var childClone, filteredAttributes, i, key, len, modelObj, myClone, newVal, ref, ref1, val; - if (newRoot == null) { - newRoot = this.root; - } - if (constructor == null) { - constructor = this.constructor; - } - if (excludeAttributes == null) { - excludeAttributes = []; - } - filteredAttributes = {}; - ref = this.attributes; - for (key in ref) { - val = ref[key]; - if (indexOf.call(excludeAttributes, key) < 0) { - filteredAttributes[key] = val; - } - } - myClone = new constructor(filteredAttributes); - ref1 = myClone.attributes; - for (key in ref1) { - val = ref1[key]; - if (key === 'root') { - myClone.set(key, newRoot); - } else if (val instanceof ModelBase && (key !== 'root' && key !== 'parent')) { - myClone.set(key, val.cloneModel(newRoot)); - } else if (Array.isArray(val)) { - newVal = []; - if (val[0] instanceof ModelBase && key !== 'value') { - for (i = 0, len = val.length; i < len; i++) { - modelObj = val[i]; - childClone = modelObj.cloneModel(newRoot); - if (childClone.parent === this) { - childClone.parent = myClone; - if (key === 'options' && childClone.selected) { - myClone.addOptionValue(childClone.value); - } - } - newVal.push(childClone); - } - } else { - newVal = _.clone(val); - } - myClone.set(key, newVal); - } - } - return myClone; - }; - return ModelBase; -})(Backbone.Model); +}).call(this); diff --git a/lib/modelField.js b/lib/modelField.js index c482d77..4814bd7 100644 --- a/lib/modelField.js +++ b/lib/modelField.js @@ -1,7 +1,4 @@ -var ModelBase, ModelField, ModelOption, Mustache, globals, jiff, - extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, - hasProp = {}.hasOwnProperty, - slice = [].slice; +var ModelBase, ModelField, ModelOption, Mustache, globals, jiff; ModelBase = require('./modelBase'); @@ -13,516 +10,526 @@ Mustache = require('mustache'); jiff = require('jiff'); - /* A ModelField represents a model object that render as a DOM field NOTE: The following field types are subclasses: image, tree, date - */ - -module.exports = ModelField = (function(superClass) { - extend(ModelField, superClass); - - function ModelField() { - return ModelField.__super__.constructor.apply(this, arguments); - } - - ModelField.prototype.modelClassName = 'ModelField'; - - ModelField.prototype.initialize = function() { - var ref2, ref3; - this.setDefault('type', 'text'); - this.setDefault('options', []); - this.setDefault('value', (function() { - switch (this.get('type')) { - case 'multiselect': - return []; - case 'bool': - return false; - case 'info': - case 'button': - return void 0; - default: - return (this.get('defaultValue')) || ''; +*/ +module.exports = ModelField = (function() { + class ModelField extends ModelBase { + initialize() { + var ref2, ref3; + this.setDefault('type', 'text'); + this.setDefault('options', []); + this.setDefault('value', (function() { + switch (this.get('type')) { + case 'multiselect': + return []; + case 'bool': + return false; + case 'info': + case 'button': + return void 0; + default: + return (this.get('defaultValue')) || ''; + } + }).call(this)); + this.setDefault('defaultValue', this.get('value')); //used for control type and clear() + this.set('isValid', true); + this.setDefault('validators', []); + this.setDefault('onChangeHandlers', []); + this.setDefault('dynamicValue', null); + this.setDefault('template', null); + this.setDefault('autocomplete', null); + this.setDefault('beforeInput', function(val) { + return val; + }); + this.setDefault('beforeOutput', function(val) { + return val; + }); + super.initialize({ + objectMode: true + }); + //difficult to catch bad types at render time. error here instead + if ((ref2 = this.type) !== 'info' && ref2 !== 'text' && ref2 !== 'url' && ref2 !== 'email' && ref2 !== 'tel' && ref2 !== 'time' && ref2 !== 'date' && ref2 !== 'textarea' && ref2 !== 'bool' && ref2 !== 'tree' && ref2 !== 'color' && ref2 !== 'select' && ref2 !== 'multiselect' && ref2 !== 'image' && ref2 !== 'button' && ref2 !== 'number') { + return globals.handleError(`Bad field type: ${this.type}`); } - }).call(this)); - this.setDefault('defaultValue', this.get('value')); - this.set('isValid', true); - this.setDefault('validators', []); - this.setDefault('onChangeHandlers', []); - this.setDefault('dynamicValue', null); - this.setDefault('template', null); - this.setDefault('autocomplete', null); - this.setDefault('beforeInput', function(val) { - return val; - }); - this.setDefault('beforeOutput', function(val) { - return val; - }); - ModelField.__super__.initialize.apply(this, arguments); - if ((ref2 = this.type) !== 'info' && ref2 !== 'text' && ref2 !== 'url' && ref2 !== 'email' && ref2 !== 'tel' && ref2 !== 'time' && ref2 !== 'date' && ref2 !== 'textarea' && ref2 !== 'bool' && ref2 !== 'tree' && ref2 !== 'color' && ref2 !== 'select' && ref2 !== 'multiselect' && ref2 !== 'image' && ref2 !== 'button' && ref2 !== 'number') { - return globals.handleError("Bad field type: " + this.type); - } - this.bindPropFunctions('dynamicValue'); - while ((Array.isArray(this.value)) && (this.type !== 'multiselect') && (this.type !== 'tree') && (this.type !== 'button')) { - this.value = this.value[0]; - } - if (typeof this.value === 'string' && (this.type === 'multiselect')) { - this.value = [this.value]; - } - if (this.type === 'bool' && typeof this.value !== 'bool') { - this.value = !!this.value; - } - this.makePropArray('validators'); - this.bindPropFunctions('validators'); - this.makePropArray('onChangeHandlers'); - this.bindPropFunctions('onChangeHandlers'); - if (this.optionsFrom != null) { - this.ensureSelectType(); - if ((this.optionsFrom.url == null) || (this.optionsFrom.parseResults == null)) { - return globals.handleError('When fetching options remotely, both url and parseResults properties are required'); + this.bindPropFunctions('dynamicValue'); + // multiselects are arrays, others are strings. If typeof value doesn't match, convert it. + while ((Array.isArray(this.value)) && (this.type !== 'multiselect') && (this.type !== 'tree') && (this.type !== 'button')) { + this.value = this.value[0]; } - if (typeof ((ref3 = this.optionsFrom) != null ? ref3.url : void 0) === 'function') { - this.optionsFrom.url = this.bindPropFunction('optionsFrom.url', this.optionsFrom.url); + if (typeof this.value === 'string' && (this.type === 'multiselect')) { + this.value = [this.value]; } - if (typeof this.optionsFrom.parseResults !== 'function') { - return globals.handleError('optionsFrom.parseResults must be a function'); + //bools are special too. + if (this.type === 'bool' && typeof this.value !== 'bool') { + this.value = !!this.value; //convert to bool } - this.optionsFrom.parseResults = this.bindPropFunction('optionsFrom.parseResults', this.optionsFrom.parseResults); - } - this.updateOptionsSelected(); - this.on('change:value', function() { - var changeFunc, j, len1, ref4; - ref4 = this.onChangeHandlers; - for (j = 0, len1 = ref4.length; j < len1; j++) { - changeFunc = ref4[j]; - changeFunc(); + this.makePropArray('validators'); + this.bindPropFunctions('validators'); + //onChangeHandlers functions for field value changes only. For any property change, use onChangePropertiesHandlers + this.makePropArray('onChangeHandlers'); + this.bindPropFunctions('onChangeHandlers'); + if (this.optionsFrom != null) { + this.ensureSelectType(); + if ((this.optionsFrom.url == null) || (this.optionsFrom.parseResults == null)) { + return globals.handleError('When fetching options remotely, both url and parseResults properties are required'); + } + if (typeof ((ref3 = this.optionsFrom) != null ? ref3.url : void 0) === 'function') { + this.optionsFrom.url = this.bindPropFunction('optionsFrom.url', this.optionsFrom.url); + } + if (typeof this.optionsFrom.parseResults !== 'function') { + return globals.handleError('optionsFrom.parseResults must be a function'); + } + this.optionsFrom.parseResults = this.bindPropFunction('optionsFrom.parseResults', this.optionsFrom.parseResults); } - return this.updateOptionsSelected(); - }); - return this.on('change:type', function() { - if (this.type === 'multiselect') { - this.value = this.value.length > 0 ? [this.value] : []; - } else if (this.previousAttributes().type === 'multiselect') { - this.value = this.value.length > 0 ? this.value[0] : ''; + this.updateOptionsSelected(); + this.on('change:value', function() { + var changeFunc, j, len1, ref4; + ref4 = this.onChangeHandlers; + for (j = 0, len1 = ref4.length; j < len1; j++) { + changeFunc = ref4[j]; + changeFunc(); + } + return this.updateOptionsSelected(); + }); + // if type changes, need to update value + return this.on('change:type', function() { + if (this.type === 'multiselect') { + this.value = this.value.length > 0 ? [this.value] : []; + } else if (this.previousAttributes().type === 'multiselect') { + this.value = this.value.length > 0 ? this.value[0] : ''; + } + // must be *select if options present + if (this.options.length > 0 && !this.isSelectType()) { + return this.type = 'select'; + } + }); + } + + getOptionsFrom() { + var ref2, url; + if (this.optionsFrom == null) { + return; } - if (this.options.length > 0 && !this.isSelectType()) { - return this.type = 'select'; + url = typeof this.optionsFrom.url === 'function' ? this.optionsFrom.url() : this.optionsFrom.url; + if (this.prevUrl === url) { + return; } - }); - }; - - ModelField.prototype.getOptionsFrom = function() { - var ref2, url; - if (this.optionsFrom == null) { - return; - } - url = typeof this.optionsFrom.url === 'function' ? this.optionsFrom.url() : this.optionsFrom.url; - if (this.prevUrl === url) { - return; - } - this.prevUrl = url; - return typeof window !== "undefined" && window !== null ? (ref2 = window.formbuilderproxy) != null ? ref2.getFromProxy({ - url: url, - method: this.optionsFrom.method || 'get', - headerKey: this.optionsFrom.headerKey - }, (function(_this) { - return function(error, data) { + this.prevUrl = url; + return typeof window !== "undefined" && window !== null ? (ref2 = window.formbuilderproxy) != null ? ref2.getFromProxy({ + url: url, + method: this.optionsFrom.method || 'get', + headerKey: this.optionsFrom.headerKey + }, (error, data) => { var j, len1, mappedResults, opt, results1; if (error) { - return globals.handleError(globals.makeErrorMessage(_this, 'optionsFrom', error)); + return globals.handleError(globals.makeErrorMessage(this, 'optionsFrom', error)); } - mappedResults = _this.optionsFrom.parseResults(data); + mappedResults = this.optionsFrom.parseResults(data); if (!Array.isArray(mappedResults)) { return globals.handleError('results of parseResults must be an array of option parameters'); } - _this.options = []; + this.options = []; results1 = []; for (j = 0, len1 = mappedResults.length; j < len1; j++) { opt = mappedResults[j]; - results1.push(_this.option(opt)); + results1.push(this.option(opt)); } return results1; - }; - })(this)) : void 0 : void 0; - }; - - ModelField.prototype.validityMessage = void 0; + }) : void 0 : void 0; + } - ModelField.prototype.field = function() { - var obj, ref2; - obj = 1 <= arguments.length ? slice.call(arguments, 0) : []; - return (ref2 = this.parent).field.apply(ref2, obj); - }; + field(...obj) { + return this.parent.field(...obj); + } - ModelField.prototype.group = function() { - var obj, ref2; - obj = 1 <= arguments.length ? slice.call(arguments, 0) : []; - return (ref2 = this.parent).group.apply(ref2, obj); - }; + group(...obj) { + return this.parent.group(...obj); + } - ModelField.prototype.option = function() { - var newOption, nextOpts, opt, optionObject, optionParams; - optionParams = 1 <= arguments.length ? slice.call(arguments, 0) : []; - optionObject = this.buildParamObject(optionParams, ['title', 'value', 'selected', 'bidAdj', 'bidAdjFlag']); - this.ensureSelectType(); - nextOpts = (function() { - var j, len1, ref2, results1; - ref2 = this.options; - results1 = []; - for (j = 0, len1 = ref2.length; j < len1; j++) { - opt = ref2[j]; - if (opt.title !== optionObject.title) { - results1.push(opt); + option(...optionParams) { + var newOption, nextOpts, opt, optionObject; + optionObject = this.buildParamObject(optionParams, ['title', 'value', 'selected', 'bidAdj', 'bidAdjFlag']); + // when adding an option to a field, make sure it is a *select type + this.ensureSelectType(); + // If this option already exists, replace. Otherwise append + nextOpts = (function() { + var j, len1, ref2, results1; + ref2 = this.options; + results1 = []; + for (j = 0, len1 = ref2.length; j < len1; j++) { + opt = ref2[j]; + if (opt.title !== optionObject.title) { + results1.push(opt); + } } + return results1; + }).call(this); + newOption = new ModelOption(optionObject); + nextOpts.push(newOption); + this.options = nextOpts; + //if new option has selected:true, set this field's value to that + //don't remove from parent value if not selected. Might be supplied by field value during creation. + if (newOption.selected) { + this.addOptionValue(newOption.value); } - return results1; - }).call(this); - newOption = new ModelOption(optionObject); - nextOpts.push(newOption); - this.options = nextOpts; - if (newOption.selected) { - this.addOptionValue(newOption.value); + return this; } - return this; - }; - ModelField.prototype.postBuild = function() { - this.defaultValue = this.value; - return this.updateOptionsSelected(); - }; + postBuild() { + // options may have changed the starting value, so update the defaultValue to that + this.defaultValue = this.value; //todo: NO! need to clone this in case value isnt primitive + //update each option's selected status to match the field value + return this.updateOptionsSelected(); + } - ModelField.prototype.updateOptionsSelected = function() { - var bid, i, len, opt, ref, ref1, results; - ref = this.options; - results = []; - i = 0; - len = ref.length; - while (i < len) { - opt = ref[i]; - if ((ref1 = this.type) === 'multiselect' || ref1 === 'tree') { - bid = this.hasValue(opt.value); - if (bid.bidValue) { - opt.bidAdj = bid.bidValue.lastIndexOf('/') !== -1 ? bid.bidValue.split("/").pop() : this.bidAdj; + updateOptionsSelected() { + var bid, i, len, opt, ref, ref1, results; + ref = this.options; + results = []; + i = 0; + len = ref.length; + while (i < len) { + opt = ref[i]; + if ((ref1 = this.type) === 'multiselect' || ref1 === 'tree') { + bid = this.hasValue(opt.value); + if (bid.bidValue) { + opt.bidAdj = bid.bidValue.lastIndexOf('/') !== -1 ? bid.bidValue.split("/").pop() : this.bidAdj; + } + results.push(opt.selected = bid.selectStatus); + } else { + results.push(opt.selected = this.hasValue(opt.value)); } - results.push(opt.selected = bid.selectStatus); - } else { - results.push(opt.selected = this.hasValue(opt.value)); + i++; } - i++; + return results; } - return results; - }; - ModelField.prototype.isSelectType = function() { - var ref2; - return (ref2 = this.type) === 'select' || ref2 === 'multiselect' || ref2 === 'image' || ref2 === 'tree'; - }; - - ModelField.prototype.ensureSelectType = function() { - if (!this.isSelectType()) { - return this.type = 'select'; + // returns true if this type is one where a value is selected. Otherwise false + isSelectType() { + var ref2; + return (ref2 = this.type) === 'select' || ref2 === 'multiselect' || ref2 === 'image' || ref2 === 'tree'; } - }; - ModelField.prototype.child = function(value) { - var j, len1, o, ref2; - if (Array.isArray(value)) { - value = value.shift(); - } - ref2 = this.options; - for (j = 0, len1 = ref2.length; j < len1; j++) { - o = ref2[j]; - if (o.value === value) { - return o; + // certain operations require one of the select types. If its not already, change field type to select + ensureSelectType() { + if (!this.isSelectType()) { + return this.type = 'select'; } } - }; - ModelField.prototype.validator = function(func) { - this.validators.push(this.bindPropFunction('validator', func)); - this.trigger('change'); - return this; - }; + // find an option by value. Uses the same child method as groups and fields to find constituent objects + child(value) { + var j, len1, o, ref2; + if (Array.isArray(value)) { + value = value.shift(); + } + ref2 = this.options; + for (j = 0, len1 = ref2.length; j < len1; j++) { + o = ref2[j]; + if (o.value === value) { + return o; + } + } + } - ModelField.prototype.onChange = function(f) { - this.onChangeHandlers.push(this.bindPropFunction('onChange', f)); - this.trigger('change'); - return this; - }; + // add a new validator function + validator(func) { + this.validators.push(this.bindPropFunction('validator', func)); + this.trigger('change'); + return this; + } - ModelField.prototype.setDirty = function(id, whatChanged) { - var j, len1, opt, ref2; - ref2 = this.options; - for (j = 0, len1 = ref2.length; j < len1; j++) { - opt = ref2[j]; - opt.setDirty(id, whatChanged); + // add a new onChangeHandler function that triggers when the field's value changes + onChange(f) { + this.onChangeHandlers.push(this.bindPropFunction('onChange', f)); + this.trigger('change'); + return this; } - return ModelField.__super__.setDirty.call(this, id, whatChanged); - }; - ModelField.prototype.setClean = function(all) { - var j, len1, opt, ref2, results1; - ModelField.__super__.setClean.apply(this, arguments); - if (all) { + setDirty(id, whatChanged) { + var j, len1, opt, ref2; ref2 = this.options; - results1 = []; for (j = 0, len1 = ref2.length; j < len1; j++) { opt = ref2[j]; - results1.push(opt.setClean(all)); + opt.setDirty(id, whatChanged); } - return results1; + return super.setDirty(id, whatChanged); } - }; - ModelField.prototype.recalculateRelativeProperties = function() { - var dirty, j, k, len1, len2, opt, ref2, ref3, results1, validator, validityMessage, value; - dirty = this.dirty; - ModelField.__super__.recalculateRelativeProperties.apply(this, arguments); - if (this.shouldCallTriggerFunctionFor(dirty, 'isValid')) { - validityMessage = void 0; - if (this.template) { - validityMessage || (validityMessage = this.validate.template.call(this)); - } - if (this.type === 'number') { - validityMessage || (validityMessage = this.validate.number.call(this)); - } - if (!validityMessage) { - ref2 = this.validators; - for (j = 0, len1 = ref2.length; j < len1; j++) { - validator = ref2[j]; - if (typeof validator === 'function') { - validityMessage = validator.call(this); - } - if (typeof validityMessage === 'function') { - return globals.handleError("A validator on field '" + this.name + "' returned a function"); - } - if (validityMessage) { - break; - } - } - } - this.validityMessage = validityMessage; - this.set({ - isValid: validityMessage == null + setClean(all) { + var j, len1, opt, ref2, results1; + super.setClean({ + objectMode: true }); - } - if (this.template && this.shouldCallTriggerFunctionFor(dirty, 'value')) { - this.renderTemplate(); - } else { - if (typeof this.dynamicValue === 'function' && this.shouldCallTriggerFunctionFor(dirty, 'value')) { - value = this.dynamicValue(); - if (typeof value === 'function') { - return globals.handleError("dynamicValue on field '" + this.name + "' returned a function"); + if (all) { + ref2 = this.options; + results1 = []; + for (j = 0, len1 = ref2.length; j < len1; j++) { + opt = ref2[j]; + results1.push(opt.setClean(all)); } - this.set('value', value); + return results1; } } - if (this.shouldCallTriggerFunctionFor(dirty, 'options')) { - this.getOptionsFrom(); - } - ref3 = this.options; - results1 = []; - for (k = 0, len2 = ref3.length; k < len2; k++) { - opt = ref3[k]; - results1.push(opt.recalculateRelativeProperties()); - } - return results1; - }; - ModelField.prototype.addOptionValue = function(val, bidAdj) { - var findMatch, ref; - findMatch = void 0; - ref = void 0; - if ((ref = this.type) === 'multiselect' || ref === 'tree') { - if (!Array.isArray(this.value)) { - this.value = [this.value]; - } - findMatch = this.value.findIndex(function(e) { - if (typeof e === 'string') { - return e.search(val) !== -1; - } else { - return e === val; - } + recalculateRelativeProperties() { + var dirty, j, k, len1, len2, opt, ref2, ref3, results1, validator, validityMessage, value; + dirty = this.dirty; + super.recalculateRelativeProperties({ + objectMode: true }); - if (findMatch !== -1) { - if (bidAdj) { - return this.value[findMatch] = val + '/' + bidAdj; + // validity + // only fire if isValid changes. If isValid stays false but message changes, don't need to re-fire. + if (this.shouldCallTriggerFunctionFor(dirty, 'isValid')) { + validityMessage = void 0; + //certain validators are automatic on fields with certain properties + if (this.template) { + validityMessage || (validityMessage = this.validate.template.call(this)); } + if (this.type === 'number') { + validityMessage || (validityMessage = this.validate.number.call(this)); + } + //if no problems yet, try all the user-defined validators + if (!validityMessage) { + ref2 = this.validators; + for (j = 0, len1 = ref2.length; j < len1; j++) { + validator = ref2[j]; + if (typeof validator === 'function') { + validityMessage = validator.call(this); + } + if (typeof validityMessage === 'function') { + return globals.handleError(`A validator on field '${this.name}' returned a function`); + } + if (validityMessage) { + break; + } + } + } + this.validityMessage = validityMessage; + this.set({ + isValid: validityMessage == null + }); + } + // Fields with a template property can't also have a dynamicValue property. + if (this.template && this.shouldCallTriggerFunctionFor(dirty, 'value')) { + this.renderTemplate(); } else { - if (bidAdj) { - return this.value.push(val + '/' + bidAdj); - } else { - return this.value.push(val); + //dynamic value + if (typeof this.dynamicValue === 'function' && this.shouldCallTriggerFunctionFor(dirty, 'value')) { + value = this.dynamicValue(); + if (typeof value === 'function') { + return globals.handleError(`dynamicValue on field '${this.name}' returned a function`); + } + this.set('value', value); } } - } else { - return this.value = val; + if (this.shouldCallTriggerFunctionFor(dirty, 'options')) { + this.getOptionsFrom(); + } + ref3 = this.options; + results1 = []; + for (k = 0, len2 = ref3.length; k < len2; k++) { + opt = ref3[k]; + results1.push(opt.recalculateRelativeProperties()); + } + return results1; } - }; - ModelField.prototype.removeOptionValue = function(val) { - var ref; - ref = void 0; - if ((ref = this.type) === 'multiselect' || ref === 'tree') { - return this.value = this.value.filter(function(e) { - if (typeof e === 'string') { - return e.search(val) === -1; - } else { - return e !== val; + addOptionValue(val, bidAdj) { + var findMatch, ref; + findMatch = void 0; + ref = void 0; + if ((ref = this.type) === 'multiselect' || ref === 'tree') { + if (!Array.isArray(this.value)) { + this.value = [this.value]; } - }); - } else if (this.value === val) { - return this.value = ''; - } - }; - - ModelField.prototype.hasValue = function(val) { - var findMatch, ref; - findMatch = void 0; - ref = void 0; - if ((ref = this.type) === 'multiselect' || ref === 'tree') { - findMatch = this.value.findIndex(function(e) { - if (typeof e === 'string') { - return e.search(val) !== -1; + findMatch = this.value.findIndex(function(e) { + if (typeof e === 'string') { + return e.search(val) !== -1; + } else { + return e === val; + } + }); + if (findMatch !== -1) { + if (bidAdj) { + return this.value[findMatch] = val + '/' + bidAdj; + } } else { - return e === val; + if (bidAdj) { + return this.value.push(val + '/' + bidAdj); + } else { + return this.value.push(val); + } } - }); - if (findMatch !== -1) { - return { - 'bidValue': this.value[findMatch], - 'selectStatus': true - }; } else { - return { - 'selectStatus': false - }; + return this.value = val; } - } else { - return val === this.value; } - }; - ModelField.prototype.buildOutputData = function(_, skipBeforeOutput) { - var out, value; - value = (function() { - switch (this.type) { - case 'number': - out = +this.value; - if (isNaN(out)) { - return null; + removeOptionValue(val) { + var ref; + ref = void 0; + if ((ref = this.type) === 'multiselect' || ref === 'tree') { + return this.value = this.value.filter(function(e) { + if (typeof e === 'string') { + return e.search(val) === -1; } else { - return out; + return e !== val; } - break; - case 'info': - case 'button': - return void 0; - case 'bool': - return !!this.value; - default: - return this.value; + }); + } else if (this.value === val) { + return this.value = ''; } - }).call(this); - if (skipBeforeOutput) { - return value; - } else { - return this.beforeOutput(value); } - }; - ModelField.prototype.clear = function(purgeDefaults) { - if (purgeDefaults == null) { - purgeDefaults = false; + hasValue(val) { + var findMatch, ref; + findMatch = void 0; + ref = void 0; + if ((ref = this.type) === 'multiselect' || ref === 'tree') { + findMatch = this.value.findIndex(function(e) { + if (typeof e === 'string') { + return e.search(val) !== -1; + } else { + return e === val; + } + }); + if (findMatch !== -1) { + return { + 'bidValue': this.value[findMatch], + 'selectStatus': true + }; + } else { + return { + 'selectStatus': false + }; + } + } else { + return val === this.value; + } } - if (purgeDefaults) { - return this.value = (function() { + + buildOutputData(_, skipBeforeOutput) { + var out, value; + value = (function() { switch (this.type) { - case 'multiselect': - return []; + case 'number': + out = +this.value; + if (isNaN(out)) { + return null; + } else { + return out; + } + break; + case 'info': + case 'button': + return void 0; case 'bool': - return false; + return !!this.value; default: - return ''; + return this.value; } }).call(this); - } else { - return this.value = this.defaultValue; + if (skipBeforeOutput) { + return value; + } else { + return this.beforeOutput(value); + } } - }; - ModelField.prototype.ensureValueInOptions = function() { - var existingOption, j, k, l, len1, len2, len3, o, ref2, ref3, ref4, results1, v; - if (!this.isSelectType()) { - return; - } - if (typeof this.value === 'string') { - ref2 = this.options; - for (j = 0, len1 = ref2.length; j < len1; j++) { - o = ref2[j]; - if (o.value === this.value) { - existingOption = o; - } + clear(purgeDefaults = false) { + if (purgeDefaults) { + return this.value = (function() { + switch (this.type) { + case 'multiselect': + return []; + case 'bool': + return false; + default: + return ''; + } + }).call(this); + } else { + return this.value = this.defaultValue; } - if (!existingOption) { - return this.option(this.value, { - selected: true - }); + } + + ensureValueInOptions() { + var existingOption, j, k, l, len1, len2, len3, o, ref2, ref3, ref4, results1, v; + if (!this.isSelectType()) { + return; } - } else if (Array.isArray(this.value)) { - ref3 = this.value; - results1 = []; - for (k = 0, len2 = ref3.length; k < len2; k++) { - v = ref3[k]; - existingOption = null; - ref4 = this.options; - for (l = 0, len3 = ref4.length; l < len3; l++) { - o = ref4[l]; - if (o.value === v) { + if (typeof this.value === 'string') { + ref2 = this.options; + for (j = 0, len1 = ref2.length; j < len1; j++) { + o = ref2[j]; + if (o.value === this.value) { existingOption = o; } } if (!existingOption) { - results1.push(this.option(v, { + return this.option(this.value, { selected: true - })); - } else { - results1.push(void 0); + }); } + } else if (Array.isArray(this.value)) { + ref3 = this.value; + results1 = []; + for (k = 0, len2 = ref3.length; k < len2; k++) { + v = ref3[k]; + existingOption = null; + ref4 = this.options; + for (l = 0, len3 = ref4.length; l < len3; l++) { + o = ref4[l]; + if (o.value === v) { + existingOption = o; + } + } + if (!existingOption) { + results1.push(this.option(v, { + selected: true + })); + } else { + results1.push(void 0); + } + } + return results1; } - return results1; } - }; - ModelField.prototype.applyData = function(inData, clear, purgeDefaults) { - if (clear == null) { - clear = false; - } - if (purgeDefaults == null) { - purgeDefaults = false; - } - if (clear) { - this.clear(purgeDefaults); - } - if (inData != null) { - return this.value = this.beforeInput(jiff.clone(inData)); + applyData(inData, clear = false, purgeDefaults = false) { + if (clear) { + this.clear(purgeDefaults); + } + if (inData != null) { + return this.value = this.beforeInput(jiff.clone(inData)); + } } - }; - ModelField.prototype.renderTemplate = function() { - var template; - if (typeof this.template === 'object') { - template = this.template.value; - } else { - template = this.parent.child(this.template).value; - } - try { - return this.value = Mustache.render(template, this.root.data); - } catch (error1) { + //HUB-2766 this is no longer necessary as we now have biding changing option + //@ensureValueInOptions() + renderTemplate() { + var template; + if (typeof this.template === 'object') { + template = this.template.value; + } else { + template = this.parent.child(this.template).value; + } + try { + return this.value = Mustache.render(template, this.root.data); + } catch (error1) { + } } + }; + ModelField.prototype.modelClassName = 'ModelField'; + + ModelField.prototype.validityMessage = void 0; + return ModelField; -})(ModelBase); +}).call(this); diff --git a/lib/modelFieldDate.js b/lib/modelFieldDate.js index c8f3aa7..bf68e8a 100644 --- a/lib/modelFieldDate.js +++ b/lib/modelFieldDate.js @@ -1,44 +1,26 @@ -var ModelField, ModelFieldDate, moment, - extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, - hasProp = {}.hasOwnProperty; +var ModelField, ModelFieldDate, moment; ModelField = require('./modelField'); moment = require('moment'); -module.exports = ModelFieldDate = (function(superClass) { - extend(ModelFieldDate, superClass); - - function ModelFieldDate() { - return ModelFieldDate.__super__.constructor.apply(this, arguments); - } - - ModelFieldDate.prototype.initialize = function() { +module.exports = ModelFieldDate = class ModelFieldDate extends ModelField { + initialize() { this.setDefault('format', 'M/D/YYYY'); - ModelFieldDate.__super__.initialize.apply(this, arguments); + super.initialize({ + objectMode: true + }); return this.validator(this.validate.date); - }; + } - ModelFieldDate.prototype.dateToString = function(date, format) { - if (date == null) { - date = this.value; - } - if (format == null) { - format = this.format; - } + // Convert date to string according to this format + dateToString(date = this.value, format = this.format) { return moment(date).format(format); - }; + } - ModelFieldDate.prototype.stringToDate = function(str, format) { - if (str == null) { - str = this.value; - } - if (format == null) { - format = this.format; - } + // Convert string in this format to a date. Could be an invalid date. + stringToDate(str = this.value, format = this.format) { return moment(str, format, true).toDate(); - }; - - return ModelFieldDate; + } -})(ModelField); +}; diff --git a/lib/modelFieldImage.js b/lib/modelFieldImage.js index 89f9a7f..ec56c05 100644 --- a/lib/modelFieldImage.js +++ b/lib/modelFieldImage.js @@ -1,18 +1,10 @@ -var ModelField, ModelFieldImage, - extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, - hasProp = {}.hasOwnProperty, - slice = [].slice; +var ModelField, ModelFieldImage; ModelField = require('./modelField'); -module.exports = ModelFieldImage = (function(superClass) { - extend(ModelFieldImage, superClass); - - function ModelFieldImage() { - return ModelFieldImage.__super__.constructor.apply(this, arguments); - } - - ModelFieldImage.prototype.initialize = function() { +// An image field is different enough from other fields to warrant its own subclass +module.exports = ModelFieldImage = class ModelFieldImage extends ModelField { + initialize() { this.setDefault('value', {}); this.setDefault('allowUpload', false); this.setDefault('imagesPerPage', 4); @@ -22,13 +14,16 @@ module.exports = ModelFieldImage = (function(superClass) { this.setDefault('maxHeight', 0); this.setDefault('minSize', 0); this.setDefault('maxSize', 0); - this.set('optionsChanged', false); - return ModelFieldImage.__super__.initialize.apply(this, arguments); - }; + this.set('optionsChanged', false); //React needs to know if the number of options changed, + // as this requires a full reinit of the plugin at render time that is not necessary for other changes. + return super.initialize({ + objectMode: true + }); + } - ModelFieldImage.prototype.option = function() { - var optionObject, optionParams; - optionParams = 1 <= arguments.length ? slice.call(arguments, 0) : []; + // Override behaviors different from other fields. + option(...optionParams) { + var optionObject; optionObject = this.buildParamObject(optionParams, ['fileID', 'fileUrl', 'thumbnailUrl']); if (optionObject.fileID == null) { optionObject.fileID = optionObject.fileUrl; @@ -41,19 +36,21 @@ module.exports = ModelFieldImage = (function(superClass) { fileUrl: optionObject.fileUrl, thumbnailUrl: optionObject.thumbnailUrl }; + // use fileID as the key because this is how they are selected when rendered. if (optionObject.title == null) { optionObject.title = optionObject.fileID; } - this.optionsChanged = true; - return ModelFieldImage.__super__.option.call(this, optionObject); - }; + this.optionsChanged = true; //required to reinit the carousel in the ui + return super.option(optionObject); + } - ModelFieldImage.prototype.child = function(fileID) { + // image values are objects, so lookup children by fileid instead + child(fileID) { var i, len, o, ref; if (Array.isArray(fileID)) { fileID = fileID.shift(); } - if (typeof fileID === 'object') { + if (typeof fileID === 'object') { //if lookup by full object value fileID = fileID.fileID; } ref = this.options; @@ -63,26 +60,23 @@ module.exports = ModelFieldImage = (function(superClass) { return o; } } - }; + } - ModelFieldImage.prototype.removeOptionValue = function(val) { + removeOptionValue(val) { if (this.value.fileID === val.fileID) { return this.value = {}; } - }; + } - ModelFieldImage.prototype.hasValue = function(val) { + hasValue(val) { return val.fileID === this.value.fileID && val.thumbnailUrl === this.value.thumbnailUrl && val.fileUrl === this.value.fileUrl; - }; + } - ModelFieldImage.prototype.clear = function(purgeDefaults) { - if (purgeDefaults == null) { - purgeDefaults = false; - } + clear(purgeDefaults = false) { return this.value = purgeDefaults ? {} : this.defaultValue; - }; + } - ModelFieldImage.prototype.ensureValueInOptions = function() { + ensureValueInOptions() { var existingOption, i, len, o, ref; ref = this.options; for (i = 0, len = ref.length; i < len; i++) { @@ -94,8 +88,6 @@ module.exports = ModelFieldImage = (function(superClass) { if (!existingOption) { return this.option(this.value); } - }; - - return ModelFieldImage; + } -})(ModelField); +}; diff --git a/lib/modelFieldTree.js b/lib/modelFieldTree.js index 3008454..ca15c1f 100644 --- a/lib/modelFieldTree.js +++ b/lib/modelFieldTree.js @@ -1,25 +1,17 @@ -var ModelField, ModelFieldTree, - extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, - hasProp = {}.hasOwnProperty, - slice = [].slice; +var ModelField, ModelFieldTree; ModelField = require('./modelField'); -module.exports = ModelFieldTree = (function(superClass) { - extend(ModelFieldTree, superClass); - - function ModelFieldTree() { - return ModelFieldTree.__super__.constructor.apply(this, arguments); - } - - ModelFieldTree.prototype.initialize = function() { +module.exports = ModelFieldTree = class ModelFieldTree extends ModelField { + initialize() { this.setDefault('value', []); - return ModelFieldTree.__super__.initialize.apply(this, arguments); - }; + return super.initialize({ + objectMode: true + }); + } - ModelFieldTree.prototype.option = function() { - var optionObject, optionParams; - optionParams = 1 <= arguments.length ? slice.call(arguments, 0) : []; + option(...optionParams) { + var optionObject; optionObject = this.buildParamObject(optionParams, ['path', 'value', 'selected']); if (optionObject.value == null) { optionObject.value = optionObject.id; @@ -27,17 +19,12 @@ module.exports = ModelFieldTree = (function(superClass) { if (optionObject.value == null) { optionObject.value = optionObject.path.join(' > '); } - optionObject.title = optionObject.path.join('>'); - return ModelFieldTree.__super__.option.call(this, optionObject); - }; + optionObject.title = optionObject.path.join('>'); //use path as the key since that is what is rendered. + return super.option(optionObject); + } - ModelFieldTree.prototype.clear = function(purgeDefaults) { - if (purgeDefaults == null) { - purgeDefaults = false; - } + clear(purgeDefaults = false) { return this.value = purgeDefaults ? [] : this.defaultValue; - }; - - return ModelFieldTree; + } -})(ModelField); +}; diff --git a/lib/modelGroup.js b/lib/modelGroup.js index ea30836..37a5f13 100644 --- a/lib/modelGroup.js +++ b/lib/modelGroup.js @@ -1,7 +1,8 @@ -var ModelBase, ModelField, ModelFieldDate, ModelFieldImage, ModelFieldTree, ModelGroup, RepeatingModelGroup, globals, jiff, - extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, - hasProp = {}.hasOwnProperty, - slice = [].slice; + +/* + Encapsulates a group of form objects that can be added or removed to the form together multiple times +*/ +var ModelBase, ModelField, ModelFieldDate, ModelFieldImage, ModelFieldTree, ModelGroup, RepeatingModelGroup, globals, jiff; ModelBase = require('./modelBase'); @@ -17,393 +18,374 @@ globals = require('./globals'); jiff = require('jiff'); - /* A ModelGroup is a model object that can contain any number of other groups and fields - */ - -module.exports = ModelGroup = (function(superClass) { - extend(ModelGroup, superClass); - - function ModelGroup() { - return ModelGroup.__super__.constructor.apply(this, arguments); - } - - ModelGroup.prototype.modelClassName = 'ModelGroup'; - - ModelGroup.prototype.initialize = function() { - this.setDefault('children', []); - this.setDefault('root', this); - this.set('isValid', true); - this.set('data', null); - this.setDefault('beforeInput', function(val) { - return val; - }); - this.setDefault('beforeOutput', function(val) { - return val; - }); - return ModelGroup.__super__.initialize.apply(this, arguments); - }; - - ModelGroup.prototype.postBuild = function() { - var child, i, len, ref, results; - ref = this.children; - results = []; - for (i = 0, len = ref.length; i < len; i++) { - child = ref[i]; - results.push(child.postBuild()); - } - return results; - }; - - ModelGroup.prototype.field = function() { - var fieldObject, fieldParams, fld; - fieldParams = 1 <= arguments.length ? slice.call(arguments, 0) : []; - fieldObject = this.buildParamObject(fieldParams, ['title', 'name', 'type', 'value']); - if (fieldObject.disabled == null) { - fieldObject.disabled = this.disabled; - } - fld = (function() { - switch (fieldObject.type) { - case 'image': - return new ModelFieldImage(fieldObject); - case 'tree': - return new ModelFieldTree(fieldObject); - case 'date': - return new ModelFieldDate(fieldObject); - default: - return new ModelField(fieldObject); +*/ +module.exports = ModelGroup = (function() { + class ModelGroup extends ModelBase { + initialize() { + this.setDefault('children', []); + this.setDefault('root', this); + this.set('isValid', true); + this.set('data', null); + this.setDefault('beforeInput', function(val) { + return val; + }); + this.setDefault('beforeOutput', function(val) { + return val; + }); + return super.initialize({ + objectMode: true + }); + } + + postBuild() { + var child, i, len, ref, results; + ref = this.children; + results = []; + for (i = 0, len = ref.length; i < len; i++) { + child = ref[i]; + results.push(child.postBuild()); } - })(); - this.children.push(fld); - this.trigger('change'); - return fld; - }; + return results; + } - ModelGroup.prototype.group = function() { - var groupObject, groupParams, grp, key, ref, val; - groupParams = 1 <= arguments.length ? slice.call(arguments, 0) : []; - grp = {}; - if (((ref = groupParams[0].constructor) != null ? ref.name : void 0) === 'ModelGroup') { - grp = groupParams[0].cloneModel(this.root); - groupParams.shift(); - groupObject = this.buildParamObject(groupParams, ['title', 'name', 'description']); - if (groupObject.name == null) { - groupObject.name = groupObject.title; + field(...fieldParams) { + var fieldObject, fld; + fieldObject = this.buildParamObject(fieldParams, ['title', 'name', 'type', 'value']); + if (fieldObject.disabled == null) { + fieldObject.disabled = this.disabled; } - if (groupObject.title == null) { - groupObject.title = groupObject.name; - } - for (key in groupObject) { - val = groupObject[key]; - grp.set(key, val); - } - } else { - groupObject = this.buildParamObject(groupParams, ['title', 'name', 'description']); - if (groupObject.disabled == null) { - groupObject.disabled = this.disabled; - } - if (groupObject.repeating) { - grp = new RepeatingModelGroup(groupObject); + //Could move this to a factory, but fields should only be created here so probably not necessary. + fld = (function() { + switch (fieldObject.type) { + case 'image': + return new ModelFieldImage(fieldObject); + case 'tree': + return new ModelFieldTree(fieldObject); + case 'date': + return new ModelFieldDate(fieldObject); + default: + return new ModelField(fieldObject); + } + })(); + this.children.push(fld); + this.trigger('change'); + return fld; + } + + group(...groupParams) { + var groupObject, grp, key, ref, val; + grp = {}; + if (((ref = groupParams[0].constructor) != null ? ref.name : void 0) === 'ModelGroup') { + grp = groupParams[0].cloneModel(this.root); + //set any other supplied params on the clone + groupParams.shift(); //remove the cloned object + groupObject = this.buildParamObject(groupParams, ['title', 'name', 'description']); + if (groupObject.name == null) { + groupObject.name = groupObject.title; + } + if (groupObject.title == null) { + groupObject.title = groupObject.name; + } + for (key in groupObject) { + val = groupObject[key]; + grp.set(key, val); + } } else { - grp = new ModelGroup(groupObject); + groupObject = this.buildParamObject(groupParams, ['title', 'name', 'description']); + if (groupObject.disabled == null) { + groupObject.disabled = this.disabled; + } + if (groupObject.repeating) { + grp = new RepeatingModelGroup(groupObject); + } else { + grp = new ModelGroup(groupObject); + } } + this.children.push(grp); + this.trigger('change'); + return grp; } - this.children.push(grp); - this.trigger('change'); - return grp; - }; - ModelGroup.prototype.child = function(path) { - var c, child, i, len, name, ref; - if (!(Array.isArray(path))) { - path = path.split(/[.\/]/); - } - name = path.shift(); - ref = this.children; - for (i = 0, len = ref.length; i < len; i++) { - c = ref[i]; - if (c.name === name) { - child = c; + // find a child by name. + // can also find descendants multiple levels down by supplying one of + // * an array of child names (in order) eg: group.child(['childname','grandchildname','greatgrandchildname']) + // * a dot or slash delimited string. eg: group.child('childname.grandchildname.greatgrandchildname') + child(path) { + var c, child, i, len, name, ref; + if (!(Array.isArray(path))) { + path = path.split(/[.\/]/); + } + name = path.shift(); + ref = this.children; + for (i = 0, len = ref.length; i < len; i++) { + c = ref[i]; + if (c.name === name) { + child = c; + } + } + if (path.length === 0) { + return child; + } else { + return child.child(path); } } - if (path.length === 0) { - return child; - } else { - return child.child(path); - } - }; - - ModelGroup.prototype.setDirty = function(id, whatChanged) { - var child, i, len, ref; - ref = this.children; - for (i = 0, len = ref.length; i < len; i++) { - child = ref[i]; - child.setDirty(id, whatChanged); - } - return ModelGroup.__super__.setDirty.call(this, id, whatChanged); - }; - ModelGroup.prototype.setClean = function(all) { - var child, i, len, ref, results; - ModelGroup.__super__.setClean.apply(this, arguments); - if (all) { + setDirty(id, whatChanged) { + var child, i, len, ref; ref = this.children; - results = []; for (i = 0, len = ref.length; i < len; i++) { child = ref[i]; - results.push(child.setClean(all)); + child.setDirty(id, whatChanged); + } + return super.setDirty(id, whatChanged); + } + + setClean(all) { + var child, i, len, ref, results; + super.setClean({ + objectMode: true + }); + if (all) { + ref = this.children; + results = []; + for (i = 0, len = ref.length; i < len; i++) { + child = ref[i]; + results.push(child.setClean(all)); + } + return results; } - return results; } - }; - ModelGroup.prototype.recalculateRelativeProperties = function(collection) { - var child, dirty, i, len, newValid; - if (collection == null) { - collection = this.children; - } - dirty = this.dirty; - ModelGroup.__super__.recalculateRelativeProperties.apply(this, arguments); - newValid = true; - for (i = 0, len = collection.length; i < len; i++) { - child = collection[i]; - child.recalculateRelativeProperties(); - newValid && (newValid = child.isValid); + recalculateRelativeProperties(collection = this.children) { + var child, dirty, i, len, newValid; + dirty = this.dirty; + super.recalculateRelativeProperties({ + objectMode: true + }); + //group is valid if all children are valid + //might not need to check validy, but always need to recalculate all children anyway. + newValid = true; + for (i = 0, len = collection.length; i < len; i++) { + child = collection[i]; + child.recalculateRelativeProperties(); + newValid && (newValid = child.isValid); + } + return this.isValid = newValid; } - return this.isValid = newValid; - }; - ModelGroup.prototype.buildOutputData = function(group, skipBeforeOutput) { - var obj; - if (group == null) { - group = this; - } - obj = {}; - group.children.forEach(function(child) { - var childData; - childData = child.buildOutputData(void 0, skipBeforeOutput); - if (childData !== void 0) { - return obj[child.name] = childData; + buildOutputData(group = this, skipBeforeOutput) { + var obj; + obj = {}; + group.children.forEach(function(child) { + var childData; + childData = child.buildOutputData(void 0, skipBeforeOutput); + if (childData !== void 0) { // undefined values do not appear in output, but nulls do + return obj[child.name] = childData; + } + }); + if (skipBeforeOutput) { + return obj; + } else { + return group.beforeOutput(obj); } - }); - if (skipBeforeOutput) { - return obj; - } else { - return group.beforeOutput(obj); } - }; - - ModelGroup.prototype.buildOutputDataString = function() { - return JSON.stringify(this.buildOutputData()); - }; - ModelGroup.prototype.clear = function(purgeDefaults) { - var child, i, j, key, len, len1, ref, ref1, results; - if (purgeDefaults == null) { - purgeDefaults = false; + buildOutputDataString() { + return JSON.stringify(this.buildOutputData()); } - if (this.data) { - ref = Object.keys(this.data); - for (i = 0, len = ref.length; i < len; i++) { - key = ref[i]; - delete this.data[key]; + + clear(purgeDefaults = false) { + var child, i, j, key, len, len1, ref, ref1, results; + //reset the 'data' object in-place, so model code will see an empty object too. + if (this.data) { + ref = Object.keys(this.data); + for (i = 0, len = ref.length; i < len; i++) { + key = ref[i]; + delete this.data[key]; + } } + ref1 = this.children; + results = []; + for (j = 0, len1 = ref1.length; j < len1; j++) { + child = ref1[j]; + results.push(child.clear(purgeDefaults)); + } + return results; } - ref1 = this.children; - results = []; - for (j = 0, len1 = ref1.length; j < len1; j++) { - child = ref1[j]; - results.push(child.clear(purgeDefaults)); - } - return results; - }; - ModelGroup.prototype.applyData = function(inData, clear, purgeDefaults) { - var finalInData, key, ref, results, value; - if (clear == null) { - clear = false; - } - if (purgeDefaults == null) { - purgeDefaults = false; - } - if (clear) { - this.clear(purgeDefaults); - } - finalInData = this.beforeInput(jiff.clone(inData)); - - /* - This section preserves a link to the initially applied data object and merges subsequent applies on top - of it in-place. This is necessary for two reasons. - First, the scope of the running model code also references the applied data through the 'data' variable. - Every applied data must be available even though the runtime is not re-evaluated each time. - Second, templated fields use this data as the input to their Mustache evaluation. See @renderTemplate() - */ - if (this.data) { - globals.mergeData(this.data, inData); - this.trigger('change'); - } else { - this.data = inData; - } - results = []; - for (key in finalInData) { - value = finalInData[key]; - results.push((ref = this.child(key)) != null ? ref.applyData(value) : void 0); + applyData(inData, clear = false, purgeDefaults = false) { + var finalInData, key, ref, results, value; + if (clear) { + this.clear(purgeDefaults); + } + finalInData = this.beforeInput(jiff.clone(inData)); + /* + This section preserves a link to the initially applied data object and merges subsequent applies on top + of it in-place. This is necessary for two reasons. + First, the scope of the running model code also references the applied data through the 'data' variable. + Every applied data must be available even though the runtime is not re-evaluated each time. + Second, templated fields use this data as the input to their Mustache evaluation. See @renderTemplate() + */ + if (this.data) { + globals.mergeData(this.data, inData); + this.trigger('change'); + } else { + this.data = inData; + } + results = []; + for (key in finalInData) { + value = finalInData[key]; + results.push((ref = this.child(key)) != null ? ref.applyData(value) : void 0); + } + return results; } - return results; - }; - - return ModelGroup; -})(ModelBase); - - -/* - Encapsulates a group of form objects that can be added or removed to the form together multiple times - */ - -RepeatingModelGroup = (function(superClass) { - extend(RepeatingModelGroup, superClass); + }; - function RepeatingModelGroup() { - return RepeatingModelGroup.__super__.constructor.apply(this, arguments); - } + ModelGroup.prototype.modelClassName = 'ModelGroup'; - RepeatingModelGroup.prototype.modelClassName = 'RepeatingModelGroup'; + return ModelGroup; - RepeatingModelGroup.prototype.initialize = function() { - this.setDefault('defaultValue', this.get('value') || []); - this.set('value', []); - return RepeatingModelGroup.__super__.initialize.apply(this, arguments); - }; +}).call(this); - RepeatingModelGroup.prototype.postBuild = function() { - var c, i, len, ref; - ref = this.children; - for (i = 0, len = ref.length; i < len; i++) { - c = ref[i]; - c.postBuild(); +RepeatingModelGroup = (function() { + class RepeatingModelGroup extends ModelGroup { + initialize() { + this.setDefault('defaultValue', this.get('value') || []); + this.set('value', []); + return super.initialize({ + objectMode: true + }); } - return this.clear(); - }; - RepeatingModelGroup.prototype.setDirty = function(id, whatChanged) { - var i, len, ref, val; - ref = this.value; - for (i = 0, len = ref.length; i < len; i++) { - val = ref[i]; - val.setDirty(id, whatChanged); + postBuild() { + var c, i, len, ref; + ref = this.children; + for (i = 0, len = ref.length; i < len; i++) { + c = ref[i]; + c.postBuild(); + } + return this.clear(); // Apply the defaultValue for the repeating model group after it has been built } - return RepeatingModelGroup.__super__.setDirty.call(this, id, whatChanged); - }; - RepeatingModelGroup.prototype.setClean = function(all) { - var i, len, ref, results, val; - RepeatingModelGroup.__super__.setClean.apply(this, arguments); - if (all) { + setDirty(id, whatChanged) { + var i, len, ref, val; ref = this.value; - results = []; for (i = 0, len = ref.length; i < len; i++) { val = ref[i]; - results.push(val.setClean(all)); + val.setDirty(id, whatChanged); + } + return super.setDirty(id, whatChanged); + } + + setClean(all) { + var i, len, ref, results, val; + super.setClean({ + objectMode: true + }); + if (all) { + ref = this.value; + results = []; + for (i = 0, len = ref.length; i < len; i++) { + val = ref[i]; + results.push(val.setClean(all)); + } + return results; } - return results; } - }; - RepeatingModelGroup.prototype.recalculateRelativeProperties = function() { - return RepeatingModelGroup.__super__.recalculateRelativeProperties.call(this, this.value); - }; - - RepeatingModelGroup.prototype.buildOutputData = function(_, skipBeforeOutput) { - var tempOut; - tempOut = this.value.map(function(instance) { - return RepeatingModelGroup.__super__.buildOutputData.call(this, instance); - }); - if (skipBeforeOutput) { - return tempOut; - } else { - return this.beforeOutput(tempOut); + recalculateRelativeProperties() { + //ignore validity/visibility of children, only value instances + return super.recalculateRelativeProperties(this.value); } - }; - RepeatingModelGroup.prototype.clear = function(purgeDefaults) { - if (purgeDefaults == null) { - purgeDefaults = false; - } - this.value = []; - if (!purgeDefaults) { - if (this.defaultValue) { - return this.addEachSimpleObject(this.defaultValue); + buildOutputData(_, skipBeforeOutput) { + var tempOut; + tempOut = this.value.map(function(instance) { + return super.buildOutputData(instance); //build output data of each value as a group, not repeating group + }); + if (skipBeforeOutput) { + return tempOut; + } else { + return this.beforeOutput(tempOut); } } - }; - RepeatingModelGroup.prototype.applyData = function(inData, clear, purgeDefaults) { - var finalInData; - if (clear == null) { - clear = false; - } - if (purgeDefaults == null) { - purgeDefaults = false; - } - finalInData = this.beforeInput(jiff.clone(inData)); - if (finalInData) { + clear(purgeDefaults = false) { this.value = []; - } else { - if (clear) { - this.clear(purgeDefaults); + if (!purgeDefaults) { + if (this.defaultValue) { + // we do NOT want to run beforeInput when resetting to the default, so just convert each to a ModelGroup + return this.addEachSimpleObject(this.defaultValue); + } } } - return this.addEachSimpleObject(finalInData, clear, purgeDefaults); - }; - RepeatingModelGroup.prototype.addEachSimpleObject = function(o, clear, purgeDefaults) { - var added, i, key, len, obj, results, value; - if (clear == null) { - clear = false; + // applyData performs and clearing and transformations, then adds each simple object and a value ModelGroup + applyData(inData, clear = false, purgeDefaults = false) { + var finalInData; + finalInData = this.beforeInput(jiff.clone(inData)); + // always clear out and replace the model value when data is supplied + if (finalInData) { + this.value = []; + } else { + if (clear) { + this.clear(purgeDefaults); + } + } + return this.addEachSimpleObject(finalInData, clear, purgeDefaults); } - if (purgeDefaults == null) { - purgeDefaults = false; + + addEachSimpleObject(o, clear = false, purgeDefaults = false) { + var added, i, key, len, obj, results, value; +//each value in the repeating group needs to be a repeating group object, not just the anonymous object in data +//add a new repeating group to value for each in data, and apply data like with a model group + results = []; + for (i = 0, len = o.length; i < len; i++) { + obj = o[i]; + added = this.add(); + results.push((function() { + var ref, results1; + results1 = []; + for (key in obj) { + value = obj[key]; + results1.push((ref = added.child(key)) != null ? ref.applyData(value, clear, purgeDefaults) : void 0); + } + return results1; + })()); + } + return results; } - results = []; - for (i = 0, len = o.length; i < len; i++) { - obj = o[i]; - added = this.add(); - results.push((function() { - var ref, results1; - results1 = []; - for (key in obj) { - value = obj[key]; - results1.push((ref = added.child(key)) != null ? ref.applyData(value, clear, purgeDefaults) : void 0); - } - return results1; - })()); + + cloneModel(root, constructor) { + var clone, excludeAttributes; + // When cloning to a ModelGroup exclude items not intended for subordinate clones + excludeAttributes = (constructor != null ? constructor.name : void 0) === 'ModelGroup' ? ['value', 'beforeInput', 'beforeOutput', 'description'] : []; + clone = super.cloneModel(root, constructor, excludeAttributes); + // need name but not title. Can't exclude in above clone because default to each other. + clone.title = ''; + return clone; } - return results; - }; - RepeatingModelGroup.prototype.cloneModel = function(root, constructor) { - var clone, excludeAttributes; - excludeAttributes = (constructor != null ? constructor.name : void 0) === 'ModelGroup' ? ['value', 'beforeInput', 'beforeOutput', 'description'] : []; - clone = RepeatingModelGroup.__super__.cloneModel.call(this, root, constructor, excludeAttributes); - clone.title = ''; - return clone; - }; + add() { + var clone; + clone = this.cloneModel(this.root, ModelGroup); + this.value.push(clone); + this.trigger('change'); + return clone; + } - RepeatingModelGroup.prototype.add = function() { - var clone; - clone = this.cloneModel(this.root, ModelGroup); - this.value.push(clone); - this.trigger('change'); - return clone; - }; + delete(index) { + this.value.splice(index, 1); + return this.trigger('change'); + } - RepeatingModelGroup.prototype["delete"] = function(index) { - this.value.splice(index, 1); - return this.trigger('change'); }; + RepeatingModelGroup.prototype.modelClassName = 'RepeatingModelGroup'; + return RepeatingModelGroup; -})(ModelGroup); +}).call(this); diff --git a/lib/modelOption.js b/lib/modelOption.js index 17b689b..f1f0e9f 100644 --- a/lib/modelOption.js +++ b/lib/modelOption.js @@ -1,31 +1,29 @@ -var ModelBase, ModelOption, - extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, - hasProp = {}.hasOwnProperty; +var ModelBase, ModelOption; ModelBase = require('./modelBase'); -module.exports = ModelOption = (function(superClass) { - extend(ModelOption, superClass); - - function ModelOption() { - return ModelOption.__super__.constructor.apply(this, arguments); - } - - ModelOption.prototype.initialize = function() { +module.exports = ModelOption = class ModelOption extends ModelBase { + initialize() { this.setDefault('value', this.get('title')); + // No two options on a field should have the same title. This would be confusing during render. + // Even if not rendered, title can be used as primary key to determine when duplicate options should be avoided. this.setDefault('title', this.get('value')); + // selected is used to set default value and also to store current value. this.setDefault('selected', false); - this.setDefault('path', []); - ModelOption.__super__.initialize.apply(this, arguments); + // set default bid adjustment + this.setDefault('path', []); //for tree. Might should move to subclass + super.initialize({ + objectMode: true + }); + // if selected is changed, make sure parent matches + // this change likely comes from parent value changing, so be careful not to infinitely recurse. return this.on('change:selected', function() { if (this.selected) { - return this.parent.addOptionValue(this.value, this.bidAdj); + return this.parent.addOptionValue(this.value, this.bidAdj); // not selected } else { return this.parent.removeOptionValue(this.value); } }); - }; - - return ModelOption; + } -})(ModelBase); +}; diff --git a/package.json b/package.json index b4d0d70..ad60d22 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "balihoo-form-builder-model", - "version": "2.2.1", + "version": "2.2.3", "description": "Standalone code for building form builder models", "author": "Balihoo", "main": "./formbuilder.js", diff --git a/src/modelField.coffee b/src/modelField.coffee index 6fc6fef..d93ccfb 100644 --- a/src/modelField.coffee +++ b/src/modelField.coffee @@ -30,10 +30,10 @@ module.exports = class ModelField extends ModelBase @setDefault 'beforeOutput', (val) -> val super - + objectMode: true #difficult to catch bad types at render time. error here instead - if @type not in ['info', 'text', 'url', 'email', 'tel', 'time', 'date', 'textarea', - 'bool', 'tree', 'color', 'select', 'multiselect', 'image', 'button', 'number'] + if @type not in ['info', 'text', 'url', 'email', 'tel', 'time', 'date', 'textarea','bool', 'tree', + 'color', 'select', 'multiselect','image', 'button', 'number'] return globals.handleError "Bad field type: #{@type}" @bindPropFunctions 'dynamicValue' @@ -190,13 +190,14 @@ module.exports = class ModelField extends ModelBase setClean: (all) -> super + objectMode: true if all opt.setClean all for opt in @options recalculateRelativeProperties: -> dirty = @dirty super - + objectMode: true # validity # only fire if isValid changes. If isValid stays false but message changes, don't need to re-fire. if @shouldCallTriggerFunctionFor dirty, 'isValid' diff --git a/src/modelFieldDate.coffee b/src/modelFieldDate.coffee index 280d6f0..d08d952 100644 --- a/src/modelFieldDate.coffee +++ b/src/modelFieldDate.coffee @@ -5,6 +5,7 @@ module.exports = class ModelFieldDate extends ModelField initialize: -> @setDefault 'format', 'M/D/YYYY' super + objectMode: true @validator @validate.date # Convert date to string according to this format diff --git a/src/modelFieldImage.coffee b/src/modelFieldImage.coffee index 23693a6..95290a5 100644 --- a/src/modelFieldImage.coffee +++ b/src/modelFieldImage.coffee @@ -15,7 +15,7 @@ module.exports = class ModelFieldImage extends ModelField @set 'optionsChanged', false #React needs to know if the number of options changed, # as this requires a full reinit of the plugin at render time that is not necessary for other changes. super - + objectMode: true # Override behaviors different from other fields. option: (optionParams...) -> diff --git a/src/modelFieldTree.coffee b/src/modelFieldTree.coffee index 2e33f15..adb554f 100644 --- a/src/modelFieldTree.coffee +++ b/src/modelFieldTree.coffee @@ -4,7 +4,7 @@ module.exports = class ModelFieldTree extends ModelField initialize: -> @setDefault 'value', [] super - + objectMode: true option: (optionParams...) -> optionObject = @buildParamObject optionParams, ['path', 'value', 'selected'] optionObject.value ?= optionObject.id diff --git a/src/modelGroup.coffee b/src/modelGroup.coffee index ab2d609..d7b4b05 100644 --- a/src/modelGroup.coffee +++ b/src/modelGroup.coffee @@ -20,7 +20,7 @@ module.exports = class ModelGroup extends ModelBase @setDefault 'beforeOutput', (val) -> val super - + objectMode: true postBuild: -> child.postBuild() for child in @children @@ -87,12 +87,14 @@ module.exports = class ModelGroup extends ModelBase setClean: (all) -> super + objectMode: true if all child.setClean all for child in @children recalculateRelativeProperties: (collection = @children) -> dirty = @dirty super + objectMode: true #group is valid if all children are valid #might not need to check validy, but always need to recalculate all children anyway. newValid = true @@ -150,7 +152,7 @@ class RepeatingModelGroup extends ModelGroup @set 'value', [] super - + objectMode: true postBuild: -> c.postBuild() for c in @children @clear() # Apply the defaultValue for the repeating model group after it has been built @@ -161,6 +163,7 @@ class RepeatingModelGroup extends ModelGroup setClean: (all) -> super + objectMode: true if all val.setClean all for val in @value diff --git a/src/modelOption.coffee b/src/modelOption.coffee index 4aeba65..2e6afc4 100644 --- a/src/modelOption.coffee +++ b/src/modelOption.coffee @@ -11,7 +11,7 @@ module.exports = class ModelOption extends ModelBase # set default bid adjustment @setDefault 'path', [] #for tree. Might should move to subclass super - + objectMode: true # if selected is changed, make sure parent matches # this change likely comes from parent value changing, so be careful not to infinitely recurse. @on 'change:selected', -> From 4fac4091994b6615c23bced73f71ec3dace69ec1 Mon Sep 17 00:00:00 2001 From: jkareliya Date: Thu, 23 Sep 2021 12:47:22 +0530 Subject: [PATCH 04/13] HUB-3750 upgrade package version --- lib/building.js | 2 +- package.json | 4 ++-- src/building.coffee | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/building.js b/lib/building.js index fefacde..16b773b 100644 --- a/lib/building.js +++ b/lib/building.js @@ -1,6 +1,6 @@ var CoffeeScript, ModelGroup, Mustache, _, globals, jiff, throttledAlert, vm; -CoffeeScript = require('coffee-script'); +CoffeeScript = require('coffeescript').register(); Mustache = require('mustache'); diff --git a/package.json b/package.json index ad60d22..02a11b0 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,6 @@ "main": "./formbuilder.js", "dependencies": { "backbone": "1.4.0", - "coffee-script": "1.12.7", "jiff": "0.7.3", "moment": "^2.14.1", "mustache": "4.2.0", @@ -14,6 +13,7 @@ }, "devDependencies": { "coffeelint": "2.1.0", + "coffeescript": "^2.6.0", "gulp": "4.0.2", "gulp-coffee": "3.0.3", "gulp-coffeelint": "0.6.0", @@ -22,7 +22,7 @@ "istanbul": "0.4.5", "mocha": "9.1.1", "sinon": "11.1.2", - "yargs": "17.1.1" + "yargs": "^17.2.0" }, "repository": { "type": "git", diff --git a/src/building.coffee b/src/building.coffee index a438953..6b15f8b 100644 --- a/src/building.coffee +++ b/src/building.coffee @@ -1,6 +1,6 @@ -CoffeeScript = require 'coffee-script' +CoffeeScript = require('coffeescript').register() Mustache = require 'mustache' _ = require 'underscore' vm = require 'vm' From 16ed5a9f4a4f5b13cbca3a81d142a955651e3344 Mon Sep 17 00:00:00 2001 From: jkareliya Date: Mon, 11 Oct 2021 18:27:26 +0530 Subject: [PATCH 05/13] HUB-3750 update in package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 02a11b0..3cf9235 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "main": "./formbuilder.js", "dependencies": { "backbone": "1.4.0", + "coffeescript": "^2.6.0", "jiff": "0.7.3", "moment": "^2.14.1", "mustache": "4.2.0", @@ -13,7 +14,6 @@ }, "devDependencies": { "coffeelint": "2.1.0", - "coffeescript": "^2.6.0", "gulp": "4.0.2", "gulp-coffee": "3.0.3", "gulp-coffeelint": "0.6.0", From 78977c4afa1bdbaeccf19967794b38f9e8158d49 Mon Sep 17 00:00:00 2001 From: jkareliya Date: Mon, 11 Oct 2021 18:44:40 +0530 Subject: [PATCH 06/13] HUB-3750 Changed coffeescript version from 2.6.0 to 1.9.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3cf9235..c721a86 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "main": "./formbuilder.js", "dependencies": { "backbone": "1.4.0", - "coffeescript": "^2.6.0", + "coffeescript": "1.9.3", "jiff": "0.7.3", "moment": "^2.14.1", "mustache": "4.2.0", From c0672abc06ac1e4e7487670d3c0cb601ac2808ad Mon Sep 17 00:00:00 2001 From: jkareliya Date: Tue, 12 Oct 2021 11:16:22 +0530 Subject: [PATCH 07/13] HUB-3750 added logs in modelGroup.coffee --- src/modelGroup.coffee | 1 + 1 file changed, 1 insertion(+) diff --git a/src/modelGroup.coffee b/src/modelGroup.coffee index d7b4b05..fd0b0be 100644 --- a/src/modelGroup.coffee +++ b/src/modelGroup.coffee @@ -105,6 +105,7 @@ module.exports = class ModelGroup extends ModelBase buildOutputData: (group = @, skipBeforeOutput) -> obj = {} + console.log "buildOutputData======" group.children.forEach (child) -> childData = child.buildOutputData(undefined, skipBeforeOutput) unless childData is undefined # undefined values do not appear in output, but nulls do From dc59beaa2024f6cc8af9d0a05292b53993a0aaea Mon Sep 17 00:00:00 2001 From: jkareliya Date: Tue, 12 Oct 2021 12:24:08 +0530 Subject: [PATCH 08/13] HUB-3750 added logs in modelGroup.coffee --- src/modelGroup.coffee | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/modelGroup.coffee b/src/modelGroup.coffee index fd0b0be..e96733e 100644 --- a/src/modelGroup.coffee +++ b/src/modelGroup.coffee @@ -174,7 +174,8 @@ class RepeatingModelGroup extends ModelGroup buildOutputData: (_, skipBeforeOutput) -> tempOut = @value.map (instance) -> - super instance #build output data of each value as a group, not repeating group + ModelGroup.prototype.buildOutputData.apply instance + #super.buildOutputData instance #build output data of each value as a group, not repeating group if skipBeforeOutput then tempOut else @beforeOutput tempOut From 43137f7904c75e5dae63aeb4c13ba42b45190791 Mon Sep 17 00:00:00 2001 From: jkareliya Date: Tue, 12 Oct 2021 12:36:14 +0530 Subject: [PATCH 09/13] HUB-3750 change coffeescript version from 1.9.3 to 2.6.0 --- lib/modelGroup.js | 4 +++- package.json | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/modelGroup.js b/lib/modelGroup.js index 37a5f13..acce5dc 100644 --- a/lib/modelGroup.js +++ b/lib/modelGroup.js @@ -178,6 +178,7 @@ module.exports = ModelGroup = (function() { buildOutputData(group = this, skipBeforeOutput) { var obj; obj = {}; + console.log("buildOutputData======"); group.children.forEach(function(child) { var childData; childData = child.buildOutputData(void 0, skipBeforeOutput); @@ -304,8 +305,9 @@ RepeatingModelGroup = (function() { buildOutputData(_, skipBeforeOutput) { var tempOut; tempOut = this.value.map(function(instance) { - return super.buildOutputData(instance); //build output data of each value as a group, not repeating group + return ModelGroup.prototype.buildOutputData.apply(instance); }); + //super.buildOutputData instance #build output data of each value as a group, not repeating group if (skipBeforeOutput) { return tempOut; } else { diff --git a/package.json b/package.json index c721a86..cb3eb03 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "main": "./formbuilder.js", "dependencies": { "backbone": "1.4.0", - "coffeescript": "1.9.3", + "coffeescript": "2.6.0", "jiff": "0.7.3", "moment": "^2.14.1", "mustache": "4.2.0", From 16c81c6784606abc25f119dcdb4d8902f6546518 Mon Sep 17 00:00:00 2001 From: jkareliya Date: Mon, 18 Oct 2021 11:32:26 +0530 Subject: [PATCH 10/13] HUB-3750 coffeescript version change in package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index cb3eb03..d8891b4 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "main": "./formbuilder.js", "dependencies": { "backbone": "1.4.0", - "coffeescript": "2.6.0", + "coffeescript": "2.5.1", "jiff": "0.7.3", "moment": "^2.14.1", "mustache": "4.2.0", From 78ab11093ded5b1b2e745082f583c8e7dd8db8d1 Mon Sep 17 00:00:00 2001 From: jkareliya Date: Mon, 18 Oct 2021 15:53:34 +0530 Subject: [PATCH 11/13] HUB-3750 downgrade coffeescript version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d8891b4..fe75bea 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "main": "./formbuilder.js", "dependencies": { "backbone": "1.4.0", - "coffeescript": "2.5.1", + "coffeescript": "2.4.1", "jiff": "0.7.3", "moment": "^2.14.1", "mustache": "4.2.0", From 59eb3a36f97dfbebbd0fba53ebfe2ef5b93ed4c5 Mon Sep 17 00:00:00 2001 From: jkareliya Date: Mon, 18 Oct 2021 16:25:43 +0530 Subject: [PATCH 12/13] HUB-3750 version changes in package.json --- package.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index fe75bea..cb9cd9b 100644 --- a/package.json +++ b/package.json @@ -6,10 +6,10 @@ "main": "./formbuilder.js", "dependencies": { "backbone": "1.4.0", - "coffeescript": "2.4.1", + "coffeescript": "2.5.1", "jiff": "0.7.3", "moment": "^2.14.1", - "mustache": "4.2.0", + "mustache": "4.1.0", "underscore": "1.13.1" }, "devDependencies": { @@ -18,11 +18,11 @@ "gulp-coffee": "3.0.3", "gulp-coffeelint": "0.6.0", "gulp-istanbul": "1.1.3", - "gulp-mocha": "8.0.0", + "gulp-mocha": "7.0.2", "istanbul": "0.4.5", - "mocha": "9.1.1", - "sinon": "11.1.2", - "yargs": "^17.2.0" + "mocha": "8.2.1", + "sinon": "1.17.7", + "yargs": "16.1.0" }, "repository": { "type": "git", From c7bb8940415d5eb49f09051eb7e7b856da565e72 Mon Sep 17 00:00:00 2001 From: jkareliya Date: Tue, 19 Oct 2021 12:07:16 +0530 Subject: [PATCH 13/13] HUB-3750 Downgraded coffee script version due to conflict in form-builder --- lib/building.js | 2 +- src/building.coffee | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/building.js b/lib/building.js index 16b773b..456b71a 100644 --- a/lib/building.js +++ b/lib/building.js @@ -1,6 +1,6 @@ var CoffeeScript, ModelGroup, Mustache, _, globals, jiff, throttledAlert, vm; -CoffeeScript = require('coffeescript').register(); +CoffeeScript = require('coffee-script').register(); Mustache = require('mustache'); diff --git a/src/building.coffee b/src/building.coffee index 6b15f8b..c30be3c 100644 --- a/src/building.coffee +++ b/src/building.coffee @@ -1,6 +1,6 @@ -CoffeeScript = require('coffeescript').register() +CoffeeScript = require('coffee-script').register() Mustache = require 'mustache' _ = require 'underscore' vm = require 'vm'