Source: src/main/javascript/oracle/oj/ojmodule/ModuleBinding.js

Oracle® JavaScript Extension Toolkit (JET)
1.1.2

E65298-01

/*
** Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved.
**
**34567890123456789012345678901234567890123456789012345678901234567890123456789
*/


/**
 * ojModule Conventions
 * @namespace
 */
oj.ModuleBinding = {};

/**
 * Default configuration values
 * ojModule binding's conventions may be overridden for the entire application after the ojs/ojmodule
 * module is loaded. For example:
 * <p><code class="prettyprint">
 * oj.ModuleBinding.defaults.modelPath = 'models/';
 * </code></p>
 * Note that the default names of the {@link oj.ModuleBinding.ConventionMethods optional lifecycle methods} 
 * on the ViewModel are different from their counterparts on the {@link ojModule.LifecycleListener LifecycleListener} 
 * interface
 * @property {string} viewPath default View path. Defaults to 'text!views/'
 * @property {string} viewSuffix default View suffix. Defaults to '.html'
 * @property {string} modelPath default Model suffix. Defaults to 'viewModels/'
 * @property {string} initializeMethod name of the initialialization method
 * (see {@link oj.ModuleBinding.ConventionMethods#initialize definition})
 * @property {string} disposeMethod name of the dispose method 
 * (see {@link oj.ModuleBinding.ConventionMethods#dispose definition})
 * @property {string} activatedHandler name of the 'activated' event handler
 * (see {@link oj.ModuleBinding.ConventionMethods#handleActivated definition})
 * @property {string} attachedHandler name of the 'attached' event handler 
 * (see {@link oj.ModuleBinding.ConventionMethods#handleAttached definition})
 * @property {string} detachedHandler name of the 'detached' event handler
 * (see {@link oj.ModuleBinding.ConventionMethods#handleDetached definition})
 * @property {string} bindingsAppliedHandler name of the 'bindingsApplied' event handler
 * (see {@link oj.ModuleBinding.ConventionMethods#handleBindingsApplied definition})
 * @property {string} deactivatedHandler name of the 'deactivated' event handler
 * (see {@link oj.ModuleBinding.ConventionMethods#handleDeactivated definition})
 * @export
 * @memberof oj.ModuleBinding
 */
oj.ModuleBinding.defaults = 
{
  'viewPath': 'text!views/',
  'viewSuffix': '.html',
  'modelPath': 'viewModels/',
  'initializeMethod': 'initialize',
  'disposeMethod': 'dispose',
  'activatedHandler': 'handleActivated',
  'attachedHandler': 'handleAttached',
  'detachedHandler': 'handleDetached',
  'bindingsAppliedHandler': 'handleBindingsApplied',
  'deactivatedHandler': 'handleDeactivated'
};


