Skip to content

Instantly share code, notes, and snippets.

@logical-and
Created December 11, 2014 14:37
Show Gist options
  • Save logical-and/521ccdd0958d031ba153 to your computer and use it in GitHub Desktop.
Save logical-and/521ccdd0958d031ba153 to your computer and use it in GitHub Desktop.

Revisions

  1. logical-and created this gist Dec 11, 2014.
    876 changes: 876 additions & 0 deletions AbstractWidget.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,876 @@
    define([
    'jsclass/class',
    './WidgetService',
    'app/common/layout/manager',
    'app/common/layout/model',
    'app/common/datatable/component.datatable',
    'Shared/Base/ConfigureGrid',
    'Shared/Filter/filter',
    'Shared/Filter/PageFilterSortInfo',
    'Shared/Base/LoadMask',
    'Shared/Extensions/ScopedWorkspace',
    'Shared/Util/Widget/GenericHelpers',
    './WidgetLoader',
    './Util',
    'nunjucks',
    'Shared/Extensions/WindowTitleForDate',
    'async'
    ], function (
    Class, WidgetService, Manager, Model, Datatable, ConfigureGrid, Filter, PageFilterSortInfo,
    LoadMask, ScopedWorkspace, GenericHelpers, WidgetLoader, Util, nunjucks, WindowTitleForDate,
    Async
    ) {
    'use strict';

    var DIR = 'Shared/WidgetFoundation/',
    $CacheMemory = {};

    return new Class(
    {
    extend: {
    EXPORT_TYPE: {
    DETACH: 'DETACH'
    },

    loader: function(settings) {
    return new WidgetLoader(this, settings);
    }
    },

    settings: {
    // default: { features: {} } - workspace and subworkspace, all tabs, main and detach
    //
    // workspace: { default: { default: { features: {} } } } - workspace, all tabs, main and detach
    // workspace: { default: { main: { features: {} } } } - workspace, all tabs, main
    // workspace: { reports: { default: { features: {} } } } - workspace, reports tab, main and detach
    // workspace: { reports: { main: { features: {} } } } - workspace, reports tab, main
    // workspace: { reports: { detach: { features: {} } } } - workspace, reports tab, detach
    //
    // subworkspace: { default: { features: {} } } - subworkspace, main and detach
    // subworkspace: { main: { features: {} } } - subworkspace, main
    // subworkspace: { detach: { features: {} } } - subworkspace, detach

    default: {
    title: 'Abstract Widget Title',
    features: {
    export: true,
    exportType: ['DETACH'],

    maximize: true,
    mininize: true,
    close: true,
    resize: true,
    drag: true
    },
    size: {
    width: 500,
    height: 400
    },

    listeners: {
    // format: eventName: function() {}, or eventName: [function() {}]
    // widgetInitialized: [],
    // widgetPassive: function() { console.info('just become passive'); }

    // available event types:
    // eventName - widget foundation event (see this.eventsMap)
    // widget:eventName (see this.frameworkEventsMap)
    // service:serviceName
    },

    // Methods, which will be passed to this[methodKey]
    methods: {},

    plugins: {
    // PluginPath: { parameters: 'here' }
    },

    internal: {
    workspaceChangeWorkaroundTimeout: 50,
    paths: {
    service: 'Service',
    view: ':text:' + DIR + 'View/AbstractWidget.html' // :text:, :require:, :service: or direct url (without html)
    },
    services: {
    getState: 'AUM/GetWidgetState',
    saveState: 'AUM/SetWidgetState',
    getStateNoId: 'User/GetWidgetStateWithoutId',
    saveStateNoId: 'User/SetWidgetStateWithoutId'
    }
    }
    }
    },

    // Fetched settings
    actualSettings: {},

    // State
    originalState: {},
    state: {},

    service: null,
    eventListeners: null,
    memory: null,
    plugins: null,

    // Widget
    initialized: false,
    id: null, // unique id
    widgetFrameworkPath: null, // framework internal, ie AssetsUnderManagement/AssetsUnderManagement:Assets Under Management
    widgetPath: null, // ie AssetsUnderManagement/AssetsUnderManagement
    widgetWorkspaceTab: null,
    widgetObject: null,
    widgetBodyContainerId: null,
    isWidgetDetached: null,
    widgetArgs: [],
    workspaceContext: null,
    spinner: null,

    // Mapping
    featuresMap: {},
    // WidgetFoundation events
    eventsMap: {
    settingsFetched: 'settingsFetched',
    widgetInitialized: 'widgetInitialized',
    viewInitialized: 'viewInitialized',
    stateLoaded: 'stateLoaded',
    statePersisting: 'statePersisting',
    exportDataToDetached: 'exportDataToDetached',
    detachedReceiveData: 'detachedReceiveData'
    },
    // Framework events
    frameworkEventsMap: {
    // Features
    onExportClicked: Model.WIDGET_EXPORT,
    onSettingsClicked: 'settings',
    onVisualizationClicked: Model.WIDGET_VISUALIZATION,
    onFilterClicked: 'filter',
    onRefreshClicked: 'refresh',

    onResized: Model.AFTER_RESIZE,
    onMaximized: Model.AFTER_MAXIMIZE,
    onCascaded: Model.AFTER_CASCADE,
    onDragged: Model.WIDGET_DRAG_COMPLETED,
    onClose: Model.WIDGET_CLOSE,

    // Detach
    onDetachedInitializing: Model.EXPORTED_VIEW_READY,
    onDetachedDataReceived: Model.EXPORTED_VIEW_DATA_EVENT,

    // Strange one-instance widget strategy events
    onWidgetPassivate: Model.VIEW_IN_PASSIVE_MODE,
    onWidgetActivate: Model.VIEW_IN_ACTIVE_MODE,

    onScopeChanged: ScopedWorkspace.SCOPE_CHANGE_EVENT,
    onUserPreferenceChanged: 'USER_PREFERENCES_SAVED'
    },
    // Work like localization for export items
    exportTypesMap: {
    DETACH: 'Detach'
    },
    workspacesIdMap: {
    "Home": 1,
    "Clients": 2,
    "Worklist": 3,
    "Markets": 4,
    "Resources": 5,
    "Trading": 6,
    "Services": 8,
    "Reports": 9,
    "Fixed Income": 10,
    "Back Office": 19,
    "Admin": 20,
    // "Home": 21,
    // "Home": 22,
    "Accounts": 23,
    "Documents": 24,
    // "Markets": 25,
    "Bill Pay": 26,
    "Credit Card Activity": 27
    // "Trading": 28,
    },

    initialize: function (settings, whom, isDetached, args, widgetModulePath) {
    var self = this;

    // Merge inherited values in the right way
    Util.forEach(['settings', 'featuresMap', 'eventsMap', 'exportTypesMap', 'frameworkEventsMap'], function(i, key) {
    var merged = {};

    Util.forEach(self.__eigen__().lookup(key), function(i, value) {
    merged = Util.Object.merge(merged, value);
    });

    self[key] = merged;
    });

    // Merge settings
    this.settings = Util.Object.merge(this.settings, settings);

    this.widgetFrameworkPath = widgetModulePath;
    this.widgetPath = widgetModulePath.split(':')[0];
    this.isWidgetDetached = !!isDetached;
    this.widgetArgs = args || [];

    (function (initSettings, nextStep) {
    // Define some settings manually
    if (!self.isWidgetDetached) {
    self.updateWorkspaceContext();
    setTimeout(function() {
    // don't work for clients tab
    // self.widgetWorkspaceTab = Manager.getWorkspaces()[Manager.getWorkspace()].name;
    self.widgetWorkspaceTab = $('#workspaceTabsHolder').find('.workspace.active').text();

    initSettings(nextStep);
    }, self.settings.default.internal.workspaceChangeWorkaroundTimeout);
    }
    // Receive settings from original widget
    else {
    var dummyWindow = new Model.WindowProperties(whom, self.widgetFrameworkPath);
    var dummyWidget = Manager.createWidget(dummyWindow);
    Manager.renderWidget(dummyWidget, function(event, postMessage) {
    if (self.frameworkEventsMap.onDetachedDataReceived == event) {
    if (postMessage.state) {
    self.originalState = Util.Object.clone(postMessage.state);
    self.state = Util.Object.clone(postMessage.state);
    }
    self.widgetWorkspaceTab = postMessage.widgetWorkspaceTab || 'Unknown';
    self.workspaceContext = postMessage.workspaceContext || {};

    (function(next) {
    if (!self.initialized) initSettings(next);
    else next();
    })(function() {
    nextStep(function() {
    self.fireEvent(self.eventsMap.detachedReceiveData, {
    postMessage: postMessage
    });

    if (postMessage.state) {
    self.fireEvent(self.eventsMap.stateLoaded, postMessage.state);
    }
    });
    });
    }
    }, '');

    // Prepare html to make it wonderful
    $('#' + dummyWidget.getId()).hide();
    $('body #centerpane').prepend($('<div id="loading" style="text-align: center;height: 100%;display: table;width: 100%;">' +
    '<span style="display: table-cell;vertical-align: middle;font-size: 150%;" class="color-white">' +
    'Exporting...' +
    '</span>' +
    '</div>')
    );

    Manager.subscribeToEvent(self.frameworkEventsMap.onDetachedDataReceived, self.widgetFrameworkPath);
    Manager.sendToParentWindow(self.widgetFrameworkPath, self.frameworkEventsMap.onDetachedInitializing);
    }
    })(function(callback) {
    // Fetch settings from given hash
    self.actualSettings = self._fetchActualSettings(self.settings);

    // Store methods to use it as native
    Util.forEach(self.actualSettings.methods, function(methodKey, method) {
    if (self[methodKey]) throw new Error('Cannot override "' + methodKey + '"! Method must have unique name!');
    self[methodKey] = method.bind(self);
    });

    self.fireEvent(self.eventsMap.settingsFetched);

    // Initialize plugins
    if (!Util.Object.isEmpty(self.actualSettings.plugins)) {
    self.plugins = [];
    Async.each(Util.Object.getKeys(self.actualSettings.plugins), function(plugin, cb) {
    require([plugin], function(Plugin) {
    self.plugins[plugin] = new Plugin(self, self.actualSettings.plugins[plugin], cb);
    });
    }, callback);
    }
    else callback();
    }, function (listenersInitialized) {

    // Go next
    var windowProperties = new Model.WindowProperties(whom, self.widgetFrameworkPath);

    // Title
    if (self.isWorkspace()) windowProperties.setTitle(self.actualSettings.title);
    else windowProperties.setTitle(self.actualSettings.title + ' for ' + self.workspaceContext.display);

    // Features
    var features = [];
    for (var feature in self.featuresMap) {
    if (self.featuresMap.hasOwnProperty(feature)) {
    if (self.actualSettings.features[feature]) features.push(self.featuresMap[feature]);
    }
    }
    if (features.length) windowProperties.showDefaultMenuOptions(features);
    windowProperties.showDetachOption(self.actualSettings.features.export);

    // If not detached, then we customize some window properties
    if (!self.isWidgetDetached) {
    windowProperties.isMaximizable(self.actualSettings.features.maximize);
    windowProperties.isMinimizable(self.actualSettings.features.minimize);
    windowProperties.isClosable(self.actualSettings.features.close);
    windowProperties.isResizable(self.actualSettings.features.resize);
    windowProperties.isDraggable(self.actualSettings.features.drag);
    }

    // Size
    if (!self.isWidgetDetached) {
    windowProperties.setWidth(self.actualSettings.size.width);
    windowProperties.setHeight(self.actualSettings.size.height);
    windowProperties.setMinSize(self.actualSettings.size.width, self.actualSettings.size.height);
    }
    else {
    var viewPortDims = viewport();
    windowProperties.setWidth(viewPortDims.width);
    windowProperties.setHeight(viewPortDims.height);
    }

    self.widgetObject = Manager.createWidget(windowProperties);

    // And here we go
    self.stackInitServices(function() {
    self.stackInitEventListeners(function() {
    listenersInitialized && listenersInitialized();

    self.stackInitView(function() {
    if (self.isWidgetDetached) self.getNode().hide();

    // Subworkspace title
    if (self.isSubworkspace()) {
    self.appendToTitle('for ' + self.workspaceContext.display);
    }

    self.showSpinner();
    if (self.isWidgetDetached) {
    $('#loading').remove();
    self.getNode().show();
    }

    self.stackInitState(function() {
    self.hideSpinner();
    self.initialized = true;

    self.fireEvent(self.eventsMap.widgetInitialized);
    });
    });
    });
    });
    });
    },
    /**
    * Fetch settings for current workspace/subworkspace from all settings (smart merge)
    *
    * @param allSettings
    * @returns {{}}
    * @private
    */
    _fetchActualSettings: function(allSettings) {
    var settings = Util.Object.clone(allSettings),
    paths = ['default'];

    if (this.isWorkspace()) {
    // Defaults
    paths.push('workspace.default.default');

    // With tab
    var currentTab = this.getCurrentTabName().toLowerCase();
    paths.push('workspace.' + currentTab + '.default');

    // With widget state (main or detach)
    paths.push('workspace.default.' + (!this.isDetached() ? 'main' : 'detach'));
    paths.push('workspace.' + currentTab + '.' + (!this.isDetached() ? 'main' : 'detach'));
    }
    else {
    // Defaults
    paths.push('subworkspace.default');

    // With widget state (main or detach)
    paths.push('subworkspace.' + (!this.isDetached() ? 'main' : 'detach'));
    }

    var fetchedSettings = {};
    for (var i = 0; i < paths.length; i++) {
    fetchedSettings = Util.Object.merge(fetchedSettings, Util.Object.getPath(settings, paths[i], {}));
    }

    return fetchedSettings;
    },

    // --- Stack
    /**
    * Initialize widget services
    * @private
    */
    stackInitServices: function(next) {
    var self = this;

    require([this._determineServicesPath()], function (Service) {
    self.service = (new Service).initializeServices(self.getWidgetPath());

    // Listen services as events
    Util.forEach(self.service.servicesConfig, function(serviceName) {
    self.service.listenService(serviceName, function(data) {
    if (!self.active) return;

    self.fireEvent('service:' + serviceName, data);
    });
    });

    next();
    });
    },
    /**
    * Subscribe to events
    * @private
    */
    stackInitEventListeners: function(next) {
    var self = this;

    Util.forEach(this.actualSettings.listeners, function(eventName) {
    var events = self.actualSettings.listeners[eventName];
    if ('function' == typeof events) events = [events];

    for (var i = 0; i < events.length; i++) {
    self.listenEvent(eventName, events[i]);
    }
    });

    next();
    },
    stackInitState: function(next) {
    if (!this.isWidgetDetached) this.loadState(next);
    else next();
    },

    // --- Generic
    getNodeId: function () {
    return this.widgetObject.getId();
    },
    getNode: function () {
    return $('#' + this.getNodeId());
    },
    getWidgetObject: function() {
    return this.widgetObject;
    },
    getNodeBodyContainerId: function() {
    return this.widgetBodyContainerId;
    },
    getNodeBodyContainer: function() {
    return $('#' + this.widgetBodyContainerId);
    },
    getWidgetFrameworkPath: function() {
    return this.widgetFrameworkPath;
    },
    getWidgetPath: function () {
    return this.widgetPath;
    },
    /**
    * For require.js usage
    * @param {Boolean} [trailingSlash=true]
    * @returns {String}
    */
    getWidgetDir: function(trailingSlash) {
    if (undefined === trailingSlash) trailingSlash = true;

    return this.widgetPath.replace(/\/[^/]+$/, '') + (trailingSlash ? '/' : '');
    },
    getSettings: function () {
    return this.actualSettings;
    },
    getAllSettings: function() {
    return this.settings;
    },
    isInitialized: function() {
    return this.initialized;
    },

    // --- Environment
    isDetached: function() {
    return this.isWidgetDetached;
    },
    getArgs: function() {
    return this.widgetArgs;
    },
    getWorkspaceContext: function() {
    return this.workspaceContext;
    },
    updateWorkspaceContext: function() {
    if (this.isWidgetDetached) throw new Error('Cannot update workspace context for detached widget');
    this.workspaceContext = ScopedWorkspace.getScopeOfCurrentWorkspace();

    return this;
    },
    isWorkspace: function() {
    return this.workspaceContext === ScopedWorkspace.SCOPE_UNDEFINED
    },
    isSubworkspace: function() {
    return !this.isWorkspace();
    },
    getCurrentTabName: function() {
    return this.widgetWorkspaceTab;
    },

    // --- Events
    listenEvent: function (event, handler) {
    if (!this.eventListeners) this.eventListeners = {};
    var self = this;

    var eventModificators = {
    once: function(event, handler) {
    var extraHandler = function() {
    // Remove event
    if (~self.eventListeners[event].indexOf(extraHandler)) {
    delete self.eventListeners[event][self.eventListeners[event].indexOf(extraHandler)];
    }

    // Call original handler
    handler.apply(this, arguments);
    };

    return extraHandler;
    }
    };

    var extraEventMatch = event.match(/:([^:]+)$/);
    if (extraEventMatch && eventModificators[extraEventMatch[1]]) {
    // if (!eventModificators[extraEventMatch[1]]) throw new Error('Modificator ' + extraEventMatch[1] + ' is unknown');

    // Remove modificator from event
    event = event.replace(/:([^:]+)$/, '');
    handler = eventModificators[extraEventMatch[1]](event, handler);
    }

    if (!this.eventListeners[event]) this.eventListeners[event] = [];
    this.eventListeners[event].push(handler);

    return this;
    },
    fireEvent: function(event, args) {
    if (!this.eventListeners) this.eventListeners = {};

    // Currently used for widget final instance handlers (settings.listeners)
    if (this.eventListeners[event]) {
    for (var i = 0; i < this.eventListeners[event].length; i++) {
    this.eventListeners[event][i].call(this, args);
    }
    }

    // Internal event methods
    var methodEventHandler = event.replace(/^[^:]+:/, '') + 'EventHandler';
    if (this[methodEventHandler] && 'function' == typeof this[methodEventHandler]) this[methodEventHandler].call(this, args);

    return this;
    },

    // --- Event handlers
    onCloseEventHandler: function() {
    this.saveStateIfChanged();
    },
    onExportClickedEventHandler: function() {
    var self = this;

    require(["Shared/Extensions/Export"], function (_export) {

    var items = [];
    Util.forEach(self.actualSettings.features.exportType, function(i, type) {
    if (self.exportTypesMap[type]) {
    items.push({
    name: self.exportTypesMap[type],
    callback: self.onExportingEventHandler.bind(self),
    id: Util.getUniqueId(),
    type: type
    });
    }
    });

    _export.createExportmenu(self.getNodeId(), self.getNodeBodyContainerId(), items);
    });
    },
    onExportingEventHandler: function(item) {
    var self = this;

    switch (item.type) {
    case 'DETACH':
    Manager.detachWindow(this.widgetFrameworkPath, function(event) {
    // Export general values
    if (self.frameworkEventsMap.onDetachedInitializing == event) {
    var postMessage = {
    state: self.state,
    widgetWorkspaceTab: self.widgetWorkspaceTab,
    workspaceContext: self.workspaceContext
    };
    self.fireEvent(self.eventsMap.exportDataToDetached, {
    postMessage: postMessage,
    sendMethod: this.sendMessage.bind(this)
    });
    this.sendMessage(postMessage);
    }

    // console.info('export layoutEventHandler: ', arguments, this);
    });
    break;

    default:
    console.error('Unknown export type: ' + item.type);
    }
    },
    onScopeChangedEventHandler: function() {
    this.updateWorkspaceContext().appendToTitle('for ' + this.workspaceContext.display);
    },

    // --- Services
    getService: function() {
    return this.service;
    },
    getServices: function() {
    return WidgetService;
    },
    callOwnService: function(service, parameters, callback) {
    this.service.call(this.service.getRealServiceName(service), parameters, callback);

    return this;
    },
    _determineServicesPath: function () {
    return this.getWidgetDir() + this.actualSettings.internal.paths.service;
    },

    // --- State
    getFromState: function (path, def) {
    return Util.Object.getPath(this.state, path, def);
    },
    setToState: function (path, value) {
    Util.Object.setPath(this.state, path, value);

    return this;
    },
    loadState: function (callback) {
    var requestCallback = function (data) {
    var state = data || {};
    this.state = state;
    this.originalState = Util.Object.clone(state);

    this.fireEvent(this.eventsMap.stateLoaded, state);
    callback(state);
    }.bind(this);

    if (this.actualSettings.stateId) {
    console.warn('stateId is deprecated, but used in "' + this.widgetPath + '" - ' + this.actualSettings.stateId);
    WidgetService.directRequest(this.actualSettings.internal.services.getState,
    { id: this.actualSettings.stateId }, 'GET', 'json', function(data) {
    requestCallback(data && data.Preference ? data.Preference : {});
    });
    }
    else {
    WidgetService.directRequest(this.actualSettings.internal.services.getStateNoId,
    {
    widgetPath: this.widgetFrameworkPath,
    workspaceId: Util.Object.getPath(this.workspacesIdMap, this.getCurrentTabName(), this.workspacesIdMap.Home),
    isSubworkspace: this.isSubworkspace()
    }, 'GET', 'json', function(data) {
    if (data && data.json) data = data.json;
    requestCallback(data);
    }, true);
    }

    return this;
    },
    isStateChanged: function () {
    // skip whitespace from comparison
    return JSON.stringify(this.originalState).replace(/\s+/g, '') != JSON.stringify(this.state).replace(/\s+/g, '');
    },
    saveState: function (callback) {
    var self = this;

    this.fireEvent(this.eventsMap.statePersisting, this.state);

    var requestCallback = function () {
    self.originalState = Util.Object.clone(self.state);

    if (callback) callback();
    }.bind(this);

    if (this.actualSettings.stateId) {
    console.warn('stateId is deprecated, but used in "' + this.widgetPath + '" - ' + this.actualSettings.stateId);
    WidgetService.directRequest(this.actualSettings.internal.services.saveState,
    { id: this.actualSettings.stateId, Preference: this.state }, 'POST', 'json', requestCallback);
    }
    else {
    WidgetService.directRequest(this.actualSettings.internal.services.saveStateNoId,
    {
    widgetPath: this.widgetFrameworkPath,
    workspaceId: Util.Object.getPath(this.workspacesIdMap, this.getCurrentTabName(), this.workspacesIdMap.Home),
    isSubworkspace: this.isSubworkspace(),
    json: this.state
    }, 'POST', 'json', function(data) {
    if (!data.Response) alert('Error: state wasn`t persisted! { Response: false }');
    requestCallback();
    });
    }

    return this;
    },
    saveStateIfChanged: function(callback) {
    if (!this.isStateChanged()) { if (callback) callback(); }
    else this.saveState(callback);

    return this;
    },

    // --- View
    loadView: function (callback) {
    this.loadHTML(this.actualSettings.internal.paths.view, callback);

    return this;
    },
    loadHTML: function(loadUrl, callback) {

    loadUrl = loadUrl.replace(/{WidgetFoundation}\/?/, DIR);

    // Service loading
    if (-1 != loadUrl.indexOf(':service:')) {
    WidgetService.call(loadUrl.replace(':service:'), callback);
    }

    // Require
    else if (-1 != loadUrl.indexOf(':require:')) {
    require([loadUrl.replace(':require:')], callback);
    }

    // Require-text
    else if (-1 != loadUrl.indexOf(':require-text:')) {
    require(['Lib/Vendor/RequireJS/text!' + loadUrl.replace(':require-text:', '')], callback);
    }

    // Text
    else if (-1 != loadUrl.indexOf(':text:')) {
    nunjucks.render(loadUrl.replace(':text:', '')

    // Relative to widget path
    .replace(new RegExp('^\./'), this.widgetPath.replace(/[^/]+$/, '') + '/'),

    function (err, html) {
    if (err) {
    console.error(err);
    }
    else setTimeout(function() { callback(html); }, 0);
    });
    }

    // URL request
    else {
    WidgetService.directRequest(loadUrl, {}, 'GET', 'html', function (template) {
    callback(template);
    }.bind(this));
    }

    return this;
    },

    // --- Spinner
    getSpinner: function() {
    if (!this.spinner) {
    if (this.widgetBodyContainerId) {
    this.spinner = new LoadMask.LoadMask(this.getNodeBodyContainer().parent().prop('id'));
    }
    else {
    console.warn('Cannot construct spinner for ' + this.widgetPath + '! Node id is unknown yet!');
    }
    }

    return this.spinner;
    },
    showSpinner: function() {
    if (!this.spinnerCallCount) this.spinnerCallCount = 0;
    // Count how much it called to show
    this.spinnerCallCount++;
    if (1 == this.spinnerCallCount) {
    var spinner = this.getSpinner();
    if (spinner) spinner.start();
    }

    return this;
    },
    hideSpinner: function() {
    if (!this.spinnerCallCount) this.spinnerCallCount = 0;
    // Prevent from going under zero number
    if (this.spinnerCallCount > 0) this.spinnerCallCount--;

    if (0 == this.spinnerCallCount) {
    var spinner = this.getSpinner();
    if (spinner) spinner.stop();
    }

    return this;
    },

    // --- Memory cache
    getFromGlobalCacheOrExecute: function(handler, key, callback) {
    if ('function' == typeof key) {
    callback = key;
    key = undefined;
    }
    if (undefined === key) key = Util.md5(handler.toString());

    (function(handle) {
    // Already cached
    if ($CacheMemory[key]) handle($CacheMemory[key]);
    // Need to be cached
    else handler(function(result) { callback($CacheMemory[key] = result); });
    })(callback);
    },
    getFromMemory: function(path, def) {
    if (!this.memory) this.memory = {};
    return Util.Object.getPath(this.memory, path, def);
    },
    setToMemory: function(path, value) {
    if (!this.memory) this.memory = {};
    Util.Object.setPath(this.memory, path, value);

    return this;
    },

    // --- Title & header
    /**
    * Set widget title
    * @param {String} title
    */
    setTitle: function(title) {
    title = (title + '').trim();
    if (title) {
    Manager.updateWidgetTitle(this.widgetFrameworkPath, title);
    this.lastWidgetTitle = title;
    }

    return this;
    },
    getTitle: function() {
    return this.lastWidgetTitle;
    },
    /**
    * Append something to widget title. If empty value passed (or no value), then we restore original title
    * @param {String} [text='']
    * @param {Boolean} [spaceBefore=true]
    * @returns this
    */
    appendToTitle: function(text, spaceBefore) {
    if (!spaceBefore) spaceBefore = true;
    this.setTitle(this.actualSettings.title + (!text ? '' : (!spaceBefore ? text : ' ' + text)));

    return this;
    },
    setAsOfDate: function(asOfDate) {
    if (asOfDate) WindowTitleForDate.addDateToTitle(this.getNodeId(), asOfDate, true);
    else WindowTitleForDate.removeDateFromTitle(this.getNodeId());

    this.lastAsOfDate = asOfDate;

    return this;
    },
    getAsOfDate: function() {
    return this.lastAsOfDate || '';
    }
    });
    });
    164 changes: 164 additions & 0 deletions CustomFilter.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,164 @@
    define([
    'jsclass/class',
    './AbstractPlugin',
    'Shared/WidgetFoundation/Util',
    'async',

    // Filters preloading
    'Shared/WidgetFoundation/CustomFilter/ApplyButton',
    'Shared/WidgetFoundation/CustomFilter/Input',
    'Shared/WidgetFoundation/CustomFilter/Select'
    ], function(
    Class, AbstractPlugin, Util, Async,

    // Preloaded filters
    ApplyButtonFilter, InputFilter, SelectFilter) {
    return new Class(AbstractPlugin, {

    // Shortcuts
    preloadedCustomFilters: {
    ApplyButton: ApplyButtonFilter,
    Input: InputFilter,
    Select: SelectFilter
    },

    filters: null,

    initialize: function(widget, args, callback) {
    this.callSuper(widget, args);
    this.filters = [];

    // Preserve context
    var self = this;

    // Async filter loading (some files aren't loaded yet)
    Async.each(args.filters, function(filter, cb) {
    // Constructed filter (yeah, class that not from WF-package can be used)
    if (filter.setCustomFilterPlugin) {
    filter.setCustomFilterPlugin(self);
    self.filters.push(filter);

    cb();
    }
    else {
    // Filter name only, without any parameters
    if ('string' == typeof filter) filter = [filter, {}];

    // Filter with parameters [filterName, parametersObject]
    if (2 === filter.length && 'object' == typeof filter) {

    // Here is args structure, example - ['SomeFilter', { title: 'custom title' }]
    var filterName = filter[0],
    filterArgs = filter[1];

    var onLoad = function (filterClass) {
    var filterObject = new filterClass(filterArgs);
    filterObject.setCustomFilterPlugin(self);
    self.filters.push(filterObject);

    setTimeout(cb, 10);
    };

    // File already loaded
    if (self.preloadedCustomFilters[filterName]) onLoad(self.preloadedCustomFilters[filterName]);

    // Or load class by require-js lib
    else require([filterName], onLoad);

    }

    else throw new Error('Args type is unknown!');
    }

    }, function() {

    // Initial values
    widget.listenEvent(widget.eventsMap.stateLoaded, function() {
    Util.forEach(self.filters, function(i, filter) {
    if (filter.isPersistent()) {
    filter.setValue(widget.getFromState(
    'customFilter.' + filter.getName() + self.getContextUID(),
    filter.getDefaultValue()
    )).dataInitialized();
    }
    });
    });

    // HTML
    widget.listenEvent(widget.eventsMap.viewInitialized, function() {
    var filtersNode = widget.getNodeBodyContainer().find('.filters');
    Util.forEach(self.filters, function(i, filter) {
    var node = $(filter.getHTML());
    filter.nodeCreated(node);
    filtersNode.append(node);
    filter.nodeRendered();
    });
    });

    // Affect on data request
    widget.listenEvent(widget.eventsMap.dataRequestParametersGenerated, function(parameters) {
    Util.Object.merge(parameters.pager.customFilters, (function() {
    var filters = {};
    Util.forEach(self.filters, function (i, filter) {
    if (filter.hasValue()) {
    filters[filter.getName()] = filter.getValue();
    }
    });

    return filters;
    })());
    });

    callback();
    });
    },

    /**
    * Store values in widget state
    *
    * @returns this
    */
    persistValues: function() {
    var self = this;

    Util.forEach(self.filters, function(i, filter) {
    if (filter.isPersistent()) {
    self.getWidget().setToState(
    'customFilter.' + filter.getName() + self.getContextUID(),
    filter.getValue()
    );
    }
    });

    this.getWidget().saveStateIfChanged();

    return this;
    },

    /**
    * Reload widget data (only update)
    *
    * @returns this
    */
    reloadData: function() {
    this.getWidget().reloadGridData();

    return this;
    },

    /**
    * Combined logic
    *
    * @returns this
    */
    persistAndReloadData: function() {
    this.persistValues().reloadData();

    return this;
    },

    getContextUID: function() {
    return '';
    }
    });
    });
    16 changes: 16 additions & 0 deletions RestictedAccountsDefaultModel.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,16 @@
    /**
    * Widget data model
    *
    * @author Sermik 2014, And <[email protected]>
    */
    define(['Shared/WidgetFoundation/WidgetViewModel'], function (Model) {
    return Model.constructor({
    columns: {
    AccountNumber: { type: Model.FIELD_TYPE.ACCOUNT_NUMBER, title: 'Account' },
    RepCode: { type: Model.FIELD_TYPE.REP_ID, title: 'Rep' },
    AccountName: { type: Model.FIELD_TYPE.STRING, title: 'Name' },
    OpenDate: { type: Model.FIELD_TYPE.DATE },
    Restriction: { type: Model.FIELD_TYPE.STRING }
    }
    });
    });
    49 changes: 49 additions & 0 deletions RestrictedAccounts.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,49 @@
    /**
    * Widget itself
    *
    * @author Sermik 2014, And <[email protected]>
    */
    define(['Shared/WidgetFoundation/GridWidget', 'Shared/WidgetFoundation/Util'], function (GridWidget, Util) {
    return GridWidget.loader({
    default: {
    title: 'Restricted Accounts',
    // Minimum size
    size: { width: 1000 },
    // Widget custom filters
    filter: {
    customFilters: [
    ['Select', /* input type */ {
    title: 'Restriction Type:',
    // Input name (must match with server model)
    name: 'restrictionType',
    values: [
    ["All", "All"],
    ["111175", "An Employee of Other Brokerage Firm-Rule 407"],
    ["111185", "Abandoned Property"],
    ["111165", "BRR"],
    ["111144", "CBG Missing Documents"],
    ["111134", "CP Baker - contact margin"],
    ["111153", "Dawson James"],
    ["111121", "Delinquent Documents"],
    ["111122", "Date of Birth/UTMA"],
    ["111142", "EJ Sterling"],
    ["111125", "Equifax Mismatch - AML"],
    ["111133", "FINRA Rule 2111"],
    ["111141", "4 Points Capital-Gene Murphy"],
    ["111155", "Fraud Alert"],
    ["111136", "Lead/Generation Accounts"],
    ["111124", "John Thomas Missing Documents"],
    ["111135", "Margin"],
    ["111143", "Meyers"],
    ["111145", "New Accounts"],
    ["111126", "PCG-San Francisco - contact NA"],
    ["111154", "WRP"]
    ]
    }],
    // Button, which send data to server and persist values
    'ApplyButton'
    ]
    }
    }
    });
    });