/*
* Your installation or use of this SugarCRM file is subject to the applicable
* terms available at
* http://support.sugarcrm.com/Resources/Master_Subscription_Agreements/.
* If you do not agree to all of the applicable terms or do not have the
* authority to bind the entity as an authorized representative, then do not
* install or use this SugarCRM file.
*
* Copyright (C) SugarCRM Inc. All rights reserved.
*/
const Events = require('./events');
const Context = require('./context');
const ViewManager = require('../view/view-manager');
/**
* The controller manages the loading and unloading of layouts.
*
* ## Extending controller
*
* Applications may choose to extend the controller to provide a custom
* implementation. Your custom controller class name should be the capitalized
* {@link Config#appId|appID} for your application followed by the word
* "Controller".
*
* Example:
* ```
* SUGAR.App.PortalController = SUGAR.App.controller.extend({
* loadView: function(params) {
* // Custom implementation of loadView
* // Should call super method:
* SUGAR.App.Controller.prototype.loadView.call(this, params);
* }
* });
* ```
*
* @module Core/Controller
*/
/**
* @alias module:Core/Controller
*/
module.exports = Backbone.View.extend({
/**
* Instantiates the application's main context and binds global events.
*
* @memberOf module:Core/Controller
* @instance
*/
initialize: function() {
/**
* The primary context of the app. This context is associated with the
* root layout.
*
* @type {Core/Context}
* @memberOf module:Core/Controller
* @alias context
* @instance
*/
this.context = new Context();
Events.on('app:sync:complete', function() {
SUGAR.App.api.setStateProperty('loadingAfterSync', true);
_.each(SUGAR.App.additionalComponents, function(component) {
if (component && _.isFunction(component._setLabels)) {
component._setLabels();
}
component.render();
});
SUGAR.App.router.reset();
SUGAR.App.api.clearStateProperty('loadingAfterSync');
});
Events.on('app:login:success', function() {
SUGAR.App.sync();
});
},
/**
* Loads a layout.
*
* Sets the context with the given params, creates the layout based on
* metadata, loads the data and renders it. It also disposes the previous
* displayed layout. This method is called by the router when the route is
* changed.
*
* @memberOf module:Core/Controller
* @param {Object} params Properties to set to the context and used to
* determine the layout to load.
* @param {string} params.layout The layout name. It will be used to grab
* the corresponding metadata.
* @param {string} params.module The module the layout belongs to.
* @param {boolean} [params.skipFetch=false] If `true`, do not fetch the
* data.
* @instance
*/
loadView: function(params) {
var oldLayout = this.layout;
//FIXME SC-5124 will trigger 'app:view:load', and add a deprecation warning for 'app:view:change'.
if (!SUGAR.App.triggerBefore('app:view:change') || !SUGAR.App.triggerBefore('app:view:load')) {
return;
}
// Reset context and initialize it with new params
this.context.clear({silent: true});
this.context.set(params);
// Prepare model and collection
this.context.prepare();
// Create an instance of the layout and bind it to the data instance
this.layout = ViewManager.createLayout({
name: params.layout,
module: params.module,
context: this.context
});
if (oldLayout && oldLayout.el) {
// Take out the previous layout element from the content container,
// and then keep it in the document fragment
// in order to destroy jQuery plugin safe.
var oldLayoutEl = document.createDocumentFragment();
oldLayoutEl.appendChild(oldLayout.el);
}
// Render the layout to the main element
// Since the previous element is already gone,
// .append is better way because .html requires
// additional cost for .empty().
SUGAR.App.$contentEl.append(this.layout.$el);
//initialize subcomponents in the layout
this.layout.initComponents();
// Fetch the data, the layout will be rendered when fetch completes
if (!params || (params && !params.skipFetch)) {
this.layout.loadData();
}
// Render the layout with empty data
this.layout.render();
if (oldLayout) {
oldLayout.dispose();
}
SUGAR.App.trigger('app:view:change', params.layout, params);
},
/**
* Creates, renders, and registers within the app additional components.
*
* @memberOf module:Core/Controller
* @param {Object} components The components to load. They will
* be created using metadata view definitions and rendered on the page.
* The components objects are cached in the the global `SUGAR.App` variable
* under the `additionalComponents` property.
* @instance
*/
loadAdditionalComponents: function(components) {
if (!_.isEmpty(SUGAR.App.additionalComponents)) {
SUGAR.App.logger.error('`Controller.loadAdditionalComponents` has already been called. ' +
'It can not be called twice.');
return;
}
SUGAR.App.additionalComponents = {};
_.each(components, function(component, name) {
if (component.target) {
var $el = this.$(component.target);
if (!$el.get(0)) {
SUGAR.App.logger.error('Unable to place additional component "' + name + '": the target specified ' +
'does not exist.');
return;
}
if (component.layout) {
SUGAR.App.additionalComponents[name] = ViewManager.createLayout({
context: this.context,
type: component.layout,
el: $el,
});
SUGAR.App.additionalComponents[name].initComponents();
} else {
SUGAR.App.additionalComponents[name] = ViewManager.createView({
type: component.view || name,
context: this.context,
el: $el,
});
}
SUGAR.App.additionalComponents[name].render();
} else {
SUGAR.App.logger.error('Unable to place additional component "' + name + '": no target specified.');
}
}, this);
}
});