menu

SiteGenesis / Server-side JS / Source: app_storefront_controllers/cartridge/scripts/models/AbstractModel.js

'use strict';

/**
 * Model for prototype model functionality.
 * @module models/AbstractModel
 */

/* API Includes */
var Class = require('~/cartridge/scripts/util/Class').Class;
var Logger = require('dw/system/Logger');

var AbstractModel = Class.extend({
    /**
     * Property holding the wrapped object that is initialized in the child classes. Usually,
     * the static get() method of the app.js module is used to obtain a model instance.
     */
    object: null,

    /**
     * Abstract class for all modules implementing the {@tutorial Models} concept. Models typlcally wrap
     * {@link dw.object.PersistenObject} instances or more general instances of Salesforce Commerce Cloud API classes.
     *
     * @extends module:util/Class~Class
     * @constructs module:models/AbstractModel~AbstractModel
     * @param obj {Object}
     * @see https://bitbucket.org/demandware/sitegenesis-community/wiki/Home
     */
    init: function (obj) {
        if (!obj) {
            throw new Error('Wrapped object may not be null.');
        }

        this.object = obj;
        // Optionally, intializes properties. Be careful of the potential performance impact,
        // which is why it is preferable to do this only for subclasses that really need it
        //this.initProperties();
        return this;
    },

    /**
     * Returns a wrapped object instance. This method needs to be implemented by the subclasses.
     *
     * @abstract
     * @alias module:models/AbstractModel~AbstractModel/get
     * @return {Void}
     */
    get: function () {
        Logger.warn('Generic helper access method "get()" not implemented for subclass');
        return new AbstractModel({custom: {}});
    },

    /**
     * Gets value from prepopulated object.
     * If the key is point-delimited, parses JSON
     * If not, obtains a value from "custom" property of an object
     * @alias module:models/AbstractModel~AbstractModel/getValue
     * @param {String} key The JSON key to retrieve a value for.
     * @return {Object}
     */
    getValue: function (key) {
        if (empty(key)) {
            return null;
        }

        // Adds any special value handling here, such as automatic handling of JSON.
        var value = this.object.custom[key];

        return value;
    },

    /**
     * Sets value to prepopulated object.
     * If the key is point-delimited, parses JSON and sets up a target value.
     * @alias module:models/AbstractModel~AbstractModel/setValue.
     * @return {Boolean} true if value is successfully set.
     */
    setValue: function (key, value) {
        // this will works under transactional nodes
        if (!this.object || empty(key)) {
            return false;
        }

        try {
            this.object.custom[key] = value;
        } catch (e) {
            return false;
        }
    },

    /**
     * Creates property access and delegate it to the appropriate getters & setters of the wrapper or wrapped object
     * @alias module:models/AbstractModel~AbstractModel/initProperties
     */
    initProperties: function () {
        var instance = this;
        // properties.forEach(function(property) {
        //     instance.__defineGetter__(
        //         property,
        //         function(){
        //           return instance.object[property];
        //         }
        //     );
        // });
        var duration = new Date().getTime();
        var properties = [];
        for (var property in instance.object) {
            properties.push(property);
        }
        properties.forEach(function (property) {
            var propertyName;
            if (property.indexOf('get') === 0) {
                // remove get and lowercase first character, i.e. getOnline -> online
                propertyName = property.substring(3,4).toLowerCase() + property.substring(4);
                // only define if there is a corresponding property as well
                if (properties.indexOf(propertyName) > -1) {
                    //Logger.debug('Defining property get access for {0}',propertyName);
                    instance.__defineGetter__(
                        propertyName,
                        (property in instance) ? function () {return instance[property]();} : function () {return instance.object[propertyName];}
                    );
                }
            }
            // handle setters
            if (property.indexOf('set') === 0) {
                // remove get and lowercase first character, i.e. getOnline -> online
                propertyName = property.substring(3,4).toLowerCase() + property.substring(4);
                // only define if there is a corresponding property as well
                if (properties.indexOf(propertyName) > -1) {
                    //Logger.debug('Defining property set access for {0}',propertyName);
                    instance.__defineSetter__(
                        propertyName,
                        (property in instance) ? function (v) {return instance[property](v);} : function (v) {return instance.object[property](v);}
                    );
                }
            }
        });
        duration = new Date().getTime() - duration;
        Logger.info('{0}ms to define property access',duration);
    },

    /**
     * Fallback to use wrapped object's native functions in case method is not defined.
     * The logic will try to invoke method for this.object, and throw TypeError if the method does not exist
     *
     * @param {String} methodName The name of a method to use as a fallback.
     * @param {Array} methodArgs The arguments for the method.
     *
     * @alias module:models/AbstractModel~AbstractModel/__noSuchMethod__
     * @return Record Result or exception if the method does not exist.
     * @throws {TypeError}
     */
    __noSuchMethod__: function (methodName, methodArgs) {
        if (methodName in this.object && 'function' === typeof this.object[methodName]) {
            return this.object[methodName].apply(this.object, methodArgs);
        }
        // If the method cannot be found.
        Logger.error('Method "{0}" does not exist for {1}',methodName,this.object.class);
        throw new TypeError();
    }
});

/** The AbstractModel class */
module.exports = AbstractModel;

X Privacy Update: We use cookies to make interactions with our websites and services easy and meaningful, to better understand how they are used. By continuing to use this site you are giving us your consent to do this. Privacy Policy.