models/Field.js

/**
 * @module app/models/Field
 */
define(
    [
        'knockout',
        'common'
    ],
    function(ko, lib) {
        /**
         * This will map selected properties into an observable or an observableArray, and the rest will be copied or ignored
         * @constructor
         * @alias module:app/models/Field
         * @param {Object} fieldObj The valid field settings object
         * 
         * @example <caption>Example usage for Field</caption>
         * //use it before adding the field to the array of fields
         * var field = new Field(field_settings);
         * //then add it to the observableArray that is responsible for showing the fields
         * //for example you have fields_array variable
         * var fields_array = ko.observableArray([]);
         * fields_array(field);
         * 
         * @see module:templates/index
         *
         * @requires knockout
         * @requires common
         */
        function Field(fieldObj) {

            var prop;

            var UUID = lib.createUuid();
            /**
             * The properties that will be ignored, or they're just here if the value needs only to be copied
             * @type {Array}
             */
            var ignoredProperties = [
                'id',
                'key',
                'childFields',
                'name',
                'parent',
                'parents',
                'display_order',
                'created_at',
                'updated_at',
                'deleted_at',
                'created_by',
                'updated_by',
                'deleted_by'
            ];
            /**
             * The property to be casted to the specified type
             * @type {Object}
             */
            var casts = {
                options: []
            };

            var arrProp = null;


            for (prop in fieldObj) {
                if (fieldObj.hasOwnProperty(prop) && !(ko.isObservable(fieldObj[prop]))) {

                    /**
                     * The field type
                     * @type {Number}
                     */
                    this.key = fieldObj.key;

                    /**
                     * The name of the field generated using common#createUuid
                     * @see common#createUuid
                     * @type {String}
                     */
                    this.name = fieldObj.name || UUID;

                    /**
                     * The direct parent of this field, will hold it's parent name
                     * @type {String}
                     */
                    this.parent = fieldObj.parent;

                    /**
                     * A flag used to determine if the field controls(edit, delete, drag) will be displayed
                     * @type {Boolean}
                     */
                    this.showControls = typeof fieldObj.showControls === 'undefined' ? true : fieldObj.showControls;

                    /**
                     * If the module:js/models/Field#showNumbering is true, this will contain the numbering order of the field
                     * @see module:js/models/Field#showNumbering
                     * @type {Number}
                     */
                    this.numbering = ko.observable(false);

                    /**
                     * A flag for the numbering visibility
                     * @type {Boolean}
                     */
                    this.showNumbering = false;
                    /**
                     * A flag to mark the field as a repeatable, used on the report editing where the user can add multiple copies of the field
                     * @type {Boolean}
                     */
                    this.repeatable = ko.observable(fieldObj.repeatable || false);

                    /**
                     * The child field(s) of this field
                     * @type {Array}
                     */
                    this.childFields = null;


                    if (!ko.isObservable(fieldObj.childFields)) {
                        this.childFields = ko.observableArray([]);
                    }


                    if (fieldObj.id) {
                        this.id = fieldObj.id;
                    }

                    // ------------------------------------------------------
                    // Check if the current prop is not an ignored property
                    // ------------------------------------------------------
                    if (ignoredProperties.indexOf(prop) === -1) {
                        if (casts[prop] && casts[prop] instanceof Array) {

                            arrProp = fieldObj[prop];

                            try {
                                arrProp = JSON.parse(arrProp)
                            } catch (err) {
                                // console.error('Field model: ', err);
                            }

                            if (arrProp instanceof Array && arrProp.length) {
                                // converts this property into an observableArray
                                this[prop] = ko.observableArray(arrProp.map(function(item) {
                                    // and the child properties will be converted to an observable
                                    for (var prop in item) {
                                        if (!(ko.isObservable(item[prop]))) {
                                            item[prop] = ko.observable(item[prop]);
                                        }
                                    }
                                    return item;
                                }));

                            } else {
                                this[prop] = ko.observableArray([]);

                            }
                        } else {
                            this[prop] = ko.observable(fieldObj[prop]);

                        }
                    } //END: if
                }
            } // END: for in


            // -----------------------
            // Subscriptions: START
            // -----------------------
            if (this.showNumbering && ko.isObservable(this.showNumbering)) {
                this.showNumbering.subscribe(function(value) {
                    ko.postbox.publish('numberingToggled', {
                        field: this,
                        parent: null
                    });

                    if (!value && this.repeatable()) {
                        this.repeatable(false);
                    }
                }, this);
            }

            if (this.max_chars && ko.isObservable(this.max_chars)) {
                this.max_chars.subscribe(function(len) {
                    if (len) {
                        this.value(this.value().substring(0, len));
                    }
                }, this);
            }

            if (this.fg_changed && ko.isObservable(this.fg_changed)) {
                this.fg_changed.subscribe(function(changed) {
                    this.foreground(changed ? this.fg_class() : '');
                }, this);
            }

            if (this.bg_changed && ko.isObservable(this.bg_changed)) {
                this.bg_changed.subscribe(function(changed) {
                    this.background(changed ? this.fg_class() : '');
                }, this);
            }
            // -----------------------
            // Subscriptions: END
            // -----------------------
        }

        return Field;
    });