(function ()
{
  ko['bindingHandlers']['ojModule'] = 
  {
    'init' : function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext)
    {
      var currentViewModel;
      var cache = {};
      var currentCacheKey;
      var pendingViewId = -1;
      var cacheHolder;


      var invokeModelDispose = function(model)
      {
        _invokeViewModelMethod(model, 'disposeMethod', [element, valueAccessor]);
      };
      
      var disposeAssociatedComponentViewModel = function ()
      {
        invokeModelDispose(currentViewModel);
      };
      
      var nodeDispose = function()
      {
        disposeAssociatedComponentViewModel();
        
        // call dispose() on cached models
        var keys = Object.keys(cache);
        for (var i=0; i<keys.length; i++)
        {
          var model = cache[keys[i]].model;
          invokeModelDispose(model);
        }
        
        // Knockout will call ko.cleanNode on all child nodes including the cacheHolder 
      };

      ko.utils.domNodeDisposal.addDisposeCallback(element, nodeDispose);
      
      // This function is used to interrupt Promise chains when the new view/viewModel combination is being loaded
      var checkPeningId = function(id)
      {
        if (id != pendingViewId)
        {
          throw new Error("Promise cancelled because ojModule is fetching new View and ViewModel");
        }
      };
      
      var initCacheHolder = function()
      {
        if (!cacheHolder)
        {
          cacheHolder = document.createElement('div');
          cacheHolder.className = "oj-helper-module-cache";
          element.appendChild(cacheHolder);
        }
      };

      ko.computed(function ()
      {
        pendingViewId++;
        var value = ko.utils.unwrapObservable(valueAccessor());

        var moduleName;
        var viewName;
        var params;
        var modelFactory;
        var viewFunction;
        var cacheKey;
        var lifecycleListener;        

        if (typeof value === 'string')
        {
          moduleName = value;
        }
        else 
        {
          moduleName = ko.utils.unwrapObservable(value['name']);
          viewName = ko.utils.unwrapObservable(value['viewName']);

          params = ko.utils.unwrapObservable(value['params']);
          
          modelFactory = ko.utils.unwrapObservable(value['viewModelFactory']);
          viewFunction = ko.utils.unwrapObservable(value['createViewFunction']);
          cacheKey = ko.utils.unwrapObservable(value['cacheKey']);
          lifecycleListener = ko.utils.unwrapObservable(value['lifecycleListener']);
        }
        
        var attachPromise = _invokeLifecycleListener(lifecycleListener, 'activated', [element, valueAccessor]);
        
        var viewPromise;
        var modelPromise;
        
        
        var cached = cacheKey == null ? null : cache[cacheKey];
        
        if (cached != null)
        {
          delete cache[cacheKey];
          viewPromise = Promise.resolve(cached.view);
          modelPromise = Promise.resolve(cached.model);
        }
        else
        {
          if (modelFactory != null)
          {
            modelPromise = ko.ignoreDependencies(modelFactory['createViewModel'], modelFactory, [params, valueAccessor]); 
          }
          
          if (modelPromise == null && moduleName != null)
          {
            modelPromise = _getRequirePromise(oj.ModuleBinding.defaults['modelPath'] + moduleName);
          }
          
          if (modelPromise != null)
          {
            // Wrap model promise to perform initialization
            modelPromise = modelPromise.then(
              function(id, viewModel)
              {
                checkPeningId(id);
                
                if (typeof viewModel === 'function')
                {
                  viewModel = new viewModel(params);
                }
                else 
                {
                  // If the function returns a value, use it as the new model instance
                  viewModel = _invokeViewModelMethod(viewModel, 'initializeMethod', [element, valueAccessor]) || viewModel;
                }
                
                return viewModel;
              }.bind(null, pendingViewId)
            );
            
            // Handle the case where the Model is responsible for creating a View
            if (viewFunction != null)
            {
              viewPromise = modelPromise.then(
                function(id, model)
                {
                  checkPeningId(id);
                  
                  var viewMethod = (model == null) ? null : model[viewFunction];
                  return (viewMethod == null ? null : viewMethod());
                }.bind(null, pendingViewId)
              ); 
            }  

          }
          if (viewPromise == null)
          {
            viewName = viewName == null ? moduleName : viewName;
            if (viewName != null)
            {
              viewPromise = _getRequirePromise(oj.ModuleBinding.defaults['viewPath'] + viewName + 
                                                                      oj.ModuleBinding.defaults['viewSuffix']);
            }
            else
            {
              throw new Error('View name must be specified');
            }
          }
          
        }
        
        if (viewPromise == null)
        {
          throw new Error('ojModule is missing a View');
        }
        
        var modelAttachPromise;
        
        if (modelPromise != null)
        {
          modelAttachPromise = modelPromise.then(
              function(id, viewModel)
              {
                checkPeningId(id);
                return _invokeViewModelMethod(viewModel, 'activatedHandler', [element, valueAccessor]);
                
              }.bind(null, pendingViewId)
          );
          
        }
       
        var masterPromise = Promise.all([viewPromise, modelPromise, attachPromise, modelAttachPromise]);
        
        masterPromise.then(
          function(id, values)
          {
            if (id != pendingViewId)
            {
              return;
            }
            
            var nodes = _getDomNodes(values[0]);
            var model = values[1];
            
            var saveInCache = false;
            var cachedNodeArray;
            
            
            if (currentCacheKey != null)
            {
              saveInCache = true;
              cachedNodeArray = [];
              initCacheHolder();
            }
            
            _removeAndPossiblyCacheChildren(element, cachedNodeArray, cacheHolder, !saveInCache);           
            
            if (pendingViewId > 0) // ensure that this is not the very first view displayed by the binding
            {
              _invokeLifecycleListener(lifecycleListener, 'detached', [element, valueAccessor, currentViewModel, cachedNodeArray]);
              _invokeViewModelMethod(currentViewModel, 'detachedHandler', [element, valueAccessor, cachedNodeArray]);
              
              _invokeLifecycleListener(lifecycleListener, 'deactivated', [element, valueAccessor, currentViewModel]);
              _invokeViewModelMethod(currentViewModel, 'deactivatedHandler', [element, valueAccessor]);
            }
            
            if (saveInCache)
            {
              _propagateSubtreeVisibilityToComponents(cachedNodeArray, false);
              cache[currentCacheKey] = {model: currentViewModel, view: cachedNodeArray};
            }
            else
            {
              disposeAssociatedComponentViewModel();
            }
  
            currentViewModel = model;
            currentCacheKey = cacheKey;
            
            // Insert new child nodes
            for (var i=0, j=nodes.length; i<j; i++)
            {
              if (!cacheHolder)
              {
                element.appendChild(nodes[i]);
              }
              else
              {
                element.insertBefore(nodes[i], cacheHolder);
              }
            }
            
            var fromCache = cached != null;
            
            if (fromCache)
            {
              _propagateSubtreeVisibilityToComponents(nodes, true);
            }
            
            _invokeLifecycleListener(lifecycleListener, 'attached', [element, valueAccessor, currentViewModel, fromCache]);
            _invokeViewModelMethod(currentViewModel, 'attachedHandler', [element, valueAccessor, fromCache]);
  
            if (!fromCache)
            {
              var childBindingContext = bindingContext['createChildContext'](model,
              undefined, function (ctx)
              {
                ctx['$module'] = model;
              });
    
              // Bug 21070137: instead of calling applyBindingsToDescendants(element), we have to activate
              // bindings only on the nodes we just inserted. This will avoid applying bindings to the hidden cached
              // views
              for (var n=0, l=nodes.length; n<l; n++)
              {
                var node = nodes[n];
                var type = node.nodeType;
                if (type == 1/*element*/ || type == 8/*comment*/)
                {
                  ko.applyBindings(childBindingContext, node);
                }
              }
              
              _invokeLifecycleListener(lifecycleListener, 'bindingsApplied', [element, valueAccessor, model]);
              _invokeViewModelMethod(model, 'bindingsAppliedHandler', [element, valueAccessor]);
            }
             
          }.bind(null, pendingViewId),
          
          /* reject callback */
          function(id, reason)
          {
            if (id != pendingViewId)
            {
              return;
            }
            
            oj.Logger.error(reason);
            
          }.bind(null, pendingViewId)
        
        );
        
      },
      null, 
      {disposeWhenNodeIsRemoved : element}
      );

      return {'controlsDescendantBindings' : true};
    }
  };

  /**
   * @ignore
   */
  function _isDocumentFragment(content) 
  {
    if (window['DocumentFragment'])
    {
      return content instanceof DocumentFragment;
    } 
    else 
    {
      return content && content.nodeType === 11;
    }
  };
  
  /**
   * @ignore
   */
  function _invokeLifecycleListener(listener, method, params)
  {
    if (listener && listener[method])
    {
      var data = {'element': params[0], 'valueAccessor': params[1]};
      if (params.length > 2)
      {
        data['viewModel'] = params[2];
        if (params.length > 3)
        {
          data[typeof params[3] === 'boolean' ? 'fromCache': 'cachedNodes'] = params[3];
        }
      }
      // bug 21172738: suspend dependency detection while listeners are invoked
      var ret = ko.ignoreDependencies(listener[method], listener, [data]); 
  
      return ret;
    }
  };
  
  /**
   * @ignore
   * @param {?Object} model 
   * @param {string} key 
   * @param {Array=} params
   * @return {Object|undefined}
   */
  function _invokeViewModelMethod(model, key, params)
  {
    if (model == null)
    {
      return;
    }
    var name = oj.ModuleBinding.defaults[key];
    if (name != null && model)
    {
      var handler = model[name];
      if (typeof handler === 'function')
      {
        
        var data = undefined;
        if (params)
        {
          data = {'element': params[0], 'valueAccessor': params[1]};
          if (params.length > 2)
          {
            data[typeof params[2] === 'boolean' ? 'fromCache': 'cachedNodes'] = params[2];
          }
        }
        // bug 21172738: suspend dependency detection while listeners are invoked
        var ret = ko.ignoreDependencies(handler, model, [data]);
        return ret;
      }
    }
  };
  
  /**
   * @ignore
   */
  function _removeAndPossiblyCacheChildren(node, cacheArray, cacheHolder, needsClean)
  {
    // 'Virtual' (comment) elements are not currently supported for this binding
    var child = node.firstChild;
    while (child)
    {
      var next = child.nextSibling;
      // Skip the special 'cache holder' element
      if (child !== cacheHolder)
      {       
        if (needsClean)
        {
          ko.cleanNode(child);
        }
             
        if (cacheArray) // nodes are being cached
        {
          cacheArray.push(child);
          cacheHolder.appendChild(child);
        }
        else
        {
          node.removeChild(child);
        }
      }
   
      child = next;
    }
  }
  
  
  
  /**
   * @ignore
   */
  function _propagateSubtreeVisibilityToComponents(nodeArray, visible)
  {
    if (oj.Components != null)
    {
      for (var i = 0; i<nodeArray.length; i++)
      {
        if (visible)
        {
          oj.Components.subtreeShown(nodeArray[i]);
        }
        else
        {
          oj.Components.subtreeHidden(nodeArray[i]);
        }
      }
    }
  }
  
  /**
   * @ignore 
   */
  function _getDomNodes(content)
  {
    if (typeof content === 'string')
    {
      content = ko.utils.parseHtmlFragment(content);
    }
    else if (_isDocumentFragment(content))
    {
      content = ko.utils.arrayPushAll([], content.childNodes);
    }
    else
    {
      content = ko.utils.arrayPushAll([], content);
    }
    return content;
  }
  
  /**
   * @ignore
   */
  function _getRequirePromise(module)
  {
    return new Promise(
      function(resolve, reject)
      {
        require([module],
          function(loaded)
          {
            resolve(loaded);
          },
          function()
          {
            throw new Error("ojModule failed to load " + module);
          }
        
        );
      }
    );
  };
  

})();

