UNPKG

ah-dashboard-plugin

Version:

Plugin for an ActionHero Dashboard with many functionalities

1,402 lines (1,177 loc) 55.8 kB
/* * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ 'use strict'; define(['angular'], function (angular) { angular.module('ui.dashboard', ['ui.bootstrap', 'ui.sortable']); angular.module('ui.dashboard') .directive('dashboard', ['WidgetModel', 'WidgetDefCollection', '$modal', 'DashboardState', '$log', function (WidgetModel, WidgetDefCollection, $modal, DashboardState, $log) { return { restrict: 'A', templateUrl: function (element, attr) { return attr.templateUrl ? attr.templateUrl : 'template/dashboard.html'; }, scope: true, controller: ['$scope', '$attrs', function (scope, attrs) { // default options var defaults = { stringifyStorage: true, hideWidgetSettings: false, hideWidgetClose: false, settingsModalOptions: { templateUrl: 'template/widget-settings-template.html', controller: 'WidgetSettingsCtrl' }, onSettingsClose: function (result, widget) { // NOTE: dashboard scope is also passed as 3rd argument jQuery.extend(true, widget, result); }, onSettingsDismiss: function (reason) { // NOTE: dashboard scope is also passed as 2nd argument $log.info('widget settings were dismissed. Reason: ', reason); } }; // from dashboard="options" // scope.options = scope.$eval(attrs.dashboard); // extend default settingsModalOptions // scope.options.settingsModalOptions = scope.options.settingsModalOptions || {}; // extend options with defaults // angular.extend(defaults.settingsModalOptions, scope.options.settingsModalOptions); // angular.extend(scope.options.settingsModalOptions, defaults.settingsModalOptions); // angular.extend(defaults, scope.options); // angular.extend(scope.options, defaults); // from dashboard="options" scope.options = scope.$eval(attrs.dashboard); // Deep options scope.options.settingsModalOptions = scope.options.settingsModalOptions || {}; _.each(['settingsModalOptions'], function (key) { // Ensure it exists on scope.options scope.options[key] = scope.options[key] || {}; // Set defaults _.defaults(scope.options[key], defaults[key]); }); // Shallow options _.defaults(scope.options, defaults); // jQuery.extend(true, defaults, scope.options); // jQuery.extend(scope.options, defaults); var sortableDefaults = { stop: function () { scope.saveDashboard(); }, handle: '.widget-header' }; scope.sortableOptions = angular.extend({}, sortableDefaults, scope.options.sortableOptions || {}); }], link: function (scope) { // Save default widget config for reset scope.defaultWidgets = scope.options.defaultWidgets; //scope.widgetDefs = scope.options.widgetDefinitions; scope.widgetDefs = new WidgetDefCollection(scope.options.widgetDefinitions); var count = 1; // Instantiate new instance of dashboard state scope.dashboardState = new DashboardState( scope.options.storage, scope.options.storageId, scope.options.storageHash, scope.widgetDefs, scope.options.stringifyStorage ); /** * Instantiates a new widget on the dashboard * @param {Object} widgetToInstantiate The definition object of the widget to be instantiated */ scope.addWidget = function (widgetToInstantiate, doNotSave) { var defaultWidgetDefinition = scope.widgetDefs.getByName(widgetToInstantiate.name); if (!defaultWidgetDefinition) { throw 'Widget ' + widgetToInstantiate.name + ' is not found.'; } // Determine the title for the new widget var title; if (widgetToInstantiate.title) { title = widgetToInstantiate.title; } else if (defaultWidgetDefinition.title) { title = defaultWidgetDefinition.title; } else { title = 'Widget ' + count++; } // Deep extend a new object for instantiation widgetToInstantiate = jQuery.extend(true, {}, defaultWidgetDefinition, widgetToInstantiate); // Instantiation var widget = new WidgetModel(widgetToInstantiate, { title: title }); scope.widgets.push(widget); if (!doNotSave) { scope.saveDashboard(); } }; /** * Removes a widget instance from the dashboard * @param {Object} widget The widget instance object (not a definition object) */ scope.removeWidget = function (widget) { scope.widgets.splice(_.indexOf(scope.widgets, widget), 1); scope.saveDashboard(); }; /** * Opens a dialog for setting and changing widget properties * @param {Object} widget The widget instance object */ scope.openWidgetSettings = function (widget) { // Set up $modal options var options = _.defaults({}, widget.settingsModalOptions, scope.options.settingsModalOptions); // Ensure widget is resolved options.resolve = { widget: function () { return widget; } }; // Create the modal var modalInstance = $modal.open(options); var onClose = widget.onSettingsClose || scope.options.onSettingsClose; var onDismiss = widget.onSettingsDismiss || scope.options.onSettingsDismiss; // Set resolve and reject callbacks for the result promise modalInstance.result.then( function (result) { // Call the close callback onClose(result, widget, scope); //AW Persist title change from options editor scope.$emit('widgetChanged', widget); }, function (reason) { // Call the dismiss callback onDismiss(reason, scope); } ); }; /** * Remove all widget instances from dashboard */ scope.clear = function (doNotSave) { scope.widgets = []; if (doNotSave === true) { return; } scope.saveDashboard(); }; /** * Used for preventing default on click event * @param {Object} event A click event * @param {Object} widgetDef A widget definition object */ scope.addWidgetInternal = function (event, widgetDef) { event.preventDefault(); scope.addWidget(widgetDef); }; /** * Uses dashboardState service to save state */ scope.saveDashboard = function (force) { if (!scope.options.explicitSave) { scope.dashboardState.save(scope.widgets); } else { if (typeof scope.options.unsavedChangeCount !== 'number') { scope.options.unsavedChangeCount = 0; } if (force) { scope.options.unsavedChangeCount = 0; scope.dashboardState.save(scope.widgets); } else { ++scope.options.unsavedChangeCount; } } }; /** * Wraps saveDashboard for external use. */ scope.externalSaveDashboard = function () { scope.saveDashboard(true); }; /** * Clears current dash and instantiates widget definitions * @param {Array} widgets Array of definition objects */ scope.loadWidgets = function (widgets) { // AW dashboards are continuously saved today (no "save" button). //scope.defaultWidgets = widgets; scope.savedWidgetDefs = widgets; scope.clear(true); _.each(widgets, function (widgetDef) { scope.addWidget(widgetDef, true); }); }; /** * Resets widget instances to default config * @return {[type]} [description] */ scope.resetWidgetsToDefault = function () { scope.loadWidgets(scope.defaultWidgets); scope.saveDashboard(); }; // Set default widgets array var savedWidgetDefs = scope.dashboardState.load(); // Success handler function handleStateLoad(saved) { scope.options.unsavedChangeCount = 0; if (saved && saved.length) { scope.loadWidgets(saved); } else if (scope.defaultWidgets) { scope.loadWidgets(scope.defaultWidgets); } else { scope.clear(true); } } if (savedWidgetDefs instanceof Array) { handleStateLoad(savedWidgetDefs); } else if (savedWidgetDefs && typeof savedWidgetDefs === 'object' && typeof savedWidgetDefs.then === 'function') { savedWidgetDefs.then(handleStateLoad, handleStateLoad); } else { handleStateLoad(); } // expose functionality externally // functions are appended to the provided dashboard options scope.options.addWidget = scope.addWidget; scope.options.loadWidgets = scope.loadWidgets; scope.options.saveDashboard = scope.externalSaveDashboard; // save state scope.$on('widgetChanged', function (event) { event.stopPropagation(); scope.saveDashboard(); }); } }; }]); /* * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ 'use strict'; angular.module('ui.dashboard') .directive('dashboardLayouts', ['LayoutStorage', '$timeout', '$modal', function (LayoutStorage, $timeout, $modal) { return { scope: true, templateUrl: function (element, attr) { return attr.templateUrl ? attr.templateUrl : 'template/dashboard-layouts.html'; }, link: function (scope, element, attrs) { scope.options = scope.$eval(attrs.dashboardLayouts); var layoutStorage = new LayoutStorage(scope.options); scope.layouts = layoutStorage.layouts; scope.createNewLayout = function () { var newLayout = { title: 'Custom', defaultWidgets: scope.options.defaultWidgets || [] }; layoutStorage.add(newLayout); scope.makeLayoutActive(newLayout); layoutStorage.save(); return newLayout; }; scope.removeLayout = function (layout) { layoutStorage.remove(layout); layoutStorage.save(); }; scope.makeLayoutActive = function (layout) { var current = layoutStorage.getActiveLayout(); if (current && current.dashboard.unsavedChangeCount) { var modalInstance = $modal.open({ templateUrl: 'template/save-changes-modal.html', resolve: { layout: function () { return layout; } }, controller: 'SaveChangesModalCtrl' }); // Set resolve and reject callbacks for the result promise modalInstance.result.then( function () { current.dashboard.saveDashboard(); scope._makeLayoutActive(layout); }, function () { scope._makeLayoutActive(layout); } ); } else { scope._makeLayoutActive(layout); } }; scope._makeLayoutActive = function (layout) { angular.forEach(scope.layouts, function (l) { if (l !== layout) { l.active = false; } else { l.active = true; } }); layoutStorage.save(); }; scope.isActive = function (layout) { return !!layout.active; }; scope.editTitle = function (layout) { var input = element.find('input[data-layout="' + layout.id + '"]'); layout.editingTitle = true; $timeout(function () { input.focus()[0].setSelectionRange(0, 9999); }); }; // saves whatever is in the title input as the new title scope.saveTitleEdit = function (layout) { layout.editingTitle = false; layoutStorage.save(); }; scope.options.saveLayouts = function () { layoutStorage.save(true); }; scope.options.addWidget = function () { var layout = layoutStorage.getActiveLayout(); if (layout) { layout.dashboard.addWidget.apply(layout.dashboard, arguments); } }; scope.options.loadWidgets = function () { var layout = layoutStorage.getActiveLayout(); if (layout) { layout.dashboard.loadWidgets.apply(layout.dashboard, arguments); } }; scope.options.saveDashboard = function () { var layout = layoutStorage.getActiveLayout(); if (layout) { layout.dashboard.saveDashboard.apply(layout.dashboard, arguments); } }; } }; }]); /* * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ 'use strict'; angular.module('ui.dashboard') .directive('widget', function ($injector) { return { controller: 'DashboardWidgetCtrl', link: function (scope) { var widget = scope.widget; // set up data source if (widget.dataModelType) { var DataModelConstructor; // data model constructor function var type = typeof widget.dataModelType; if (type === 'function') { DataModelConstructor = widget.dataModelType; } else if (type === 'string') { $injector.invoke([widget.dataModelType, function (DataModelType) { DataModelConstructor = DataModelType; }]); } else { throw new Error('widget dataModelType should be function or string'); } var ds; if (widget.dataModelArgs) { ds = new DataModelConstructor(widget.dataModelArgs); } else { ds = new DataModelConstructor(); } widget.dataModel = ds; ds.setup(widget, scope); ds.init(); scope.$on('$destroy', _.bind(ds.destroy, ds)); } // Compile the widget template, emit add event scope.compileTemplate(); scope.$emit('widgetAdded', widget); } }; }); /* * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ 'use strict'; angular.module('ui.dashboard') .factory('LayoutStorage', function () { var noopStorage = { setItem: function () { }, getItem: function () { }, removeItem: function () { } }; function LayoutStorage(options) { var defaults = { storage: noopStorage, storageHash: '', stringifyStorage: true }; angular.extend(defaults, options); angular.extend(options, defaults); this.id = options.storageId; this.storage = options.storage; this.storageHash = options.storageHash; this.stringifyStorage = options.stringifyStorage; this.widgetDefinitions = options.widgetDefinitions; this.defaultLayouts = options.defaultLayouts; this.widgetButtons = options.widgetButtons; this.explicitSave = options.explicitSave; this.defaultWidgets = options.defaultWidgets; this.settingsModalOptions = options.settingsModalOptions; this.onSettingsClose = options.onSettingsClose; this.onSettingsDismiss = options.onSettingsDismiss; this.options = options; this.options.unsavedChangeCount = 0; this.layouts = []; this.states = {}; this.load(); this._ensureActiveLayout(); } LayoutStorage.prototype = { add: function (layouts) { if (!(layouts instanceof Array)) { layouts = [layouts]; } var self = this; angular.forEach(layouts, function (layout) { layout.dashboard = layout.dashboard || {}; layout.dashboard.storage = self; layout.dashboard.storageId = layout.id = self._getLayoutId.call(self, layout); layout.dashboard.widgetDefinitions = self.widgetDefinitions; layout.dashboard.stringifyStorage = false; layout.dashboard.defaultWidgets = layout.defaultWidgets || self.defaultWidgets; layout.dashboard.widgetButtons = self.widgetButtons; layout.dashboard.explicitSave = self.explicitSave; layout.dashboard.settingsModalOptions = self.settingsModalOptions; layout.dashboard.onSettingsClose = self.onSettingsClose; layout.dashboard.onSettingsDismiss = self.onSettingsDismiss; self.layouts.push(layout); }); }, remove: function (layout) { var index = this.layouts.indexOf(layout); if (index >= 0) { this.layouts.splice(index, 1); delete this.states[layout.id]; // check for active if (layout.active && this.layouts.length) { var nextActive = index > 0 ? index - 1 : 0; this.layouts[nextActive].active = true; } } }, save: function () { var state = { layouts: this._serializeLayouts(), states: this.states, storageHash: this.storageHash }; if (this.stringifyStorage) { state = JSON.stringify(state); } this.storage.setItem(this.id, state); this.options.unsavedChangeCount = 0; }, load: function () { var serialized = this.storage.getItem(this.id); this.clear(); if (serialized) { // check for promise if (typeof serialized === 'object' && typeof serialized.then === 'function') { this._handleAsyncLoad(serialized); } else { this._handleSyncLoad(serialized); } } else { this._addDefaultLayouts(); } }, clear: function () { this.layouts = []; this.states = {}; }, setItem: function (id, value) { this.states[id] = value; this.save(); }, getItem: function (id) { return this.states[id]; }, removeItem: function (id) { delete this.states[id]; this.save(); }, getActiveLayout: function () { var len = this.layouts.length; for (var i = 0; i < len; i++) { var layout = this.layouts[i]; if (layout.active) { return layout; } } return false; }, _addDefaultLayouts: function () { var self = this; angular.forEach(this.defaultLayouts, function (layout) { self.add(angular.extend({}, layout)); }); }, _serializeLayouts: function () { var result = []; angular.forEach(this.layouts, function (l) { result.push({ title: l.title, id: l.id, active: l.active, defaultWidgets: l.dashboard.defaultWidgets }); }); return result; }, _handleSyncLoad: function (serialized) { var deserialized; if (this.stringifyStorage) { try { deserialized = JSON.parse(serialized); } catch (e) { this._addDefaultLayouts(); return; } } else { deserialized = serialized; } if (this.storageHash !== deserialized.storageHash) { this._addDefaultLayouts(); return; } this.states = deserialized.states; this.add(deserialized.layouts); }, _handleAsyncLoad: function (promise) { var self = this; promise.then( angular.bind(self, this._handleSyncLoad), angular.bind(self, this._addDefaultLayouts) ); }, _ensureActiveLayout: function () { for (var i = 0; i < this.layouts.length; i++) { var layout = this.layouts[i]; if (layout.active) { return; } } if (this.layouts[0]) { this.layouts[0].active = true; } }, _getLayoutId: function (layout) { if (layout.id) { return layout.id; } var max = 0; for (var i = 0; i < this.layouts.length; i++) { var id = this.layouts[i].id; max = Math.max(max, id * 1); } return max + 1; } }; return LayoutStorage; }); /* * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ 'use strict'; angular.module('ui.dashboard') .factory('DashboardState', ['$log', '$q', function ($log, $q) { function DashboardState(storage, id, hash, widgetDefinitions, stringify) { this.storage = storage; this.id = id; this.hash = hash; this.widgetDefinitions = widgetDefinitions; this.stringify = stringify; } DashboardState.prototype = { /** * Takes array of widget instance objects, serializes, * and saves state. * * @param {Array} widgets scope.widgets from dashboard directive * @return {Boolean} true on success, false on failure */ save: function (widgets) { if (!this.storage) { return true; } var serialized = _.map(widgets, function (widget) { var widgetObject = { title: widget.title, name: widget.name, style: widget.style, dataModelOptions: widget.dataModelOptions, storageHash: widget.storageHash, attrs: widget.attrs }; return widgetObject; }); var item = { widgets: serialized, hash: this.hash }; if (this.stringify) { item = JSON.stringify(item); } this.storage.setItem(this.id, item); return true; }, /** * Loads dashboard state from the storage object. * Can handle a synchronous response or a promise. * * @return {Array|Promise} Array of widget definitions or a promise */ load: function () { if (!this.storage) { return null; } var serialized; // try loading storage item serialized = this.storage.getItem(this.id); if (serialized) { // check for promise if (typeof serialized === 'object' && typeof serialized.then === 'function') { return this._handleAsyncLoad(serialized); } // otherwise handle synchronous load return this._handleSyncLoad(serialized); } else { return null; } }, _handleSyncLoad: function (serialized) { var deserialized, result = []; if (!serialized) { return null; } if (this.stringify) { try { // to deserialize the string deserialized = JSON.parse(serialized); } catch (e) { // bad JSON, log a warning and return $log.warn('Serialized dashboard state was malformed and could not be parsed: ', serialized); return null; } } else { deserialized = serialized; } // check hash against current hash if (deserialized.hash !== this.hash) { $log.info('Serialized dashboard from storage was stale (old hash: ' + deserialized.hash + ', new hash: ' + this.hash + ')'); this.storage.removeItem(this.id); return null; } // Cache widgets var savedWidgetDefs = deserialized.widgets; // instantiate widgets from stored data for (var i = 0; i < savedWidgetDefs.length; i++) { // deserialized object var savedWidgetDef = savedWidgetDefs[i]; // widget definition to use var widgetDefinition = this.widgetDefinitions.getByName(savedWidgetDef.name); // check for no widget if (!widgetDefinition) { // no widget definition found, remove and return false $log.warn('Widget with name "' + savedWidgetDef.name + '" was not found in given widget definition objects'); continue; } // check widget-specific storageHash if (widgetDefinition.hasOwnProperty('storageHash') && widgetDefinition.storageHash !== savedWidgetDef.storageHash) { // widget definition was found, but storageHash was stale, removing storage $log.info('Widget Definition Object with name "' + savedWidgetDef.name + '" was found ' + 'but the storageHash property on the widget definition is different from that on the ' + 'serialized widget loaded from storage. hash from storage: "' + savedWidgetDef.storageHash + '"' + ', hash from WDO: "' + widgetDefinition.storageHash + '"'); continue; } // push instantiated widget to result array result.push(savedWidgetDef); } return result; }, _handleAsyncLoad: function (promise) { var self = this; var deferred = $q.defer(); promise.then( // success function (res) { var result = self._handleSyncLoad(res); if (result) { deferred.resolve(result); } else { deferred.reject(result); } }, // failure function (res) { deferred.reject(res); } ); return deferred.promise; } }; return DashboardState; }]); /* * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ 'use strict'; angular.module('ui.dashboard') .factory('WidgetDataModel', function () { function WidgetDataModel() { } WidgetDataModel.prototype = { setup: function (widget, scope) { this.dataAttrName = widget.dataAttrName; this.dataModelOptions = widget.dataModelOptions; this.widgetScope = scope; }, updateScope: function (data) { this.widgetScope.widgetData = data; }, init: function () { // to be overridden by subclasses }, destroy: function () { // to be overridden by subclasses } }; return WidgetDataModel; }); /* * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ 'use strict'; angular.module('ui.dashboard') .factory('WidgetDefCollection', function () { function WidgetDefCollection(widgetDefs) { this.push.apply(this, widgetDefs); // build (name -> widget definition) map for widget lookup by name var map = {}; _.each(widgetDefs, function (widgetDef) { map[widgetDef.name] = widgetDef; }); this.map = map; } WidgetDefCollection.prototype = Object.create(Array.prototype); WidgetDefCollection.prototype.getByName = function (name) { return this.map[name]; }; return WidgetDefCollection; }); /* * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ 'use strict'; angular.module('ui.dashboard') .factory('WidgetModel', function () { // constructor for widget model instances function WidgetModel(Class, overrides) { var defaults = { title: 'Widget', name: Class.name, attrs: Class.attrs, dataAttrName: Class.dataAttrName, dataModelType: Class.dataModelType, dataModelArgs: Class.dataModelArgs, // used in data model constructor, not serialized //AW Need deep copy of options to support widget options editing dataModelOptions: Class.dataModelOptions, settingsModalOptions: Class.settingsModalOptions, onSettingsClose: Class.onSettingsClose, onSettingsDismiss: Class.onSettingsDismiss, style: Class.style }; overrides = overrides || {}; angular.extend(this, angular.copy(defaults), overrides); this.style = this.style || { width: '33%' }; this.setWidth(this.style.width); if (Class.templateUrl) { this.templateUrl = Class.templateUrl; } else if (Class.template) { this.template = Class.template; } else { var directive = Class.directive || Class.name; this.directive = directive; } } WidgetModel.prototype = { // sets the width (and widthUnits) setWidth: function (width, units) { width = width.toString(); units = units || width.replace(/^[-\.\d]+/, '') || '%'; this.widthUnits = units; width = parseFloat(width); if (width < 0) { return false; } if (units === '%') { width = Math.min(100, width); width = Math.max(0, width); } this.style.width = width + '' + units; return true; } }; return WidgetModel; }); /* * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ 'use strict'; angular.module('ui.dashboard') .controller('SaveChangesModalCtrl', ['$scope', '$modalInstance', 'layout', function ($scope, $modalInstance, layout) { // add layout to scope $scope.layout = layout; $scope.ok = function () { $modalInstance.close(); }; $scope.cancel = function () { $modalInstance.dismiss(); }; }]); /* * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ 'use strict'; angular.module('ui.dashboard') .controller('DashboardWidgetCtrl', ['$scope', '$element', '$compile', '$window', '$timeout', function ($scope, $element, $compile, $window, $timeout) { // Fills "container" with compiled view $scope.makeTemplateString = function () { var widget = $scope.widget; // First, build template string var templateString = ''; if (widget.templateUrl) { // Use ng-include for templateUrl templateString = '<div ng-include="\'' + widget.templateUrl + '\'"></div>'; } else if (widget.template) { // Direct string template templateString = widget.template; } else { // Assume attribute directive templateString = '<div ' + widget.directive; // Check if data attribute was specified if (widget.dataAttrName) { widget.attrs = widget.attrs || {}; widget.attrs[widget.dataAttrName] = 'widgetData'; } // Check for specified attributes if (widget.attrs) { // First check directive name attr if (widget.attrs[widget.directive]) { templateString += '="' + widget.attrs[widget.directive] + '"'; } // Add attributes _.each(widget.attrs, function (value, attr) { // make sure we aren't reusing directive attr if (attr !== widget.directive) { templateString += ' ' + attr + '="' + value + '"'; } }); } templateString += '></div>'; } return templateString; }; $scope.grabResizer = function (e) { var widget = $scope.widget; var widgetElm = $element.find('.widget'); // ignore middle- and right-click if (e.which !== 1) { return; } e.stopPropagation(); e.originalEvent.preventDefault(); // get the starting horizontal position var initX = e.clientX; // console.log('initX', initX); // Get the current width of the widget and dashboard var pixelWidth = widgetElm.width(); var pixelHeight = widgetElm.height(); var widgetStyleWidth = widget.style.width; var widthUnits = widget.widthUnits; var unitWidth = parseFloat(widgetStyleWidth); // create marquee element for resize action var $marquee = angular.element('<div class="widget-resizer-marquee" style="height: ' + pixelHeight + 'px; width: ' + pixelWidth + 'px;"></div>'); widgetElm.append($marquee); // determine the unit/pixel ratio var transformMultiplier = unitWidth / pixelWidth; // updates marquee with preview of new width var mousemove = function (e) { var curX = e.clientX; var pixelChange = curX - initX; var newWidth = pixelWidth + pixelChange; $marquee.css('width', newWidth + 'px'); }; // sets new widget width on mouseup var mouseup = function (e) { // remove listener and marquee jQuery($window).off('mousemove', mousemove); $marquee.remove(); // calculate change in units var curX = e.clientX; var pixelChange = curX - initX; var unitChange = Math.round(pixelChange * transformMultiplier * 100) / 100; // add to initial unit width var newWidth = unitWidth * 1 + unitChange; widget.setWidth(newWidth + widthUnits); $scope.$emit('widgetChanged', widget); $scope.$apply(); }; jQuery($window).on('mousemove', mousemove).one('mouseup', mouseup); }; // replaces widget title with input $scope.editTitle = function (widget) { var widgetElm = $element.find('.widget'); widget.editingTitle = true; // HACK: get the input to focus after being displayed. $timeout(function () { widgetElm.find('form.widget-title input:eq(0)').focus()[0].setSelectionRange(0, 9999); }); }; // saves whatever is in the title input as the new title $scope.saveTitleEdit = function (widget) { widget.editingTitle = false; $scope.$emit('widgetChanged', widget); }; $scope.compileTemplate = function () { var container = $scope.findWidgetContainer($element); var templateString = $scope.makeTemplateString(); var widgetElement = angular.element(templateString); container.empty(); container.append(widgetElement); $compile(widgetElement)($scope); }; $scope.findWidgetContainer = function (element) { // widget placeholder is the first (and only) child of .widget-content return element.find('.widget-content'); }; }]); /* * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ 'use strict'; angular.module('ui.dashboard') .controller('WidgetSettingsCtrl', ['$scope', '$modalInstance', 'widget', function ($scope, $modalInstance, widget) { // add widget to scope $scope.widget = widget; // set up result object $scope.result = jQuery.extend(true, {}, widget); $scope.ok = function () { $modalInstance.close($scope.result); }; $scope.cancel = function () { $modalInstance.dismiss('cancel'); }; }]); angular.module("ui.dashboard").run(["$templateCache", function ($templateCache) { $templateCache.put("template/alt-dashboard.html", "<div>\n" + " <div class=\"btn-toolbar\" ng-if=\"!options.hideToolbar\">\n" + " <div class=\"btn-group\" ng-if=\"!options.widgetButtons\">\n" + " <button type=\"button\" class=\"dropdown-toggle btn btn-primary\" data-toggle=\"dropdown\">Add Widget <span\n" + " class=\"caret\"></span></button>\n" + " <ul class=\"dropdown-menu\" role=\"menu\">\n" + " <li ng-repeat=\"widget in widgetDefs\">\n" + " <a href=\"#\" ng-click=\"addWidgetInternal($event, widget);\"><span class=\"label label-primary\">{{widget.name}}</span></a>\n" + " </li>\n" + " </ul>\n" + " </div>\n" + "\n" + " <div class=\"btn-group\" ng-if=\"options.widgetButtons\">\n" + " <button ng-repeat=\"widget in widgetDefs\"\n" + " ng-click=\"addWidgetInternal($event, widget);\" type=\"button\" class=\"btn btn-primary\">\n" + " {{widget.name}}\n" + " </button>\n" + " </div>\n" + "\n" + " <button class=\"btn btn-warning\" ng-click=\"resetWidgetsToDefault()\">Default Widgets</button>\n" + "\n" + " <button ng-if=\"options.storage && options.explicitSave\" ng-click=\"options.saveDashboard()\" class=\"btn btn-success\" ng-hide=\"!options.unsavedChangeCount\">{{ !options.unsavedChangeCount ? \"Alternative - No Changes\" : \"Save\" }}</button>\n" + "\n" + " <button ng-click=\"clear();\" ng-hide=\"!widgets.length\" type=\"button\" class=\"btn btn-info\">Clear</button>\n" + " </div>\n" + "\n" + " <div ui-sortable=\"sortableOptions\" ng-model=\"widgets\" class=\"dashboard-widget-area\">\n" + " <div ng-repeat=\"widget in widgets\" ng-style=\"widget.style\" class=\"widget-container\" widget>\n" + " <div class=\"widget panel panel-default\">\n" + " <div class=\"widget-header panel-heading\">\n" + " <h3 class=\"panel-title\">\n" + " <span class=\"widget-title\" ng-dblclick=\"editTitle(widget)\" ng-hide=\"widget.editingTitle\">{{widget.title}}</span>\n" + " <form action=\"\" class=\"widget-title\" ng-show=\"widget.editingTitle\" ng-submit=\"saveTitleEdit(widget)\">\n" + " <input type=\"text\" ng-model=\"widget.title\" class=\"form-control\">\n" + " </form>\n" + " <span class=\"label label-primary\" ng-if=\"!options.hideWidgetName\">{{widget.name}}</span>\n" + " <span ng-click=\"removeWidget(widget);\" class=\"glyphicon glyphicon-remove\" ng-if=\"!options.hideWidgetClose\"></span>\n" + " <span ng-click=\"openWidgetSettings(widget);\" class=\"glyphicon glyphicon-cog\" ng-if=\"!options.hideWidgetSettings\"></span>\n" + " </h3>\n" + " </div>\n" + " <div class=\"panel-body widget-content\"></div>\n" + " <div class=\"widget-ew-resizer\" ng-mousedown=\"grabResizer($event)\"></div>\n" + " </div>\n" + " </div>\n" + " </div>\n" + "</div>\n" ); $templateCache.put("template/dashboard-layouts.html", "<ul class=\"nav nav-tabs layout-tabs\">\n" + " <li ng-repeat=\"layout in layouts\" ng-class=\"{ active: layout.active }\">\n" + " <a ng-click=\"makeLayoutActive(layout)\">\n" + " <span ng-dblclick=\"editTitle(layout)\" ng-show=\"!layout.editingTitle\">{{layout.title}}</span>\n" + " <form action=\"\" class=\"layout-title\" ng-show=\"layout.editingTitle\" ng-submit=\"saveTitleEdit(layout)\">\n" + " <input type=\"text\" ng-model=\"layout.title\" class=\"form-control\" data-layout=\"{{layout.id}}\">\n" + " </form>\n" + " <span ng-click=\"removeLayout(layout)\" class=\"glyphicon glyphicon-remove remove-layout-icon\"></span>\n" + " <!-- <span class=\"glyphicon glyphicon-pencil\"></span> -->\n" + " <!-- <span class=\"glyphicon glyphicon-remove\"></span> -->\n" + " </a>\n" + " </li>\n" + " <li>\n" + " <a ng-click=\"createNewLayout()\">\n" + " <span class=\"glyphicon glyphicon-plus\"></span>\n" + " </a>\n" + " </li>\n" + "</ul>\n" + "<div ng-repeat=\"layout in layouts | filter:isActive\" dashboard=\"layout.dashboard\" template-url=\"template/dashboard.html\"></div>\n" ); $templateCache.put("template/dashboard.html", "<div>\n" +