/**
 * A duck-typing interface that defines optional ViewModel methods that the ojModule binding will invoke by convention
 * @interface ConventionMethods
 * @memberof oj.ModuleBinding
 */

/**
 * This method may be implemented on the ViewModel to perform initialization tasks. The method will be invoked only
 * if the ViewModel factory or the AMD module returns a ViewModel instance (as opposed to a constructor function).
 * Note that this method will be invoked only once when the ViewModel is created, i.e. it will not be called when the
 * View is being brought from cache
 * 
 * @method initialize
 * @instance
 * @param {Object} info  - an object with the following key-value pairs:
 * <ul>
 * <li>'element' - DOM element where the binding is attached</li>
 * <li>'valueAccessor' - binding's value accessor</li>
 * </ul>
 * To retrieve the object passed as the 'params' option on the binding, use  
 * <code>ko.utils.unwrapObservable(info.valueAccessor()).params</code> if the entire binding value is an observable or
 * <code>ko.utils.unwrapObservable(info.valueAccessor().params)</code> if the bidning value is a plain object literal. 
 * @return {Object|undefined} - optional return value that will be used as the new ViewModel instance
 * @memberof oj.ModuleBinding.ConventionMethods
 */

/**
 * This method may be implemented by the ViewModel to perform cleanup tasks. Note that this method will be invoked only 
 * once before all binding's refernces to the ViewModel are released.
 * @method dispose
 * @instance
 * @param {Object} info  - an object with the following key-value pairs:
 * <ul>
 * <li>'element' - DOM element where the binding is attached</li>
 * <li>'valueAccessor' - binding's value accessor</li>
 * </ul>
 * @memberof oj.ModuleBinding.ConventionMethods
 */
 
/**
  * Optional ViewModel method invoked when this ViewModel is about to be used for the View transition
  * @method handleActivated
  * @instance
  * @param {Object} info  - an object with the following key-value pairs:
  * <ul>
  * <li>'element' - DOM element where the binding is attached</li>
  * <li>'valueAccessor' - binding's value accessor</li>
  * </ul>
  * @return {Promise|undefined} - If the callback returns a Promise, the next phase (attaching DOM) will be delayed until
  * the promise is resolved
  * @memberof oj.ModuleBinding.ConventionMethods
  */
 
 /**
  * Optional ViewModel method invoked after the View is inserted into the document DOM
  * @method handleAttached
  * @instance
  * @param {Object} info  - an object with the following key-value pairs:
  * <ul>
  * <li>'element' - DOM element where the binding is attached</li>
  * <li>'valueAccessor' - binding's value accessor</li>
  * <li>'fromCache ' - a boolean indicating whether the module was retrieved from cache</li>
  * </ul>
  * @memberof oj.ModuleBinding.ConventionMethods
  */
 
 /**
  * Optional ViewModel method invoked after the bidnings are applied on this View. 
  * If the current View is retrieved from cache, the bindings will not be re-applied, 
  * and this callback will not be invoked.
  * @method handleBindingsApplied
  * @instance
  * @param {Object} info  - an object with the following key-value pairs:
  * <ul>
  * <li>'element' - DOM element where the binding is attached</li>
  * <li>'valueAccessor' - binding's value accessor</li>
  * </ul>
  * @memberof oj.ModuleBinding.ConventionMethods
  */

 /**
  * Optional ViewModel method invoked after the View is removed from the document DOM
  * @method handleDetached
  * @instance
  * @param {Object} info  - an object with the following key-value pairs:
  * <ul>
  * <li>'element' - DOM element where the binding is attached</li>
  * <li>'valueAccessor' - binding's value accessor</li>
  * <li>'cachedNodes' - an Array containing cached nodes for the View (if the cache is enabled)</li>
  * </ul>
  *  @memberof oj.ModuleBinding.ConventionMethods
  */

 /**
  * Optional ViewModel method invoked when the View/ViewModel combination becomes inactive
  * @method handleDeactivated
  * @instance
  * @param {Object} info  - an object with the following key-value pairs:
  * <ul>
  * <li>'element' - DOM element where the binding is attached</li>
  * <li>'valueAccessor' - binding's value accessor</li>
  * </ul>
  * @memberof oj.ModuleBinding.ConventionMethods
  */
  
  /**
   * <p>ojModule binding for Knockout.js manages content replacement within a particular region of the page. The binding 
   * can be used to implement navigation within a region of a single-page application.</p>
   * 
   * <p>
   * <h2>Terminology</h2>
   * <ul>
   * <li>View - the HTML markup to be displayed within the page fragment. The markup can contain Knockout bindings, which 
   * will be automatically activated after the View is attached to its container.</li>
   * <li>ViewModel - optional ViewModel that will be applied by Knockout to the View</li>
   * <li>Module - a combination of View and ViewModel</li>
   * </ul>
   * </p>
   * 
   * <p>
   * <h2>Supported Features</h2>
   * <ul>
   * <li>ViewModel and View definitions loaded using require.js</li>
   * <li>Ability to provide a ViewModel by supplying a factory instance</li>
   * <li>View Caching</li>
   * <li>Ability for a ViewModel to create a View</li>
   * <li>Notifications about the lifecycle events (Module activation and deactivation, attaching and detaching DOM, 
   * applying bindings)</li>
   * </ul>
   * </p>
   * 
   * <p>
   * <h2>Examples</h2>
   * <pre class="prettyprint">
   * <!-- Simple module binding with the currentCenter observable containing the name of the ViewModel, 
   *      which is also used as the name of the View -->
   * <div data-bind="ojModule: currentCenter"></div>
   *
   * <!-- Module binding with a LifecycleListener -->
   * <div data-bind="ojModule: {name: currentCenter, lifecycleListener: moduleLifecycle}"></div>
   *
   * <!-- Module binding with caching -->
   * <div data-bind="ojModule: {name: currentName, cacheKey: currentName}"></div>
   * 
   * <!-- View-only module binding -->
   * <div data-bind="ojModule: {viewName: currentName}"></div>
   * </pre>
   * </p>
   * 
   * <p>
   * <h2>Conventions</h2>
   * <ul>
   * <li>ViewModel modules will have .js extension, and Views will have .html extension</li>
   * <li>ViewModel modules are expected to be found under the 'viewModels/' folder, and Views are expected to be found 
   * under the 'views/' folder. Both viewModels and /views folders are expected to be in the 'base' RequireJs folder 
   * (usually js/)</li>
   * <li>Optional 'initialize' and 'dispose' functions will be invoked on the ViewModel after it is created or before 
   * it is destroyed respectively. See {@link oj.ModuleBinding.ConventionMethods ConventionMethods} for method definitions</li>
   * <li>Optional lifecycle event handlers can be implemented by the ViewModel as well. 
   * See {@link oj.ModuleBinding.ConventionMethods  ConventionMethods} for method definitions</li>.
   * </ul>
   * For information to modifying the convention defaults see {@link oj.ModuleBinding}
   * </p>
   * 
   * <p>
   * <h2>ViewModel's Lifecycle</h2>
   * The instance of the ViewModel is established as follows:
   * <ol>
   * <li>If the viewModelFactory is provided, the 'starter' object is determined by resolving a Promise returned 
   * by the factory's createViewModel() method. Otherwise, the 'starter' object will be assigned the return value 
   * of the AMD module specified by the 'name' option on the binding (if present)</li>
   * <li>If the 'starter' object is a function, it will be treated as a constructor that will be invoked to create the 
   * ViewModel instance, and the 'params' object specified by the binding's 'params' option will be passed into the constructor 
   * function as a parameter. Otherwise, the {@link oj.ModuleBinding.ConventionMethods#initialize initialize convention method}
   * will be invoked on the 'starter' instance. If the method does not return anything, the 'starter' object will be treated
   * as the ViewModel instance. Otherwise, the return value will become the ViewModel instance.</li>
   * </ol>
   * ViewModel's {@link oj.ModuleBinding.ConventionMethods#dispose dispose convention method} will be invoked before binding's
   * references to the ViewModel instance are released.
   * The following lifecycle listeners are supported on a ViewModel instance as convention methods (listed in the order
   * they will be fired):
   * <ul>
   * <li>{@link oj.ModuleBinding.ConventionMethods#handleActivated handleActivated}</li>
   * <li>{@link oj.ModuleBinding.ConventionMethods#handleAttached handleAttached}</li>
   * <li>{@link oj.ModuleBinding.ConventionMethods#handleBindingsApplied handleBindingsApplied}</li>
   * <li>{@link oj.ModuleBinding.ConventionMethods#handleDetached handleDetached}</li>
   * <li>{@link oj.ModuleBinding.ConventionMethods#handleDeactivated handleDeactivated}</li>
   * </ul>
   * </p>
   * 
   * <p>
   * <h2>Acessing ViewModel Data</h2>
   * The instance of the ViewModel will be applied to the View, i.e. it will serve as View's default data object. The same
   * instance will also be available as $module (this may be useful for accessing module's ViewModel in foreach iterators).
   * For the View-only modules, the default data object will be null. Parent ViewModel will be available as $parent. 
   * </p>
   * 
   * <p>
   * <h2>Avoiding Mutiple re-renders</h2>
   * This binding keeps track of changes to each of its options. If an observable holding an option value is mutated,
   * the view will be re-rendered. One of the following approaches may be used to avoid unnecessary re-renders:
   * <ul>
   * <li>Use a single observable to store the entire binding value with all attributes:
   * <p>
   * <pre class="prettyprint">
   * this.moduleValue = ko.observable({name: 'acc_details', cacheKey:'acc'});
   * ...
   * <div data-bind="ojModule: moduleValue"></div>
   * </pre>
   * </p>
   * </li>
   * <li>Use the same observable for the name and cacheKey attributes:
   * <p>
   * <pre class="prettyprint">
   * this.moduleName = ko.observable('acc_details');
   * ...
   * <div data-bind="ojModule: {name: moduleName, cacheKey: moduleName}}"></div> 
   * </pre>
   * </p>
   * </li>
   * <li>Use rate-limiting for the observables referenced by the binding:
   * <p>
   * <pre class="prettyprint">
   * this.moduleName = ko.observable('acc_details').extend({ rateLimit: 1 });
   * this.cKey = ko.observable('acc').extend({ rateLimit: 1 });
   * this.vName = ko.observable('accView').extend({ rateLimit: 1 });
   * ...
   * <div data-bind="ojModule: {name: moduleName, cacheKey: cKey, viewName: vName}}"></div>
   * ...
   * // Mutating multiple observables independently is OK when they use a rate limit extender
   * this.moduleName('preferences');
   * this.cKey('prefs');
   * this.vName('prefsView);
   * </pre>
   * </p>
   * </li>
   * </ul>
   * </p>
   * 
   * <p>
   * <h2>Using ojModule with Require.js</h2>
   * Request <code>ojs/ojmodule</code> module before Knockout bindings are applied
   * </p>
   * 
   * @namespace
   * @name ojModule
   */
 
   /**
   * @name Options
   * @property {string} name ViewModel name. Required if viewModelFactory parameter is not specified. The name will be
   * used to load an AMD module containing the definition of the ViewModel. If the module returns a function, it will be used
   * as a ViewModel constructor, and any other return value (including null) will be treated as a ViewModel instance.
   * If the entire binding's value is a string, it will be treated as a 'name' option.
   * If you need to create a view-only module, use the <code>viewName</code> option
   * @property {string} viewName View name. If omitted, the name of the View is assumed to be the same as the name of the 
   * VewModel.
   * @property {Object} viewModelFactory instance of the factory that implements <code>createViewModel(params, valueAccessor)</code> 
   * method. The method has to a return a Promise that will resolve to the ViewModel instance or constructor.
   * @property {Object} params object that will be passed into the ViewModel constructor or the
   * createViewModel method of the View Model factory as a parameter.
   * @property {string} createViewFunction name of the ViewModel function used to create a View. If this parameter is 
   * specified, the ViewModel will be responsible for providing the definition of the View. The function has to return 
   * a Promise that will be resolved to document fragment, an array of DOM nodes or a string containing the HTML
   * @property {string} cacheKey The key used to cache the View after it is no longer displayed. Setting this parameter will 
   * enable View caching. When the View is about t be cached, its Knockout bindings will not be deactivated when 
   * the View is removed from DOM tree. The cache will be discarded after when the window object is destroyed or 
   * when the ojModule binding instance is deactivated. Since changes to any binding option will re-render the view, 
   * it is very important that cacheKey and the rest of the options are applied atomically. Failure to do so will result in
   * a wrong cached view being retrieved. See the 'Avoiding Multiple Re-renders' section above.
   * @property {Object} lifecycleListener An instance of the object implementing one or more methods defined by the
   * {@link ojModule.LifecycleListener LifecycleListener} duck-typing interface
   * 
   * @instance
   * @memberof ojModule
   */
   
   /**
   * A duck-typing interface that defines methods for the ojModule binding's lifecycle listener. 
   * Use 'lifecycleListener' option on the ojModule binding to set the listener.
   * @interface
   * @name LifecycleListener 
   * @memberof ojModule
   */
  
   /**
    * Invoked when the binding starts loading a new View and ViewModel
    * @function activated
    * @param {Object} info  - an object with the following key-value pairs:
    * <ul>
    * <li>'element' - DOM element where the binding is attached</li>
    * <li>'valueAccessor' - binding's value accessor</li>
    * </ul>
    * @return {Promise|undefined} - If the callback returns a Promise, the next phase (attaching DOM) will be delayed until
    * the promise is resolved
    * @memberof ojModule.LifecycleListener
    * @instance
    */
    
  
   
   /**
    * Invoked after the View is inserted into the document DOM
    * @method attached
    * @param {Object} info  - an object with the following key-value pairs:
    * <ul>
    * <li>'element' - DOM element where the binding is attached</li>
    * <li>'valueAccessor' - binding's value accessor</li>
    * <li>'viewModel' - ViewModel for the View being attached</li>
    * <li>'fromCache' - a boolean indicating the view was retrieved from cache</li>
    * </ul>
    * @memberof ojModule.LifecycleListener
    * @instance
    */
  
   /**
    * Invoked after the bidnings are applied on this View. If the current View is retrieved from cache,
    * the bindings will not be re-applied, and this callback will not be invoked.
    * @method bindingsApplied
    * @param {Object} info  - an object with the following key-value pairs:
    * <ul>
    * <li>'element' - DOM element where the binding is attached</li>
    * <li>'valueAccessor' - binding's value accessor</li>
    * <li>'viewModel' - ViewModel for the View being attached</li>
    * </ul>
    * @memberof ojModule.LifecycleListener
    * @instance
    */
  
   /**
    * Invoked after the View is removed from the document DOM
    * @method detached
    * @param {Object} info  - an object with the following key-value pairs:
    * <ul>
    * <li>'element' - DOM element where the binding is attached</li>
    * <li>'valueAccessor' - binding's value accessor</li>
    * <li>'viewModel' - ViewModel for the View being detached</li>
    * <li>'cachedNodes' - an Array containing cached nodes for the View (if the cache is enabled)</li>
    * </ul>
    * @memberof ojModule.LifecycleListener
    * @instance
    */
 
   /**
    * Invoked when the View/ViewModel combination becomes inactive
    * @method deactivated
    * @param {Object} info  - an object with the following key-value pairs:
    * <ul>
    * <li>'element' - DOM element where the binding is attached</li>
    * <li>'valueAccessor' - binding's value accessor</li>
    * <li>'viewModel' - ViewModel being deactivated</li>
    * </ul>
    * @memberof ojModule.LifecycleListener
    * @instance
    */