From d25e01fccef31980bb7a05d4d098182c7b22c5ef Mon Sep 17 00:00:00 2001 From: Andy Perlitch Date: Tue, 10 Feb 2015 15:54:59 -0800 Subject: [PATCH 01/18] grunt -> gulp --- .bowerrc | 3 + .editorconfig | 13 + .gitignore | 13 +- .jshintrc | 15 +- .yo-rc.json | 65 + README.md | 4 +- bower.json | 29 +- demo/index.html | 74 - dist/malhar-angular-dashboard.css | 80 + ...shboard.js => malhar-angular-dashboard.js} | 1639 ++++++++--------- e2e/main.po.js | 15 + e2e/main.spec.js | 21 + favicon.ico | Bin 0 -> 32988 bytes gulp/build-demo.js | 82 + gulp/build.js | 47 + gulp/e2e-tests.js | 35 + gulp/inject.js | 41 + gulp/proxy.js | 65 + gulp/server.js | 59 + gulp/styles.js | 53 + gulp/unit-tests.js | 35 + gulp/watch.js | 14 + gulpfile.js | 54 +- karma.conf.js | 70 +- package.json | 92 +- protractor.conf.js | 25 + src/404.html | 157 ++ src/angular-ui-dashboard.css | 88 - .../app}/customWidgetSettings.js | 0 {demo/scripts => src/app}/dataModel.js | 0 {demo/scripts => src/app}/demo.js | 14 +- demo/demo.css => src/app/demo.less | 0 {demo/scripts => src/app}/directives.js | 0 {demo/scripts => src/app}/explicitSave.js | 0 src/app/index.js | 3 + src/app/index.less | 16 + {demo/scripts => src/app}/layouts.js | 0 {demo/scripts => src/app}/resize.js | 0 .../configurableWidgetModalOptions.html | 0 .../app}/template/customSettingsTemplate.html | 0 {demo => src/app}/template/fluid.html | 0 {demo => src/app/template}/layouts.html | 0 {demo => src/app}/template/resizable.html | 0 {demo => src/app/template}/view.html | 0 .../app}/template/widgetSpecificSettings.html | 0 src/app/vendor.less | 3 + .../dashboard/WidgetSettingsCtrl.js} | 0 .../directives/dashboard/altDashboard.html | 0 .../directives/dashboard}/dashboard.html | 2 +- .../directives/dashboard}/dashboard.js | 4 +- .../directives/dashboard/dashboard.less | 0 .../directives/dashboard}/dashboard.spec.js | 0 .../dashboard}/widget-settings-template.html | 0 .../dashboardLayouts/SaveChangesModal.html | 0 .../dashboardLayouts}/SaveChangesModalCtrl.js | 0 .../dashboardLayouts/dashboardLayouts.html | 2 +- .../dashboardLayouts/dashboardLayouts.js} | 5 +- .../dashboardLayouts/dashboardLayouts.spec.js | 0 .../directives/widget/DashboardWidgetCtrl.js} | 0 .../widget/DashboardWidgetCtrl.spec.js | 0 .../directives/widget}/widget.js | 0 .../directives/widget/widget.spec.js | 0 .../models/DashboardState.js} | 0 src/{ => components}/models/LayoutStorage.js | 0 .../components/models}/LayoutStorage.spec.js | 0 .../models/WidgetDataModel.js} | 0 .../models/WidgetDefCollection.js} | 17 + .../models/WidgetModel.js} | 0 src/favicon.ico | Bin 0 -> 4286 bytes src/index.html | 73 + template/widget-default-content.html | 0 71 files changed, 1779 insertions(+), 1248 deletions(-) create mode 100644 .bowerrc create mode 100644 .editorconfig create mode 100644 .yo-rc.json delete mode 100644 demo/index.html create mode 100644 dist/malhar-angular-dashboard.css rename dist/{angular-ui-dashboard.js => malhar-angular-dashboard.js} (80%) create mode 100644 e2e/main.po.js create mode 100644 e2e/main.spec.js create mode 100644 favicon.ico create mode 100644 gulp/build-demo.js create mode 100644 gulp/build.js create mode 100644 gulp/e2e-tests.js create mode 100644 gulp/inject.js create mode 100644 gulp/proxy.js create mode 100644 gulp/server.js create mode 100644 gulp/styles.js create mode 100644 gulp/unit-tests.js create mode 100644 gulp/watch.js create mode 100644 protractor.conf.js create mode 100644 src/404.html delete mode 100644 src/angular-ui-dashboard.css rename {demo/scripts => src/app}/customWidgetSettings.js (100%) rename {demo/scripts => src/app}/dataModel.js (100%) rename {demo/scripts => src/app}/demo.js (92%) rename demo/demo.css => src/app/demo.less (100%) rename {demo/scripts => src/app}/directives.js (100%) rename {demo/scripts => src/app}/explicitSave.js (100%) create mode 100644 src/app/index.js create mode 100644 src/app/index.less rename {demo/scripts => src/app}/layouts.js (100%) rename {demo/scripts => src/app}/resize.js (100%) rename {demo => src/app}/template/configurableWidgetModalOptions.html (100%) rename {demo => src/app}/template/customSettingsTemplate.html (100%) rename {demo => src/app}/template/fluid.html (100%) rename {demo => src/app/template}/layouts.html (100%) rename {demo => src/app}/template/resizable.html (100%) rename {demo => src/app/template}/view.html (100%) rename {demo => src/app}/template/widgetSpecificSettings.html (100%) create mode 100644 src/app/vendor.less rename src/{controllers/widgetSettingsCtrl.js => components/directives/dashboard/WidgetSettingsCtrl.js} (100%) rename template/alt-dashboard.html => src/components/directives/dashboard/altDashboard.html (100%) rename {template => src/components/directives/dashboard}/dashboard.html (98%) rename src/{directives => components/directives/dashboard}/dashboard.js (98%) rename dist/angular-ui-dashboard.css => src/components/directives/dashboard/dashboard.less (100%) rename {test/spec => src/components/directives/dashboard}/dashboard.spec.js (100%) rename {template => src/components/directives/dashboard}/widget-settings-template.html (100%) rename template/save-changes-modal.html => src/components/directives/dashboardLayouts/SaveChangesModal.html (100%) rename src/{controllers => components/directives/dashboardLayouts}/SaveChangesModalCtrl.js (100%) rename template/dashboard-layouts.html => src/components/directives/dashboardLayouts/dashboardLayouts.html (92%) rename src/{directives/dashboard-layouts.js => components/directives/dashboardLayouts/dashboardLayouts.js} (95%) rename test/spec/dashboard-layouts.spec.js => src/components/directives/dashboardLayouts/dashboardLayouts.spec.js (100%) rename src/{controllers/dashboardWidgetCtrl.js => components/directives/widget/DashboardWidgetCtrl.js} (100%) rename test/spec/dashboardWidgetCtrl.js => src/components/directives/widget/DashboardWidgetCtrl.spec.js (100%) rename src/{directives => components/directives/widget}/widget.js (100%) rename test/spec/widget.js => src/components/directives/widget/widget.spec.js (100%) rename src/{models/dashboardState.js => components/models/DashboardState.js} (100%) rename src/{ => components}/models/LayoutStorage.js (100%) rename {test/spec => src/components/models}/LayoutStorage.spec.js (100%) rename src/{models/widgetDataModel.js => components/models/WidgetDataModel.js} (100%) rename src/{models/widgetDefCollection.js => components/models/WidgetDefCollection.js} (77%) rename src/{models/widgetModel.js => components/models/WidgetModel.js} (100%) create mode 100644 src/favicon.ico create mode 100644 src/index.html delete mode 100644 template/widget-default-content.html diff --git a/.bowerrc b/.bowerrc new file mode 100644 index 0000000..69fad35 --- /dev/null +++ b/.bowerrc @@ -0,0 +1,3 @@ +{ + "directory": "bower_components" +} diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..e717f5e --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +# http://editorconfig.org +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false diff --git a/.gitignore b/.gitignore index f021a04..cd2b4a8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ -node_modules -bower_components -.idea -.DS_Store -/template/dashboard.js -/coverage -/demo_dist \ No newline at end of file +node_modules/ +bower_components/ +.sass-cache/ +.tmp/ +dist/ +/demo/ \ No newline at end of file diff --git a/.jshintrc b/.jshintrc index 3353f07..2683e48 100644 --- a/.jshintrc +++ b/.jshintrc @@ -1,6 +1,5 @@ { "node": true, - "browser": true, "esnext": true, "bitwise": true, "camelcase": true, @@ -18,9 +17,19 @@ "strict": true, "trailing": true, "smarttabs": true, + "white": true, + "validthis": true, "globals": { "angular": false, - "_": false, - "jQuery": false + // Angular Mocks + "inject": false, + // JASMINE + "describe": false, + "it": false, + "before": false, + "beforeEach": false, + "after": false, + "afterEach": false, + "expect": false } } diff --git a/.yo-rc.json b/.yo-rc.json new file mode 100644 index 0000000..e48afce --- /dev/null +++ b/.yo-rc.json @@ -0,0 +1,65 @@ +{ + "generator-gulp-angular": { + "props": { + "paths": { + "src": "src", + "dist": "dist", + "e2e": "e2e", + "tmp": ".tmp" + }, + "angularVersion": "~1.3.4", + "angularModules": [], + "jQuery": { + "name": null, + "version": null + }, + "resource": { + "name": null, + "version": null, + "module": null + }, + "router": { + "name": null, + "version": null, + "module": null + }, + "ui": { + "name": "bootstrap-sass-official", + "version": "~3.3.1", + "key": "bootstrap", + "module": null + }, + "bootstrapComponents": { + "name": "angular-bootstrap", + "version": "0.12.x", + "key": "ui-bootstrap", + "module": "ui.bootstrap" + }, + "cssPreprocessor": { + "key": "less", + "extension": "less", + "module": "gulp-less", + "version": "~1.3.6" + }, + "jsPreprocessor": { + "key": "none", + "extension": "js", + "srcExtension": "js", + "module": null, + "version": null + }, + "htmlPreprocessor": { + "key": "none", + "extension": "html", + "module": null, + "version": null + }, + "foundationComponents": { + "name": null, + "version": null, + "key": null, + "module": null + } + } + } +} \ No newline at end of file diff --git a/README.md b/README.md index 7733c86..a43544a 100644 --- a/README.md +++ b/README.md @@ -176,14 +176,14 @@ You can think of Widget Definition Objects as a __class__ and the widgets on the | key | type | default value | required | description | ----------------- | ------ | ------------- | -------- | ----------- -| name | Object | n/a | true | Name of Widget Definition Object. If no `templateUrl`, `template`, or `directive` are on the Widget Definition | Object, this is assumed to be a directive name. In other words, the `directive` attribute is set to this value. +| name | String | n/a | true | Name of Widget Definition Object. If no `templateUrl`, `template`, or `directive` are on the Widget Definition Object, this is assumed to be a directive name. In other words, the `directive` attribute is set to this value. | title | String | n/a | false | Default title of widget instances | attrs | Object | n/a | false | Map of attributes to add to the markup of the widget. Changes to these will be stored when using the `storage` option | (see **Persistence** section below). | templateUrl | String | n/a | false | URL of template to use for widget content | template | String | n/a | false | String template (ignored if templateUrl is present) | directive | String | n/a | false | HTML-injectable directive name (eg. `"ng-show"`) | dataModelType | Function or String | n/a | false | Constructor for the dataModel object, which provides data to the widget (see below for more information). -| dataModelOptions | Object | n/a | false | Arbitrary values to supply to the dataModel. Available on dataModel instance as this.dataModelOptions. Serializable | values in this object will also be saved if `storage` is being used (see the **Persistence** section below). +| dataModelOptions | Object | n/a | false | Arbitrary values to supply to the dataModel. Available on dataModel instance as this.dataModelOptions. Serializable values in this object will also be saved if `storage` is being used (see the **Persistence** section below). | dataModelArgs | Object | n/a | false | Object to be passed to data model constructor function. This object is not serialized by default and if defined should be present in widget definitions. | dataAttrName | String | n/a | false | Name of attribute to bind `widgetData` model | storageHash | String | n/a | false | This is analogous to the `storageHash` option on the dashboard, except at a widget-level instead of a dashboard-wide | level. This can be helpful if you would only like to invalidate stored state of one widget at a time instead of all widgets. diff --git a/bower.json b/bower.json index 7557a4f..391c665 100644 --- a/bower.json +++ b/bower.json @@ -1,20 +1,25 @@ { "name": "malhar-angular-dashboard", - "main": "./dist/angular-ui-dashboard.js", - "version": "0.8.1", - "license": "Apache License, v2.0", + "version": "0.8.2", "dependencies": { - "jquery": "~2.0.3", + "bootstrap": "~3.3.1", + "angular-bootstrap": "0.12.x", "angular": "~1.3", - "angular-bootstrap": "~0.11.0", - "angular-ui-sortable": "~0.13.1", - "jquery-ui": "~1.11.0", - "lodash": "~2.4.1" + "angular-ui-sortable": "~0.13.3", + "lodash": "~3.1" }, + "main": [ + "dist/malhar-angular-dashboard.css", + "dist/malhar-angular-dashboard.js" + ], "devDependencies": { - "angular-route": "~1.3", "angular-mocks": "~1.3", - "angular-markdown-directive": "~0.3.0", - "bootstrap": "~3.2.0" + "angular-route": "~1.3", + "angular-markdown-directive": "~0.3.1" + }, + "overrides": { + "showdown": { + "main": "src/showdown.js" + } } -} +} \ No newline at end of file diff --git a/demo/index.html b/demo/index.html deleted file mode 100644 index 2d407c4..0000000 --- a/demo/index.html +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - -
- -
- -
- - - - - - - - - - - - - - - - - - - - - - - - diff --git a/dist/malhar-angular-dashboard.css b/dist/malhar-angular-dashboard.css new file mode 100644 index 0000000..58e4b77 --- /dev/null +++ b/dist/malhar-angular-dashboard.css @@ -0,0 +1,80 @@ +.dashboard-widget-area { + margin: 10px 0 30px; + min-height: 200px; +} +.widget-container { + float: left; + display: inline-block; + width: 33%; + padding-bottom: 1em; +} +.widget { + margin: 0 1em 0 0; + background-color: white; + border: 2px solid #444; + border-radius: 5px; + position: relative; + height: 100%; +} +.widget-header { + overflow: hidden; +} +.widget-header .label { + display: inline-block; + vertical-align: middle; +} +.widget-header .glyphicon { + cursor: pointer; + float: right; + opacity: 0.5; + margin-left: 5px; +} +.widget-header .glyphicon:hover { + opacity: 1; +} +.widget-header .widget-title { + vertical-align: middle; +} +.widget-header form.widget-title { + display: inline; +} +.widget-header form.widget-title input.form-control { + width: auto; + display: inline-block; +} +.widget-content { + overflow: hidden; +} +.widget .widget-ew-resizer { + position: absolute; + width: 5px; + right: -2px; + height: 100%; + top: 0; + cursor: ew-resize; +} +.widget .widget-s-resizer { + cursor: ns-resize; + height: 5px; + width: 100%; + bottom: -7px; + left: 0; +} +.widget .widget-resizer-marquee { + box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.5); + position: absolute; + top: 0; + left: 0; + z-index: 2; +} +.remove-layout-icon { + vertical-align: text-top; + cursor: pointer; + opacity: 0.3; +} +.remove-layout-icon:hover { + opacity: 1; +} +.layout-title { + display: inline-block; +} diff --git a/dist/angular-ui-dashboard.js b/dist/malhar-angular-dashboard.js similarity index 80% rename from dist/angular-ui-dashboard.js rename to dist/malhar-angular-dashboard.js index 9091c13..a044ac7 100644 --- a/dist/angular-ui-dashboard.js +++ b/dist/malhar-angular-dashboard.js @@ -22,7 +22,7 @@ angular.module('ui.dashboard') return { restrict: 'A', templateUrl: function(element, attr) { - return attr.templateUrl ? attr.templateUrl : 'template/dashboard.html'; + return attr.templateUrl ? attr.templateUrl : 'components/directives/dashboard/dashboard.html'; }, scope: true, @@ -33,7 +33,7 @@ angular.module('ui.dashboard') hideWidgetSettings: false, hideWidgetClose: false, settingsModalOptions: { - templateUrl: 'template/widget-settings-template.html', + templateUrl: 'components/directives/dashboard/widget-settings-template.html', controller: 'WidgetSettingsCtrl' }, onSettingsClose: function(result, widget) { // NOTE: dashboard scope is also passed as 3rd argument @@ -301,153 +301,11 @@ angular.module('ui.dashboard') }; }]); -/* - * 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) { - if (layout.locked) { - return; - } - - 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); - } - }; - - var sortableDefaults = { - stop: function() { - scope.options.saveLayouts(); - }, - }; - scope.sortableOptions = angular.extend({}, sortableDefaults, scope.options.sortableOptions || {}); - } - }; - } - ]); +angular.module("ui.dashboard").run(["$templateCache", function($templateCache) {$templateCache.put("components/directives/dashboard/altDashboard.html","
\n
\n
\n \n \n \n \n
\n\n
\n \n
\n\n \n\n \n\n \n
\n\n
\n
\n
\n
\n

\n {{widget.title}}\n
\n \n
\n {{widget.name}}\n \n \n

\n
\n
\n
\n
\n
\n
\n
\n"); +$templateCache.put("components/directives/dashboard/dashboard.html","
\n
\n
\n \n \n \n \n
\n
\n \n
\n\n \n\n \n\n \n
\n\n
\n
\n
\n
\n

\n {{widget.title}}\n
\n \n
\n {{widget.name}}\n \n \n

\n
\n
\n
\n
\n
\n
\n
\n
"); +$templateCache.put("components/directives/dashboard/widget-settings-template.html","
\n \n

Widget Options {{widget.title}}

\n
\n\n
\n
\n
\n \n
\n \n
\n
\n
\n
\n
\n\n
\n \n \n
"); +$templateCache.put("components/directives/dashboardLayouts/SaveChangesModal.html","
\n \n

Unsaved Changes to \"{{layout.title}}\"

\n
\n\n
\n

You have {{layout.dashboard.unsavedChangeCount}} unsaved changes on this dashboard. Would you like to save them?

\n
\n\n
\n \n \n
"); +$templateCache.put("components/directives/dashboardLayouts/dashboardLayouts.html","\n
");}]); /* * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved. * @@ -532,240 +390,224 @@ angular.module('ui.dashboard') 'use strict'; angular.module('ui.dashboard') - .factory('LayoutStorage', function() { + .controller('DashboardWidgetCtrl', ['$scope', '$element', '$compile', '$window', '$timeout', + function($scope, $element, $compile, $window, $timeout) { - var noopStorage = { - setItem: function() { + $scope.status = { + isopen: false + }; - }, - getItem: function() { + // Fills "container" with compiled view + $scope.makeTemplateString = function() { - }, - removeItem: function() { + var widget = $scope.widget; - } - }; + // First, build template string + var templateString = ''; - + if (widget.templateUrl) { - function LayoutStorage(options) { + // Use ng-include for templateUrl + templateString = '
'; - var defaults = { - storage: noopStorage, - storageHash: '', - stringifyStorage: true - }; + } else if (widget.template) { - angular.extend(defaults, options); - angular.extend(options, defaults); + // Direct string template + templateString = widget.template; - 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.lockDefaultLayouts = options.lockDefaultLayouts; - 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; + } else { - this.layouts = []; - this.states = {}; - this.load(); - this._ensureActiveLayout(); - } + // Assume attribute directive + templateString = '
= 0) { - this.layouts.splice(index, 1); - delete this.states[layout.id]; + // First check directive name attr + if (widget.attrs[widget.directive]) { + templateString += '="' + widget.attrs[widget.directive] + '"'; + } - // check for active - if (layout.active && this.layouts.length) { - var nextActive = index > 0 ? index - 1 : 0; - this.layouts[nextActive].active = true; + // Add attributes + _.each(widget.attrs, function(value, attr) { + + // make sure we aren't reusing directive attr + if (attr !== widget.directive) { + templateString += ' ' + attr + '="' + value + '"'; + } + + }); } + templateString += '>
'; } - }, + return templateString; + }; - save: function() { + $scope.grabResizer = function(e) { - var state = { - layouts: this._serializeLayouts(), - states: this.states, - storageHash: this.storageHash - }; + var widget = $scope.widget; + var widgetElm = $element.find('.widget'); - if (this.stringifyStorage) { - state = JSON.stringify(state); + // ignore middle- and right-click + if (e.which !== 1) { + return; } - this.storage.setItem(this.id, state); - this.options.unsavedChangeCount = 0; - }, + e.stopPropagation(); + e.originalEvent.preventDefault(); - load: function() { + // get the starting horizontal position + var initX = e.clientX; + // console.log('initX', initX); - var serialized = this.storage.getItem(this.id); + // Get the current width of the widget and dashboard + var pixelWidth = widgetElm.width(); + var pixelHeight = widgetElm.height(); + var widgetStyleWidth = widget.containerStyle.width; + var widthUnits = widget.widthUnits; + var unitWidth = parseFloat(widgetStyleWidth); - this.clear(); + // create marquee element for resize action + var $marquee = angular.element('
'); + widgetElm.append($marquee); - if (serialized) { - // check for promise - if (angular.isObject(serialized) && angular.isFunction(serialized.then)) { - this._handleAsyncLoad(serialized); - } else { - this._handleSyncLoad(serialized); - } - } else { - this._addDefaultLayouts(); - } - }, + // determine the unit/pixel ratio + var transformMultiplier = unitWidth / pixelWidth; - clear: function() { - this.layouts = []; - this.states = {}; - }, + // 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'); + }; - setItem: function(id, value) { - this.states[id] = value; - this.save(); - }, + // sets new widget width on mouseup + var mouseup = function(e) { + // remove listener and marquee + jQuery($window).off('mousemove', mousemove); + $marquee.remove(); - getItem: function(id) { - return this.states[id]; - }, + // calculate change in units + var curX = e.clientX; + var pixelChange = curX - initX; + var unitChange = Math.round(pixelChange * transformMultiplier * 100) / 100; - removeItem: function(id) { - delete this.states[id]; - this.save(); - }, + // add to initial unit width + var newWidth = unitWidth * 1 + unitChange; + widget.setWidth(newWidth, widthUnits); + $scope.$emit('widgetChanged', widget); + $scope.$apply(); + $scope.$broadcast('widgetResized', { + width: newWidth + }); + }; - getActiveLayout: function() { - var len = this.layouts.length; - for (var i = 0; i < len; i++) { - var layout = this.layouts[i]; - if (layout.active) { - return layout; - } + jQuery($window).on('mousemove', mousemove).one('mouseup', mouseup); + }; + + //TODO refactor + $scope.grabSouthResizer = function(e) { + var widgetElm = $element.find('.widget'); + + // ignore middle- and right-click + if (e.which !== 1) { + return; } - return false; - }, - _addDefaultLayouts: function() { - var self = this; - var defaults = this.lockDefaultLayouts ? { locked: true } : {}; - angular.forEach(this.defaultLayouts, function(layout) { - self.add(angular.extend(_.clone(defaults), layout)); - }); - }, + e.stopPropagation(); + e.originalEvent.preventDefault(); - _serializeLayouts: function() { - var result = []; - angular.forEach(this.layouts, function(l) { - result.push({ - title: l.title, - id: l.id, - active: l.active, - locked: l.locked, - defaultWidgets: l.dashboard.defaultWidgets - }); - }); - return result; - }, + // get the starting horizontal position + var initY = e.clientY; + // console.log('initX', initX); - _handleSyncLoad: function(serialized) { - - var deserialized; + // Get the current width of the widget and dashboard + var pixelWidth = widgetElm.width(); + var pixelHeight = widgetElm.height(); - if (this.stringifyStorage) { - try { + // create marquee element for resize action + var $marquee = angular.element('
'); + widgetElm.append($marquee); - deserialized = JSON.parse(serialized); + // updates marquee with preview of new height + var mousemove = function(e) { + var curY = e.clientY; + var pixelChange = curY - initY; + var newHeight = pixelHeight + pixelChange; + $marquee.css('height', newHeight + 'px'); + }; - } catch (e) { - this._addDefaultLayouts(); - return; - } - } else { + // sets new widget width on mouseup + var mouseup = function(e) { + // remove listener and marquee + jQuery($window).off('mousemove', mousemove); + $marquee.remove(); - deserialized = serialized; + // calculate height change + var curY = e.clientY; + var pixelChange = curY - initY; - } + //var widgetContainer = widgetElm.parent(); // widget container responsible for holding widget width and height + var widgetContainer = widgetElm.find('.widget-content'); - if (this.storageHash !== deserialized.storageHash) { - this._addDefaultLayouts(); - return; - } - this.states = deserialized.states; - this.add(deserialized.layouts); - }, + var diff = pixelChange; + var height = parseInt(widgetContainer.css('height'), 10); + var newHeight = (height + diff); - _handleAsyncLoad: function(promise) { - var self = this; - promise.then( - angular.bind(self, this._handleSyncLoad), - angular.bind(self, this._addDefaultLayouts) - ); - }, + //$scope.widget.style.height = newHeight + 'px'; - _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; - } - }, + $scope.widget.setHeight(newHeight + 'px'); - _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; - } + $scope.$emit('widgetChanged', $scope.widget); + $scope.$apply(); // make AngularJS to apply style changes - }; - return LayoutStorage; - }); + $scope.$broadcast('widgetResized', { + height: newHeight + }); + }; + + 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. * @@ -785,177 +627,135 @@ angular.module('ui.dashboard') '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, - size: widget.size, - 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 (angular.isObject(serialized) && angular.isFunction(serialized.then)) { - 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; - } + .directive('dashboardLayouts', ['LayoutStorage', '$timeout', '$modal', + function(LayoutStorage, $timeout, $modal) { + return { + scope: true, + templateUrl: function(element, attr) { + return attr.templateUrl ? attr.templateUrl : 'components/directives/dashboardLayouts/dashboardLayouts.html'; + }, + link: function(scope, element, attrs) { - if (this.stringify) { - try { // to deserialize the string + scope.options = scope.$eval(attrs.dashboardLayouts); - deserialized = JSON.parse(serialized); + var layoutStorage = new LayoutStorage(scope.options); - } catch (e) { + scope.layouts = layoutStorage.layouts; - // bad JSON, log a warning and return - $log.warn('Serialized dashboard state was malformed and could not be parsed: ', serialized); - return null; + scope.createNewLayout = function() { + var newLayout = { + title: 'Custom', + defaultWidgets: scope.options.defaultWidgets || [] + }; + layoutStorage.add(newLayout); + scope.makeLayoutActive(newLayout); + layoutStorage.save(); + return newLayout; + }; - } - } - else { - deserialized = serialized; - } + scope.removeLayout = function(layout) { + layoutStorage.remove(layout); + layoutStorage.save(); + }; - // check hash against current hash - if (deserialized.hash !== this.hash) { + scope.makeLayoutActive = function(layout) { - $log.info('Serialized dashboard from storage was stale (old hash: ' + deserialized.hash + ', new hash: ' + this.hash + ')'); - this.storage.removeItem(this.id); - return null; + var current = layoutStorage.getActiveLayout(); - } + if (current && current.dashboard.unsavedChangeCount) { + var modalInstance = $modal.open({ + templateUrl: 'template/SaveChangesModal.html', + resolve: { + layout: function() { + return layout; + } + }, + controller: 'SaveChangesModalCtrl' + }); - // Cache widgets - var savedWidgetDefs = deserialized.widgets; + // 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); + } - // instantiate widgets from stored data - for (var i = 0; i < savedWidgetDefs.length; i++) { + }; - // deserialized object - var savedWidgetDef = savedWidgetDefs[i]; + scope._makeLayoutActive = function(layout) { + angular.forEach(scope.layouts, function(l) { + if (l !== layout) { + l.active = false; + } else { + l.active = true; + } + }); + layoutStorage.save(); + }; - // widget definition to use - var widgetDefinition = this.widgetDefinitions.getByName(savedWidgetDef.name); + scope.isActive = function(layout) { + return !!layout.active; + }; - // 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; - } + scope.editTitle = function(layout) { + if (layout.locked) { + return; + } - // 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; - } + var input = element.find('input[data-layout="' + layout.id + '"]'); + layout.editingTitle = true; - // push instantiated widget to result array - result.push(savedWidgetDef); - } + $timeout(function() { + input.focus()[0].setSelectionRange(0, 9999); + }); + }; - return result; - }, + // saves whatever is in the title input as the new title + scope.saveTitleEdit = function(layout) { + layout.editingTitle = false; + layoutStorage.save(); + }; - _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); + scope.options.saveLayouts = function() { + layoutStorage.save(true); + }; + scope.options.addWidget = function() { + var layout = layoutStorage.getActiveLayout(); + if (layout) { + layout.dashboard.addWidget.apply(layout.dashboard, arguments); } - }, - // failure - function(res) { - deferred.reject(res); - } - ); - - return deferred.promise; - } + }; + 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); + } + }; - }; - return DashboardState; - }]); + var sortableDefaults = { + stop: function() { + scope.options.saveLayouts(); + }, + distance: 5 + }; + scope.sortableOptions = angular.extend({}, sortableDefaults, scope.options.sortableOptions || {}); + } + }; + } + ]); /* * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved. * @@ -975,32 +775,19 @@ angular.module('ui.dashboard') '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 - }, + .controller('SaveChangesModalCtrl', ['$scope', '$modalInstance', 'layout', function ($scope, $modalInstance, layout) { + + // add layout to scope + $scope.layout = layout; - destroy: function () { - // to be overridden by subclasses - } + $scope.ok = function () { + $modalInstance.close(); }; - return WidgetDataModel; - }); + $scope.cancel = function () { + $modalInstance.dismiss(); + }; + }]); /* * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved. * @@ -1020,26 +807,21 @@ angular.module('ui.dashboard') '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; - } + .controller('WidgetSettingsCtrl', ['$scope', '$modalInstance', 'widget', function ($scope, $modalInstance, widget) { + // add widget to scope + $scope.widget = widget; - WidgetDefCollection.prototype = Object.create(Array.prototype); + // set up result object + $scope.result = jQuery.extend(true, {}, widget); - WidgetDefCollection.prototype.getByName = function (name) { - return this.map[name]; + $scope.ok = function () { + $modalInstance.close($scope.result); }; - return WidgetDefCollection; - }); + $scope.cancel = function () { + $modalInstance.dismiss('cancel'); + }; + }]); /* * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved. * @@ -1059,7 +841,7 @@ angular.module('ui.dashboard') 'use strict'; angular.module('ui.dashboard') - .factory('WidgetModel', function ($log) { + .factory('WidgetModel', ["$log", function ($log) { // constructor for widget model instances function WidgetModel(Class, overrides) { var defaults = { @@ -1153,7 +935,7 @@ angular.module('ui.dashboard') }; return WidgetModel; - }); + }]); /* * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved. * @@ -1173,19 +955,43 @@ angular.module('ui.dashboard') 'use strict'; angular.module('ui.dashboard') - .controller('SaveChangesModalCtrl', ['$scope', '$modalInstance', 'layout', function ($scope, $modalInstance, layout) { - - // add layout to scope - $scope.layout = layout; + .factory('WidgetDefCollection', function () { - $scope.ok = function () { - $modalInstance.close(); - }; + function convertToDefinition(d) { + if (typeof d === 'function') { + return new d(); + } + return d; + } - $scope.cancel = function () { - $modalInstance.dismiss(); + function WidgetDefCollection(widgetDefs) { + + widgetDefs = widgetDefs.map(convertToDefinition); + + 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]; }; - }]); + + WidgetDefCollection.prototype.add = function(def) { + def = convertToDefinition(def); + this.push(def); + this.map[def.name] = def; + } + + return WidgetDefCollection; + }); /* * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved. * @@ -1205,224 +1011,285 @@ angular.module('ui.dashboard') 'use strict'; angular.module('ui.dashboard') - .controller('DashboardWidgetCtrl', ['$scope', '$element', '$compile', '$window', '$timeout', - function($scope, $element, $compile, $window, $timeout) { + .factory('WidgetDataModel', function () { + function WidgetDataModel() { + } - $scope.status = { - isopen: false - }; + WidgetDataModel.prototype = { + setup: function (widget, scope) { + this.dataAttrName = widget.dataAttrName; + this.dataModelOptions = widget.dataModelOptions; + this.widgetScope = scope; + }, - // Fills "container" with compiled view - $scope.makeTemplateString = function() { + updateScope: function (data) { + this.widgetScope.widgetData = data; + }, - var widget = $scope.widget; + init: function () { + // to be overridden by subclasses + }, - // First, build template string - var templateString = ''; + destroy: function () { + // to be overridden by subclasses + } + }; - if (widget.templateUrl) { + 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 ng-include for templateUrl - templateString = '
'; +'use strict'; - } else if (widget.template) { +angular.module('ui.dashboard') + .factory('LayoutStorage', function() { - // Direct string template - templateString = widget.template; + var noopStorage = { + setItem: function() { - } else { + }, + getItem: function() { - // Assume attribute directive - templateString = '
= 0) { + this.layouts.splice(index, 1); + delete this.states[layout.id]; - // Get the current width of the widget and dashboard - var pixelWidth = widgetElm.width(); - var pixelHeight = widgetElm.height(); - var widgetStyleWidth = widget.containerStyle.width; - var widthUnits = widget.widthUnits; - var unitWidth = parseFloat(widgetStyleWidth); - - // create marquee element for resize action - var $marquee = angular.element('
'); - widgetElm.append($marquee); + // check for active + if (layout.active && this.layouts.length) { + var nextActive = index > 0 ? index - 1 : 0; + this.layouts[nextActive].active = true; + } + } + }, - // determine the unit/pixel ratio - var transformMultiplier = unitWidth / pixelWidth; + save: function() { - // 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'); + var state = { + layouts: this._serializeLayouts(), + states: this.states, + storageHash: this.storageHash }; - // sets new widget width on mouseup - var mouseup = function(e) { - // remove listener and marquee - jQuery($window).off('mousemove', mousemove); - $marquee.remove(); + if (this.stringifyStorage) { + state = JSON.stringify(state); + } - // calculate change in units - var curX = e.clientX; - var pixelChange = curX - initX; - var unitChange = Math.round(pixelChange * transformMultiplier * 100) / 100; + this.storage.setItem(this.id, state); + this.options.unsavedChangeCount = 0; + }, - // add to initial unit width - var newWidth = unitWidth * 1 + unitChange; - widget.setWidth(newWidth, widthUnits); - $scope.$emit('widgetChanged', widget); - $scope.$apply(); - $scope.$broadcast('widgetResized', { - width: newWidth - }); - }; + load: function() { - jQuery($window).on('mousemove', mousemove).one('mouseup', mouseup); - }; + var serialized = this.storage.getItem(this.id); - //TODO refactor - $scope.grabSouthResizer = function(e) { - var widgetElm = $element.find('.widget'); + this.clear(); - // ignore middle- and right-click - if (e.which !== 1) { - return; + if (serialized) { + // check for promise + if (angular.isObject(serialized) && angular.isFunction(serialized.then)) { + this._handleAsyncLoad(serialized); + } else { + this._handleSyncLoad(serialized); + } + } else { + this._addDefaultLayouts(); } + }, - e.stopPropagation(); - e.originalEvent.preventDefault(); - - // get the starting horizontal position - var initY = e.clientY; - // console.log('initX', initX); + clear: function() { + this.layouts = []; + this.states = {}; + }, - // Get the current width of the widget and dashboard - var pixelWidth = widgetElm.width(); - var pixelHeight = widgetElm.height(); + setItem: function(id, value) { + this.states[id] = value; + this.save(); + }, - // create marquee element for resize action - var $marquee = angular.element('
'); - widgetElm.append($marquee); + getItem: function(id) { + return this.states[id]; + }, - // updates marquee with preview of new height - var mousemove = function(e) { - var curY = e.clientY; - var pixelChange = curY - initY; - var newHeight = pixelHeight + pixelChange; - $marquee.css('height', newHeight + 'px'); - }; + removeItem: function(id) { + delete this.states[id]; + this.save(); + }, - // sets new widget width on mouseup - var mouseup = function(e) { - // remove listener and marquee - jQuery($window).off('mousemove', mousemove); - $marquee.remove(); + 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; + }, - // calculate height change - var curY = e.clientY; - var pixelChange = curY - initY; + _addDefaultLayouts: function() { + var self = this; + var defaults = this.lockDefaultLayouts ? { locked: true } : {}; + angular.forEach(this.defaultLayouts, function(layout) { + self.add(angular.extend(_.clone(defaults), layout)); + }); + }, - //var widgetContainer = widgetElm.parent(); // widget container responsible for holding widget width and height - var widgetContainer = widgetElm.find('.widget-content'); + _serializeLayouts: function() { + var result = []; + angular.forEach(this.layouts, function(l) { + result.push({ + title: l.title, + id: l.id, + active: l.active, + locked: l.locked, + defaultWidgets: l.dashboard.defaultWidgets + }); + }); + return result; + }, - var diff = pixelChange; - var height = parseInt(widgetContainer.css('height'), 10); - var newHeight = (height + diff); + _handleSyncLoad: function(serialized) { + + var deserialized; - //$scope.widget.style.height = newHeight + 'px'; + if (this.stringifyStorage) { + try { - $scope.widget.setHeight(newHeight + 'px'); + deserialized = JSON.parse(serialized); - $scope.$emit('widgetChanged', $scope.widget); - $scope.$apply(); // make AngularJS to apply style changes + } catch (e) { + this._addDefaultLayouts(); + return; + } + } else { - $scope.$broadcast('widgetResized', { - height: newHeight - }); - }; + deserialized = serialized; - 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); - }); - }; + if (this.storageHash !== deserialized.storageHash) { + this._addDefaultLayouts(); + return; + } + this.states = deserialized.states; + this.add(deserialized.layouts); + }, - // saves whatever is in the title input as the new title - $scope.saveTitleEdit = function(widget) { - widget.editingTitle = false; - $scope.$emit('widgetChanged', widget); - }; + _handleAsyncLoad: function(promise) { + var self = this; + promise.then( + angular.bind(self, this._handleSyncLoad), + angular.bind(self, this._addDefaultLayouts) + ); + }, - $scope.compileTemplate = function() { - var container = $scope.findWidgetContainer($element); - var templateString = $scope.makeTemplateString(); - var widgetElement = angular.element(templateString); + _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; + } + }, - container.empty(); - container.append(widgetElement); - $compile(widgetElement)($scope); - }; + _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; + } - $scope.findWidgetContainer = function(element) { - // widget placeholder is the first (and only) child of .widget-content - return element.find('.widget-content'); - }; - } - ]); + }; + return LayoutStorage; + }); /* * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved. * @@ -1442,192 +1309,174 @@ angular.module('ui.dashboard') 'use strict'; angular.module('ui.dashboard') - .controller('WidgetSettingsCtrl', ['$scope', '$modalInstance', 'widget', function ($scope, $modalInstance, widget) { - // add widget to scope - $scope.widget = widget; + .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; + } - // set up result object - $scope.result = jQuery.extend(true, {}, widget); + 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; + } - $scope.ok = function () { - $modalInstance.close($scope.result); - }; + var serialized = _.map(widgets, function (widget) { + var widgetObject = { + title: widget.title, + name: widget.name, + style: widget.style, + size: widget.size, + 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 (angular.isObject(serialized) && angular.isFunction(serialized.then)) { + 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; + } - $scope.cancel = function () { - $modalInstance.dismiss('cancel'); }; - }]); -angular.module("ui.dashboard").run(["$templateCache", function($templateCache) { - - $templateCache.put("template/alt-dashboard.html", - "
\n" + - "
\n" + - "
\n" + - " \n" + - " \n" + - " \n" + - " \n" + - "
\n" + - "\n" + - "
\n" + - " \n" + - "
\n" + - "\n" + - " \n" + - "\n" + - " \n" + - "\n" + - " \n" + - "
\n" + - "\n" + - "
\n" + - "
\n" + - "
\n" + - "
\n" + - "

\n" + - " {{widget.title}}\n" + - "
\n" + - " \n" + - "
\n" + - " {{widget.name}}\n" + - " \n" + - " \n" + - "

\n" + - "
\n" + - "
\n" + - "
\n" + - "
\n" + - "
\n" + - "
\n" + - "
\n" - ); - - $templateCache.put("template/dashboard-layouts.html", - "\n" + - "
" - ); - - $templateCache.put("template/dashboard.html", - "
\n" + - "
\n" + - "
\n" + - " \n" + - " \n" + - " \n" + - " \n" + - "
\n" + - "
\n" + - " \n" + - "
\n" + - "\n" + - " \n" + - "\n" + - " \n" + - "\n" + - " \n" + - "
\n" + - "\n" + - "
\n" + - "
\n" + - "
\n" + - "
\n" + - "

\n" + - " {{widget.title}}\n" + - "
\n" + - " \n" + - "
\n" + - " {{widget.name}}\n" + - " \n" + - " \n" + - "

\n" + - "
\n" + - "
\n" + - "
\n" + - "
\n" + - "
\n" + - "
\n" + - "
\n" + - "
" - ); - - $templateCache.put("template/save-changes-modal.html", - "
\n" + - " \n" + - "

Unsaved Changes to \"{{layout.title}}\"

\n" + - "
\n" + - "\n" + - "
\n" + - "

You have {{layout.dashboard.unsavedChangeCount}} unsaved changes on this dashboard. Would you like to save them?

\n" + - "
\n" + - "\n" + - "
\n" + - " \n" + - " \n" + - "
" - ); - - $templateCache.put("template/widget-default-content.html", - "" - ); - - $templateCache.put("template/widget-settings-template.html", - "
\n" + - " \n" + - "

Widget Options {{widget.title}}

\n" + - "
\n" + - "\n" + - "
\n" + - "
\n" + - "
\n" + - " \n" + - "
\n" + - " \n" + - "
\n" + - "
\n" + - "
\n" + - "
\n" + - "
\n" + - "\n" + - "
\n" + - " \n" + - " \n" + - "
" - ); - -}]); + return DashboardState; + }]); \ No newline at end of file diff --git a/e2e/main.po.js b/e2e/main.po.js new file mode 100644 index 0000000..6b88871 --- /dev/null +++ b/e2e/main.po.js @@ -0,0 +1,15 @@ +/** + * This file uses the Page Object pattern to define the main page for tests + * https://docs.google.com/presentation/d/1B6manhG0zEXkC-H-tPo2vwU06JhL8w9-XCF9oehXzAQ + */ + +'use strict'; + +var MainPage = function() { + this.jumbEl = element(by.css('.jumbotron')); + this.h1El = this.jumbEl.element(by.css('h1')); + this.imgEl = this.jumbEl.element(by.css('img')); + this.thumbnailEls = element(by.css('body')).all(by.repeater('awesomeThing in awesomeThings')); +}; + +module.exports = new MainPage(); diff --git a/e2e/main.spec.js b/e2e/main.spec.js new file mode 100644 index 0000000..da89d22 --- /dev/null +++ b/e2e/main.spec.js @@ -0,0 +1,21 @@ +'use strict'; + +describe('The main view', function () { + var page; + + beforeEach(function () { + browser.get('http://localhost:3000/index.html'); + page = require('./main.po'); + }); + + it('should include jumbotron with correct data', function() { + expect(page.h1El.getText()).toBe('\'Allo, \'Allo!'); + expect(page.imgEl.getAttribute('src')).toMatch(/assets\/images\/yeoman.png$/); + expect(page.imgEl.getAttribute('alt')).toBe('I\'m Yeoman'); + }); + + it('list more than 5 awesome things', function () { + expect(page.thumbnailEls.count()).toBeGreaterThan(5); + }); + +}); diff --git a/favicon.ico b/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..03156aa4055978886a61f83080f9d97a403d4806 GIT binary patch literal 32988 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', + ignorePath: paths.tmp + '/partials', + addRootSlash: false + }; + + var htmlFilter = $.filter('*.html'); + var jsFilter = $.filter('**/*.js'); + var cssFilter = $.filter('**/*.css'); + var assets; + + return gulp.src(paths.tmp + '/serve/*.html') + .pipe($.inject(partialsInjectFile, partialsInjectOptions)) + .pipe(assets = $.useref.assets()) + .pipe($.rev()) + .pipe(jsFilter) + .pipe($.ngAnnotate()) + .pipe($.uglify({preserveComments: $.uglifySaveLicense})) + .pipe(jsFilter.restore()) + .pipe(cssFilter) + .pipe($.replace('../bootstrap/fonts', 'fonts')) + .pipe($.csso()) + .pipe(cssFilter.restore()) + .pipe(assets.restore()) + .pipe($.useref()) + .pipe($.revReplace()) + .pipe(htmlFilter) + .pipe($.minifyHtml({ + empty: true, + spare: true, + quotes: true + })) + .pipe(htmlFilter.restore()) + .pipe(gulp.dest(paths.demo + '/')) + .pipe($.size({ title: paths.demo + '/', showFiles: true })); +}); + +gulp.task('demo:images', function () { + return gulp.src(paths.src + '/assets/images/**/*') + .pipe(gulp.dest(paths.demo + '/assets/images/')); +}); + +gulp.task('demo:fonts', function () { + return gulp.src($.mainBowerFiles()) + .pipe($.filter('**/*.{eot,svg,ttf,woff}')) + .pipe($.flatten()) + .pipe(gulp.dest(paths.demo + '/fonts/')); +}); + +gulp.task('demo:misc', function () { + return gulp.src(paths.src + '/**/*.ico') + .pipe(gulp.dest(paths.demo + '/')); +}); + +gulp.task('demo:clean', function (done) { + $.del([paths.demo + '/', paths.tmp + '/'], done); +}); + +gulp.task('build:demo', ['demo:html', 'demo:images', 'demo:fonts', 'demo:misc']); diff --git a/gulp/build.js b/gulp/build.js new file mode 100644 index 0000000..dacb5f7 --- /dev/null +++ b/gulp/build.js @@ -0,0 +1,47 @@ +'use strict'; + +var gulp = require('gulp'); + +var paths = gulp.paths; + +var $ = require('gulp-load-plugins')({ + pattern: ['gulp-*', 'main-bower-files', 'uglify-save-license', 'del'] +}); + +gulp.task('partials', function () { + return gulp.src([ + paths.src + '/components/**/*.html' + ]) + .pipe($.angularTemplatecache('templateCacheHtml.js', { + module: 'ui.dashboard', + root: 'components' + })) + .pipe(gulp.dest(paths.tmp + '/partials/')); +}); + +gulp.task('clean', function (done) { + $.del([paths.dist + '/', paths.tmp + '/'], done); +}); + +gulp.task('build:js', ['partials'], function() { + return gulp.src([ + paths.src + '/components/**/!(*.spec|*_e2e)+(.js)', + paths.tmp + '/partials/templateCacheHtml.js' + ]) + .pipe($.angularFilesort()) + .pipe($.concat('malhar-angular-dashboard.js')) + .pipe($.ngAnnotate()) + .pipe(gulp.dest(paths.dist)) + +}); + +gulp.task('build:css', function() { + return gulp.src([ + paths.src + '/components/**/*.less' + ]) + .pipe($.concat('malhar-angular-dashboard.less')) + .pipe($.less()) + .pipe(gulp.dest(paths.dist)); +}); + +gulp.task('build', ['build:js', 'build:css']); diff --git a/gulp/e2e-tests.js b/gulp/e2e-tests.js new file mode 100644 index 0000000..99ab2c5 --- /dev/null +++ b/gulp/e2e-tests.js @@ -0,0 +1,35 @@ +'use strict'; + +var gulp = require('gulp'); + +var $ = require('gulp-load-plugins')(); + +var browserSync = require('browser-sync'); + +var paths = gulp.paths; + +// Downloads the selenium webdriver +gulp.task('webdriver-update', $.protractor.webdriver_update); + +gulp.task('webdriver-standalone', $.protractor.webdriver_standalone); + +function runProtractor (done) { + + gulp.src(paths.e2e + '/**/*.js') + .pipe($.protractor.protractor({ + configFile: 'protractor.conf.js', + })) + .on('error', function (err) { + // Make sure failed tests cause gulp to exit non-zero + throw err; + }) + .on('end', function () { + // Close browser sync server + browserSync.exit(); + done(); + }); +} + +gulp.task('protractor', ['protractor:src']); +gulp.task('protractor:src', ['serve:e2e', 'webdriver-update'], runProtractor); +gulp.task('protractor:dist', ['serve:e2e-dist', 'webdriver-update'], runProtractor); diff --git a/gulp/inject.js b/gulp/inject.js new file mode 100644 index 0000000..cc55cb7 --- /dev/null +++ b/gulp/inject.js @@ -0,0 +1,41 @@ +'use strict'; + +var gulp = require('gulp'); + +var paths = gulp.paths; + +var $ = require('gulp-load-plugins')(); + +var wiredep = require('wiredep').stream; + +gulp.task('inject', ['styles'], function () { + + var injectStyles = gulp.src([ + paths.tmp + '/serve/{app,components}/**/*.css', + '!' + paths.tmp + '/serve/app/vendor.css' + ], { read: false }); + + var injectScripts = gulp.src([ + paths.src + '/{app,components}/**/*.js', + '!' + paths.src + '/{app,components}/**/*.spec.js', + '!' + paths.src + '/{app,components}/**/*.mock.js' + ]).pipe($.angularFilesort()); + + var injectOptions = { + ignorePath: [paths.src, paths.tmp + '/serve'], + addRootSlash: false + }; + + var wiredepOptions = { + devDependencies: true, + directory: 'bower_components', + exclude: [/bootstrap\.css/, /bootstrap\.css/, /foundation\.css/] + }; + + return gulp.src(paths.src + '/*.html') + .pipe($.inject(injectStyles, injectOptions)) + .pipe($.inject(injectScripts, injectOptions)) + .pipe(wiredep(wiredepOptions)) + .pipe(gulp.dest(paths.tmp + '/serve')); + +}); diff --git a/gulp/proxy.js b/gulp/proxy.js new file mode 100644 index 0000000..2fcd734 --- /dev/null +++ b/gulp/proxy.js @@ -0,0 +1,65 @@ + /*jshint unused:false */ + +/*************** + + This file allow to configure a proxy system plugged into BrowserSync + in order to redirect backend requests while still serving and watching + files from the web project + + IMPORTANT: The proxy is disabled by default. + + If you want to enable it, watch at the configuration options and finally + change the `module.exports` at the end of the file + +***************/ + +'use strict'; + +var httpProxy = require('http-proxy'); +var chalk = require('chalk'); + +/* + * Location of your backend server + */ +var proxyTarget = 'http://server/context/'; + +var proxy = httpProxy.createProxyServer({ + target: proxyTarget +}); + +proxy.on('error', function(error, req, res) { + res.writeHead(500, { + 'Content-Type': 'text/plain' + }); + + console.error(chalk.red('[Proxy]'), error); +}); + +/* + * The proxy middleware is an Express middleware added to BrowserSync to + * handle backend request and proxy them to your backend. + */ +function proxyMiddleware(req, res, next) { + /* + * This test is the switch of each request to determine if the request is + * for a static file to be handled by BrowserSync or a backend request to proxy. + * + * The existing test is a standard check on the files extensions but it may fail + * for your needs. If you can, you could also check on a context in the url which + * may be more reliable but can't be generic. + */ + if (/\.(html|css|js|png|jpg|jpeg|gif|ico|xml|rss|txt|eot|svg|ttf|woff|cur)(\?((r|v|rel|rev)=[\-\.\w]*)?)?$/.test(req.url)) { + next(); + } else { + proxy.web(req, res); + } +} + +/* + * This is where you activate or not your proxy. + * + * The first line activate if and the second one ignored it + */ + +//module.exports = [proxyMiddleware]; +module.exports = []; diff --git a/gulp/server.js b/gulp/server.js new file mode 100644 index 0000000..e5cf4d8 --- /dev/null +++ b/gulp/server.js @@ -0,0 +1,59 @@ +'use strict'; + +var gulp = require('gulp'); + +var paths = gulp.paths; + +var util = require('util'); + +var browserSync = require('browser-sync'); + +var middleware = require('./proxy'); + +function browserSyncInit(baseDir, files, browser) { + browser = browser === undefined ? 'default' : browser; + + var routes = null; + if(baseDir === paths.src || (util.isArray(baseDir) && baseDir.indexOf(paths.src) !== -1)) { + routes = { + '/bower_components': 'bower_components' + }; + } + + browserSync.instance = browserSync.init(files, { + startPath: '/', + server: { + baseDir: baseDir, + middleware: middleware, + routes: routes + }, + browser: browser + }); +} + +gulp.task('serve', ['watch'], function () { + browserSyncInit([ + paths.tmp + '/serve', + paths.src, + paths.bower + '/bootstrap' + ], [ + paths.tmp + '/serve/{app,components}/**/*.css', + paths.src + '/{app,components}/**/*.js', + paths.src + 'src/assets/images/**/*', + paths.tmp + '/serve/*.html', + paths.tmp + '/serve/{app,components}/**/*.html', + paths.src + '/{app,components}/**/*.html' + ]); +}); + +gulp.task('serve:dist', ['build:demo'], function () { + browserSyncInit(paths.demo); +}); + +gulp.task('serve:e2e', ['inject'], function () { + browserSyncInit([paths.tmp + '/serve', paths.src], null, []); +}); + +gulp.task('serve:e2e-dist', ['build:demo'], function () { + browserSyncInit(paths.demo, null, []); +}); diff --git a/gulp/styles.js b/gulp/styles.js new file mode 100644 index 0000000..a033b26 --- /dev/null +++ b/gulp/styles.js @@ -0,0 +1,53 @@ +'use strict'; + +var gulp = require('gulp'); + +var paths = gulp.paths; + +var $ = require('gulp-load-plugins')(); + +gulp.task('styles', function () { + + var lessOptions = { + paths: [ + 'bower_components', + paths.src + '/app', + paths.src + '/components' + ] + }; + + var injectFiles = gulp.src([ + paths.src + '/{app,components}/**/*.less', + '!' + paths.src + '/app/index.less', + '!' + paths.src + '/app/vendor.less' + ], { read: false }); + + var injectOptions = { + transform: function(filePath) { + filePath = filePath.replace(paths.src + '/app/', ''); + filePath = filePath.replace(paths.src + '/components/', '../components/'); + return '@import \'' + filePath + '\';'; + }, + starttag: '// injector', + endtag: '// endinjector', + addRootSlash: false + }; + + var indexFilter = $.filter('index.less'); + + return gulp.src([ + paths.src + '/app/index.less', + paths.src + '/app/vendor.less' + ]) + .pipe(indexFilter) + .pipe($.inject(injectFiles, injectOptions)) + .pipe(indexFilter.restore()) + .pipe($.less()) + + .pipe($.autoprefixer()) + .on('error', function handleError(err) { + console.error(err.toString()); + this.emit('end'); + }) + .pipe(gulp.dest(paths.tmp + '/serve/app/')); +}); diff --git a/gulp/unit-tests.js b/gulp/unit-tests.js new file mode 100644 index 0000000..f13a9c0 --- /dev/null +++ b/gulp/unit-tests.js @@ -0,0 +1,35 @@ +'use strict'; + +var gulp = require('gulp'); + +var $ = require('gulp-load-plugins')(); + +var wiredep = require('wiredep'); + +var paths = gulp.paths; + +function runTests (singleRun, done) { + var bowerDeps = wiredep({ + directory: 'bower_components', + exclude: ['bootstrap-sass-official'], + dependencies: true, + devDependencies: true + }); + + var testFiles = bowerDeps.js.concat([ + paths.src + '/{app,components}/**/*.js' + ]); + + gulp.src(testFiles) + .pipe($.karma({ + configFile: 'karma.conf.js', + action: (singleRun)? 'run': 'watch' + })) + .on('error', function (err) { + // Make sure failed tests cause gulp to exit non-zero + throw err; + }); +} + +gulp.task('test', function (done) { runTests(true /* singleRun */, done) }); +gulp.task('test:auto', function (done) { runTests(false /* singleRun */, done) }); diff --git a/gulp/watch.js b/gulp/watch.js new file mode 100644 index 0000000..61c90a2 --- /dev/null +++ b/gulp/watch.js @@ -0,0 +1,14 @@ +'use strict'; + +var gulp = require('gulp'); + +var paths = gulp.paths; + +gulp.task('watch', ['inject'], function () { + gulp.watch([ + paths.src + '/*.html', + paths.src + '/{app,components}/**/*.less', + paths.src + '/{app,components}/**/*.js', + 'bower.json' + ], ['inject']); +}); diff --git a/gulpfile.js b/gulpfile.js index ff99059..a1449fa 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,48 +1,18 @@ -var gulp = require('gulp'); - -var clean = require('gulp-clean'); -var rimraf = require('gulp-rimraf'); -var minifyCss = require('gulp-minify-css'); -var uglify = require('gulp-uglify'); -var usemin = require('gulp-usemin'); +'use strict'; -var dev = { - dir: 'demo', - index: 'demo/index.html', - views: ['demo/view.html', 'demo/layouts.html'], - fonts: 'bower_components/bootstrap/fonts/*' -}; +var gulp = require('gulp'); -var prod = { - dir: 'demo_dist', - fonts: 'demo_dist/fonts' +gulp.paths = { + src: 'src', + dist: 'dist', + demo: 'demo', + tmp: '.tmp', + e2e: 'e2e', + bower: 'bower_components' }; -var options = { - clean: { read: false }, - uglify: { mangle: false } -}; +require('require-dir')('./gulp'); -gulp.task('clean', function() { - return gulp.src(prod.dir, options.clean) - .pipe(rimraf({ force: true })); +gulp.task('default', ['clean'], function () { + gulp.start('build'); }); - -gulp.task('copy', function() { - gulp.src(dev.fonts) - .pipe(gulp.dest(prod.fonts)); - - gulp.src(dev.views) - .pipe(gulp.dest(prod.dir)); -}); - -gulp.task('demo_dist', ['clean', 'copy'], function() { - gulp.src(dev.index) - .pipe(usemin({ - css: [minifyCss()], - js: [uglify(options.uglify)] - })) - .pipe(gulp.dest(prod.dir)); -}); - -gulp.task('serve', ['styles', 'server', 'watch']); \ No newline at end of file diff --git a/karma.conf.js b/karma.conf.js index 2e00b10..12e3ff3 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -1,71 +1,17 @@ -// Karma configuration -// http://karma-runner.github.io/0.10/config/configuration-file.html +'use strict'; module.exports = function(config) { - config.set({ - - preprocessors: { - // which files to show in coverage report - 'src/**/*.js': ['coverage'] - }, - - reporters: ['dots', 'coverage'], - - coverageReporter: { - type: 'html', - dir: 'coverage/' - }, - // base path, that will be used to resolve files and exclude - basePath: '', + config.set({ + autoWatch : false, - // testing framework to use (jasmine/mocha/qunit/...) frameworks: ['jasmine'], - // list of files / patterns to load in the browser - files: [ - 'bower_components/jquery/jquery.js', - 'bower_components/lodash/dist/lodash.js', - 'bower_components/angular/angular.js', - 'bower_components/angular-mocks/angular-mocks.js', - 'bower_components/angular-bootstrap/ui-bootstrap-tpls.js', - 'src/directives/dashboard.js', - 'src/directives/*.js', - 'src/models/*.js', - 'src/controllers/*.js', - 'template/*.js', - 'test/mock/**/*.js', - 'test/spec/**/*.js' - ], - - // list of files / patterns to exclude - exclude: [], - - // web server port - port: 8080, - - // level of logging - // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG - logLevel: config.LOG_INFO, - - - // enable / disable watching file and executing tests whenever any file changes - autoWatch: true, - - - // Start these browsers, currently available: - // - Chrome - // - ChromeCanary - // - Firefox - // - Opera - // - Safari (only Mac) - // - PhantomJS - // - IE (only Windows) - browsers: ['PhantomJS'], - + browsers : ['PhantomJS'], - // Continuous Integration mode - // if true, it capture browsers, run tests and exit - singleRun: false + plugins : [ + 'karma-phantomjs-launcher', + 'karma-jasmine' + ] }); }; diff --git a/package.json b/package.json index 6d3ebfb..a02debb 100644 --- a/package.json +++ b/package.json @@ -1,61 +1,53 @@ { "name": "malhar-angular-dashboard", - "version": "0.8.1", + "version": "0.8.2", "author": "https://github.com/DataTorrent/malhar-angular-dashboard/graphs/contributors", "homepage": "https://github.com/DataTorrent/malhar-angular-dashboard", "license": "Apache License, v2.0", "dependencies": {}, "devDependencies": { - "grunt": "~0.4.2", - "grunt-angular-templates": "~0.3.0", - "grunt-autoprefixer": "~0.2.0", - "grunt-concurrent": "~0.3.0", - "grunt-contrib-clean": "~0.5.0", - "grunt-contrib-coffee": "~0.7.0", - "grunt-contrib-compass": "~0.5.0", - "grunt-contrib-concat": "~0.3.0", - "grunt-contrib-connect": "~0.5.0", - "grunt-contrib-copy": "~0.4.1", - "grunt-contrib-cssmin": "~0.6.0", - "grunt-contrib-htmlmin": "~0.1.3", - "grunt-contrib-imagemin": "~0.2.0", - "grunt-contrib-jshint": "~0.6.0", - "grunt-contrib-uglify": "~0.2.0", - "grunt-contrib-watch": "~0.5.2", - "grunt-google-cdn": "~0.2.0", - "grunt-html2js": "~0.1.3", - "grunt-karma": "~0.8.3", - "grunt-rev": "~0.1.0", - "grunt-svgmin": "~0.2.0", - "grunt-usemin": "~0.1.11", - "karma": "~0.12.0", - "karma-chrome-launcher": "^0.1.3", - "karma-coffee-preprocessor": "~0.1.2", - "karma-coverage": "^0.2.1", - "karma-firefox-launcher": "~0.1.3", - "karma-html2js-preprocessor": "~0.1.0", - "karma-jasmine": "~0.2.2", - "karma-ng-html2js-preprocessor": "~0.1.0", - "karma-ng-scenario": "~0.1.0", - "karma-phantomjs-launcher": "~0.1.1", - "karma-requirejs": "~0.2.1", - "karma-script-launcher": "~0.1.0", - "load-grunt-tasks": "~0.1.0", - "requirejs": "~2.1.9", - "time-grunt": "~0.1.0", - "gulp-usemin": "^0.3.7", - "gulp": "^3.8.6", - "gulp-uglify": "^0.3.1", - "gulp-minify-html": "^0.1.4", - "gulp-minify-css": "^0.3.7", - "gulp-rev": "^1.0.0", - "gulp-htmlmin": "^0.1.3", - "gulp-rimraf": "^0.1.0" + "browser-sync": "~1.7.1", + "chalk": "~0.5.1", + "del": "~0.1.3", + "gulp": "~3.8.10", + "gulp-angular-filesort": "~1.0.4", + "gulp-angular-templatecache": "~1.4.2", + "gulp-autoprefixer": "~2.0.0", + "gulp-concat": "^2.4.3", + "gulp-consolidate": "~0.1.2", + "gulp-csso": "~0.2.9", + "gulp-filter": "~1.0.2", + "gulp-flatten": "~0.0.4", + "gulp-inject": "~1.0.2", + "gulp-jshint": "~1.9.0", + "gulp-karma": "~0.0.4", + "gulp-less": "~1.3.6", + "gulp-load-plugins": "~0.7.1", + "gulp-minify-html": "~0.1.7", + "gulp-ng-annotate": "~0.3.6", + "gulp-protractor": "~0.0.11", + "gulp-rename": "~1.2.0", + "gulp-replace": "~0.5.0", + "gulp-rev": "~2.0.1", + "gulp-rev-replace": "~0.3.1", + "gulp-size": "~1.1.0", + "gulp-uglify": "~1.0.1", + "gulp-useref": "~1.0.2", + "http-proxy": "~1.7.0", + "jshint-stylish": "~1.0.0", + "karma-jasmine": "~0.3.1", + "karma-phantomjs-launcher": "~0.1.4", + "main-bower-files": "~2.4.0", + "protractor": "~1.4.0", + "require-dir": "~0.1.0", + "uglify-save-license": "~0.4.1", + "wiredep": "~2.2.0" }, "engines": { - "node": ">=0.8.0" + "node": ">=0.10.0" }, - "scripts": { - "test": "grunt test" - } + "main": [ + "dist/malhar-angular-dashboard.css", + "dist/malhar-angular-dashboard.js" + ] } diff --git a/protractor.conf.js b/protractor.conf.js new file mode 100644 index 0000000..0f43a9e --- /dev/null +++ b/protractor.conf.js @@ -0,0 +1,25 @@ +'use strict'; + +var paths = require('./.yo-rc.json')['generator-gulp-angular'].props.paths; + +// An example configuration file. +exports.config = { + // The address of a running selenium server. + //seleniumAddress: 'http://localhost:4444/wd/hub', + //seleniumServerJar: deprecated, this should be set on node_modules/protractor/config.json + + // Capabilities to be passed to the webdriver instance. + capabilities: { + 'browserName': 'chrome' + }, + + // Spec patterns are relative to the current working directly when + // protractor is called. + specs: [paths.e2e + '/**/*.js'], + + // Options to be passed to Jasmine-node. + jasmineNodeOpts: { + showColors: true, + defaultTimeoutInterval: 30000 + } +}; diff --git a/src/404.html b/src/404.html new file mode 100644 index 0000000..fdace4a --- /dev/null +++ b/src/404.html @@ -0,0 +1,157 @@ + + + + + Page Not Found :( + + + +
+

Not found :(

+

Sorry, but the page you were trying to view does not exist.

+

It looks like this was the result of either:

+
    +
  • a mistyped address
  • +
  • an out-of-date link
  • +
+ + +
+ + diff --git a/src/angular-ui-dashboard.css b/src/angular-ui-dashboard.css deleted file mode 100644 index 6b5b717..0000000 --- a/src/angular-ui-dashboard.css +++ /dev/null @@ -1,88 +0,0 @@ -.dashboard-widget-area { - margin: 10px 0 30px; - min-height: 200px; -} - -.widget-container { - float:left; - display: inline-block; - width: 33%; - padding-bottom: 1em; -} - -.widget { - margin: 0 1em 0 0; - background-color: white; - border: 2px solid #444; - border-radius: 5px; - position: relative; - height: 100%; -} -.widget-header { - overflow: hidden; -} -.widget-header .label { - display: inline-block; - vertical-align: middle; -} -.widget-header .glyphicon { - cursor: pointer; - float: right; - opacity: 0.5; - margin-left: 5px; -} -.widget-header .glyphicon:hover { - opacity: 1; -} -.widget-header .widget-title { - vertical-align: middle; -} -.widget-header form.widget-title { - display: inline; -} - -.widget-header form.widget-title input.form-control { - width: auto; - display: inline-block; -} - -.widget-content { - overflow: hidden; -} - -.widget .widget-ew-resizer { - position: absolute; - width: 5px; - right: -2px; - height:100%; - top:0; - cursor: ew-resize; -} - -.widget .widget-s-resizer { - cursor: ns-resize; - height: 5px; - width: 100%; - bottom: -7px; - left: 0; -} - -.widget .widget-resizer-marquee { - box-shadow: inset 0 0 0 1px rgba(0,0,0,0.5); - position: absolute; - top: 0; - left: 0; - z-index: 2; -} - -.remove-layout-icon { - vertical-align: text-top; - cursor: pointer; - opacity: 0.3; -} -.remove-layout-icon:hover { - opacity: 1; -} -.layout-title { - display: inline-block; -} \ No newline at end of file diff --git a/demo/scripts/customWidgetSettings.js b/src/app/customWidgetSettings.js similarity index 100% rename from demo/scripts/customWidgetSettings.js rename to src/app/customWidgetSettings.js diff --git a/demo/scripts/dataModel.js b/src/app/dataModel.js similarity index 100% rename from demo/scripts/dataModel.js rename to src/app/dataModel.js diff --git a/demo/scripts/demo.js b/src/app/demo.js similarity index 92% rename from demo/scripts/demo.js rename to src/app/demo.js index 7784ed0..8d6978e 100644 --- a/demo/scripts/demo.js +++ b/src/app/demo.js @@ -24,19 +24,19 @@ angular.module('app', [ .config(function ($routeProvider) { $routeProvider .when('/', { - templateUrl: 'view.html', + templateUrl: 'app/template/view.html', controller: 'DemoCtrl', title: 'simple', description: 'This is the simplest demo.' }) .when('/resize', { - templateUrl: 'view.html', + templateUrl: 'app/template/view.html', controller: 'ResizeDemoCtrl', title: 'resize', description: 'This demo showcases widget resizing.' }) .when('/custom-settings', { - templateUrl: 'view.html', + templateUrl: 'app/template/view.html', controller: 'CustomSettingsDemoCtrl', title: 'custom widget settings', description: 'This demo showcases overriding the widget settings dialog/modal ' + @@ -45,7 +45,7 @@ angular.module('app', [ 'that controls RandomDataModel.' }) .when('/explicit-saving', { - templateUrl: 'view.html', + templateUrl: 'app/template/view.html', controller: 'ExplicitSaveDemoCtrl', title: 'explicit saving', description: 'This demo showcases an option to only save the dashboard state '+ @@ -53,7 +53,7 @@ angular.module('app', [ 'updates as you make saveable changes.' }) .when('/layouts', { - templateUrl: 'layouts.html', + templateUrl: 'app/template/layouts.html', controller: 'LayoutsDemoCtrl', title: 'dashboard layouts', description: 'This demo showcases the ability to have "dashboard layouts", ' + @@ -61,7 +61,7 @@ angular.module('app', [ 'information, take a look at [issue #31](https://github.com/DataTorrent/malhar-angular-dashboard/issues/31)' }) .when('/layouts/explicit-saving', { - templateUrl: 'layouts.html', + templateUrl: 'app/template/layouts.html', controller: 'LayoutsDemoExplicitSaveCtrl', title: 'layouts explicit saving', description: 'This demo showcases dashboard layouts with explicit saving enabled.' @@ -94,7 +94,7 @@ angular.module('app', [ }, { name: 'resizable', - templateUrl: 'template/resizable.html', + templateUrl: 'app/template/resizable.html', attrs: { class: 'demo-widget-resizable' } diff --git a/demo/demo.css b/src/app/demo.less similarity index 100% rename from demo/demo.css rename to src/app/demo.less diff --git a/demo/scripts/directives.js b/src/app/directives.js similarity index 100% rename from demo/scripts/directives.js rename to src/app/directives.js diff --git a/demo/scripts/explicitSave.js b/src/app/explicitSave.js similarity index 100% rename from demo/scripts/explicitSave.js rename to src/app/explicitSave.js diff --git a/src/app/index.js b/src/app/index.js new file mode 100644 index 0000000..b4cfb97 --- /dev/null +++ b/src/app/index.js @@ -0,0 +1,3 @@ +'use strict'; + +angular.module('dashboard', ['ui.bootstrap']); diff --git a/src/app/index.less b/src/app/index.less new file mode 100644 index 0000000..87955cb --- /dev/null +++ b/src/app/index.less @@ -0,0 +1,16 @@ +.browsehappy { + margin: 0.2em 0; + background: #ccc; + color: #000; + padding: 0.2em 0; +} + +.thumbnail { + height: 200px; + + img.pull-right { + width: 50px; + } +} +// injector +// endinjector diff --git a/demo/scripts/layouts.js b/src/app/layouts.js similarity index 100% rename from demo/scripts/layouts.js rename to src/app/layouts.js diff --git a/demo/scripts/resize.js b/src/app/resize.js similarity index 100% rename from demo/scripts/resize.js rename to src/app/resize.js diff --git a/demo/template/configurableWidgetModalOptions.html b/src/app/template/configurableWidgetModalOptions.html similarity index 100% rename from demo/template/configurableWidgetModalOptions.html rename to src/app/template/configurableWidgetModalOptions.html diff --git a/demo/template/customSettingsTemplate.html b/src/app/template/customSettingsTemplate.html similarity index 100% rename from demo/template/customSettingsTemplate.html rename to src/app/template/customSettingsTemplate.html diff --git a/demo/template/fluid.html b/src/app/template/fluid.html similarity index 100% rename from demo/template/fluid.html rename to src/app/template/fluid.html diff --git a/demo/layouts.html b/src/app/template/layouts.html similarity index 100% rename from demo/layouts.html rename to src/app/template/layouts.html diff --git a/demo/template/resizable.html b/src/app/template/resizable.html similarity index 100% rename from demo/template/resizable.html rename to src/app/template/resizable.html diff --git a/demo/view.html b/src/app/template/view.html similarity index 100% rename from demo/view.html rename to src/app/template/view.html diff --git a/demo/template/widgetSpecificSettings.html b/src/app/template/widgetSpecificSettings.html similarity index 100% rename from demo/template/widgetSpecificSettings.html rename to src/app/template/widgetSpecificSettings.html diff --git a/src/app/vendor.less b/src/app/vendor.less new file mode 100644 index 0000000..2d0f5cc --- /dev/null +++ b/src/app/vendor.less @@ -0,0 +1,3 @@ +@import '../../bower_components/bootstrap/less/bootstrap.less'; + +@icon-font-path: '/fonts/'; diff --git a/src/controllers/widgetSettingsCtrl.js b/src/components/directives/dashboard/WidgetSettingsCtrl.js similarity index 100% rename from src/controllers/widgetSettingsCtrl.js rename to src/components/directives/dashboard/WidgetSettingsCtrl.js diff --git a/template/alt-dashboard.html b/src/components/directives/dashboard/altDashboard.html similarity index 100% rename from template/alt-dashboard.html rename to src/components/directives/dashboard/altDashboard.html diff --git a/template/dashboard.html b/src/components/directives/dashboard/dashboard.html similarity index 98% rename from template/dashboard.html rename to src/components/directives/dashboard/dashboard.html index d899443..dc0b665 100644 --- a/template/dashboard.html +++ b/src/components/directives/dashboard/dashboard.html @@ -2,7 +2,7 @@
- -
\ No newline at end of file +
\ No newline at end of file diff --git a/src/directives/dashboard-layouts.js b/src/components/directives/dashboardLayouts/dashboardLayouts.js similarity index 95% rename from src/directives/dashboard-layouts.js rename to src/components/directives/dashboardLayouts/dashboardLayouts.js index 28a9420..3477d54 100644 --- a/src/directives/dashboard-layouts.js +++ b/src/components/directives/dashboardLayouts/dashboardLayouts.js @@ -22,7 +22,7 @@ angular.module('ui.dashboard') return { scope: true, templateUrl: function(element, attr) { - return attr.templateUrl ? attr.templateUrl : 'template/dashboard-layouts.html'; + return attr.templateUrl ? attr.templateUrl : 'components/directives/dashboardLayouts/dashboardLayouts.html'; }, link: function(scope, element, attrs) { @@ -54,7 +54,7 @@ angular.module('ui.dashboard') if (current && current.dashboard.unsavedChangeCount) { var modalInstance = $modal.open({ - templateUrl: 'template/save-changes-modal.html', + templateUrl: 'template/SaveChangesModal.html', resolve: { layout: function() { return layout; @@ -139,6 +139,7 @@ angular.module('ui.dashboard') stop: function() { scope.options.saveLayouts(); }, + distance: 5 }; scope.sortableOptions = angular.extend({}, sortableDefaults, scope.options.sortableOptions || {}); } diff --git a/test/spec/dashboard-layouts.spec.js b/src/components/directives/dashboardLayouts/dashboardLayouts.spec.js similarity index 100% rename from test/spec/dashboard-layouts.spec.js rename to src/components/directives/dashboardLayouts/dashboardLayouts.spec.js diff --git a/src/controllers/dashboardWidgetCtrl.js b/src/components/directives/widget/DashboardWidgetCtrl.js similarity index 100% rename from src/controllers/dashboardWidgetCtrl.js rename to src/components/directives/widget/DashboardWidgetCtrl.js diff --git a/test/spec/dashboardWidgetCtrl.js b/src/components/directives/widget/DashboardWidgetCtrl.spec.js similarity index 100% rename from test/spec/dashboardWidgetCtrl.js rename to src/components/directives/widget/DashboardWidgetCtrl.spec.js diff --git a/src/directives/widget.js b/src/components/directives/widget/widget.js similarity index 100% rename from src/directives/widget.js rename to src/components/directives/widget/widget.js diff --git a/test/spec/widget.js b/src/components/directives/widget/widget.spec.js similarity index 100% rename from test/spec/widget.js rename to src/components/directives/widget/widget.spec.js diff --git a/src/models/dashboardState.js b/src/components/models/DashboardState.js similarity index 100% rename from src/models/dashboardState.js rename to src/components/models/DashboardState.js diff --git a/src/models/LayoutStorage.js b/src/components/models/LayoutStorage.js similarity index 100% rename from src/models/LayoutStorage.js rename to src/components/models/LayoutStorage.js diff --git a/test/spec/LayoutStorage.spec.js b/src/components/models/LayoutStorage.spec.js similarity index 100% rename from test/spec/LayoutStorage.spec.js rename to src/components/models/LayoutStorage.spec.js diff --git a/src/models/widgetDataModel.js b/src/components/models/WidgetDataModel.js similarity index 100% rename from src/models/widgetDataModel.js rename to src/components/models/WidgetDataModel.js diff --git a/src/models/widgetDefCollection.js b/src/components/models/WidgetDefCollection.js similarity index 77% rename from src/models/widgetDefCollection.js rename to src/components/models/WidgetDefCollection.js index e49c90e..beaa214 100644 --- a/src/models/widgetDefCollection.js +++ b/src/components/models/WidgetDefCollection.js @@ -18,7 +18,18 @@ angular.module('ui.dashboard') .factory('WidgetDefCollection', function () { + + function convertToDefinition(d) { + if (typeof d === 'function') { + return new d(); + } + return d; + } + function WidgetDefCollection(widgetDefs) { + + widgetDefs = widgetDefs.map(convertToDefinition); + this.push.apply(this, widgetDefs); // build (name -> widget definition) map for widget lookup by name @@ -35,5 +46,11 @@ angular.module('ui.dashboard') return this.map[name]; }; + WidgetDefCollection.prototype.add = function(def) { + def = convertToDefinition(def); + this.push(def); + this.map[def.name] = def; + } + return WidgetDefCollection; }); \ No newline at end of file diff --git a/src/models/widgetModel.js b/src/components/models/WidgetModel.js similarity index 100% rename from src/models/widgetModel.js rename to src/components/models/WidgetModel.js diff --git a/src/favicon.ico b/src/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..6527905307f19ba00762f9241f7eb535fa84a2f9 GIT binary patch literal 4286 zcmchaPe@cz6vpqQW1y54B@{_hhFD-kWPgyXjSGVaf);_51TESOlSPOdvy}@W5Q+** zs6~RrtlR}7(V|sCkP&1f7!5{Hixw@4+x@+HXSm*Z^WGalm2d8S=brO@=iGm9MyZ7P zPo)%}YN|=8W~EfSfibDm2H3qnGq$y%h@zqVv#zn@@WvhIGJ8*ECePe@roq(*vwGys z4?Q;bI~MRIM&jXu6Yg@wqQ#8&8x#z55E}ONd3<&rw_h!5AbBx{CcZ%&z736jHxFa0 zsBLqly3+dQ%MZGH{QU}GW6bsq=@$a@sXtac^<8>8uP>*+d!Qdtv&&mnKlvE_T-+SC z*QNCVwcvq%+&DDc+T}Uf(2_FavDN{-&hCpIs?aW=A$mcrzyD+9(025i1~K&uVf&w4 zItQLK9T{7k?s@bnU*&p+<^UI*aHA1aH+Fo^PAzM|xjNK09?2V(Cme7IFB(BP?7#at z(>DB3w`AUFS~=(LUBdZ>v-SG4J~%Mrfj&05Z)oj13l5tbEq4x>8+;FC0Dvr zbJY#7PS$+yE_Cf7gxqQEC@RoZX5J^}71l+`Q~qnOF4D za`lhjUuqZa-sj)EHDleV2i|mc!Ly-@7IwzPM{?pBUt(+@IHi8HTz#Iq9)9h|hrL3) zfOT#@|5$JCxmRjsOj>&kUt(m8*57|W(FoE`CX*8edYv%j=3sR5>!hvglJ#@8K6j$g z&IuUbRC_{)p}sbyx%UD6Fki;t6nDk0gT5&6Q_at7FbVVOu?4VK{oR#!kyYbCc;<4+LITzoZ8-~O5L+9MiLHL4NyME>! z;Ky7<)UR!gN_~GXhMvPMHNB;EmmIK}eHD&~cRx89jth}IM#tU%ablw0|GxfE9IjRR zl-)b-IvC#UD!IewzPL77SI>R+?}<2ERr|R2o~zCC8rJUR8>DI5*0O$6+k~wZ)Mt;b z(Hul-OFl+F))}lK&&Yi*+S2kJmHDbdBWOQnaSA6S|#* + + + + Malhar Angular Dashboard + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + diff --git a/template/widget-default-content.html b/template/widget-default-content.html deleted file mode 100644 index e69de29..0000000 From aa0b83055ef74acba860858c5483fc57aa0e3fcf Mon Sep 17 00:00:00 2001 From: Andy Perlitch Date: Tue, 10 Feb 2015 16:05:02 -0800 Subject: [PATCH 02/18] removed comments from jshintrc file --- .jshintrc | 2 -- 1 file changed, 2 deletions(-) diff --git a/.jshintrc b/.jshintrc index 2683e48..f0ad7c9 100644 --- a/.jshintrc +++ b/.jshintrc @@ -21,9 +21,7 @@ "validthis": true, "globals": { "angular": false, - // Angular Mocks "inject": false, - // JASMINE "describe": false, "it": false, "before": false, From 65380da7098649b342988c49b376bbd4cf8b8761 Mon Sep 17 00:00:00 2001 From: Andy Perlitch Date: Wed, 11 Feb 2015 12:36:22 -0800 Subject: [PATCH 03/18] fix gulp test --- gulp/unit-tests.js | 16 +++++++++------- package.json | 1 + .../directives/dashboard/dashboard.spec.js | 4 ++-- .../dashboardLayouts/dashboardLayouts.spec.js | 12 +++++++++--- 4 files changed, 21 insertions(+), 12 deletions(-) diff --git a/gulp/unit-tests.js b/gulp/unit-tests.js index f13a9c0..c9a32e1 100644 --- a/gulp/unit-tests.js +++ b/gulp/unit-tests.js @@ -4,6 +4,7 @@ var gulp = require('gulp'); var $ = require('gulp-load-plugins')(); +var merge = require('merge-stream'); var wiredep = require('wiredep'); var paths = gulp.paths; @@ -15,12 +16,13 @@ function runTests (singleRun, done) { dependencies: true, devDependencies: true }); + var testFiles = gulp.src(bowerDeps.js); + var srcFiles = gulp.src([ + paths.src + '/{app,components}/**/*.js', + paths.tmp + '/partials/templateCacheHtml.js' + ]).pipe($.angularFilesort()); - var testFiles = bowerDeps.js.concat([ - paths.src + '/{app,components}/**/*.js' - ]); - - gulp.src(testFiles) + merge(testFiles, srcFiles) .pipe($.karma({ configFile: 'karma.conf.js', action: (singleRun)? 'run': 'watch' @@ -31,5 +33,5 @@ function runTests (singleRun, done) { }); } -gulp.task('test', function (done) { runTests(true /* singleRun */, done) }); -gulp.task('test:auto', function (done) { runTests(false /* singleRun */, done) }); +gulp.task('test', ['partials'], function (done) { runTests(true /* singleRun */, done) }); +gulp.task('test:auto', ['partials'], function (done) { runTests(false /* singleRun */, done) }); \ No newline at end of file diff --git a/package.json b/package.json index a02debb..40b7e1e 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "karma-jasmine": "~0.3.1", "karma-phantomjs-launcher": "~0.1.4", "main-bower-files": "~2.4.0", + "merge-stream": "^0.1.7", "protractor": "~1.4.0", "require-dir": "~0.1.0", "uglify-save-license": "~0.4.1", diff --git a/src/components/directives/dashboard/dashboard.spec.js b/src/components/directives/dashboard/dashboard.spec.js index f99da8c..9eb7a93 100644 --- a/src/components/directives/dashboard/dashboard.spec.js +++ b/src/components/directives/dashboard/dashboard.spec.js @@ -476,7 +476,7 @@ describe('Directive: dashboard', function () { expect(modalOptions.resolve.widget() === widget).toEqual(true); }); - it('should set the templateUrl in modal options to the default ("template/widget-settings-template.html")', function() { + it('should set the templateUrl in modal options to the default ("components/directives/dashboard/widget-settings-template.html")', function() { var widget = {}; var dfr = $q.defer(); spyOn(mockModal, 'open').and.callFake(function(options) { @@ -486,7 +486,7 @@ describe('Directive: dashboard', function () { }; }); childScope.openWidgetSettings(widget); - expect(modalOptions.templateUrl).toEqual('template/widget-settings-template.html'); + expect(modalOptions.templateUrl).toEqual('components/directives/dashboard/widget-settings-template.html'); }); it('should set the templateUrl in modal options to scope.options.settingsModalOptions.templateUrl', function() { diff --git a/src/components/directives/dashboardLayouts/dashboardLayouts.spec.js b/src/components/directives/dashboardLayouts/dashboardLayouts.spec.js index 25412b4..7d26b67 100644 --- a/src/components/directives/dashboardLayouts/dashboardLayouts.spec.js +++ b/src/components/directives/dashboardLayouts/dashboardLayouts.spec.js @@ -335,7 +335,9 @@ describe('Directive: dashboard-layouts', function () { it('should call dashboard.addWidget method of the active layout', function() { options.addWidget(1,2,3); expect(mockDash.dashboard.addWidget).toHaveBeenCalled(); - expect(mockDash.dashboard.addWidget.calls.first()).toEqual({object: mockDash.dashboard, args: [1,2,3]}); + var firstCall = mockDash.dashboard.addWidget.calls.first(); + expect(firstCall.object).toEqual(mockDash.dashboard); + expect(firstCall.args).toEqual([1,2,3]); }); it('should do nothing if there is no active layout', function() { @@ -352,7 +354,9 @@ describe('Directive: dashboard-layouts', function () { it('should call dashboard.loadWidgets of the current layout', function() { options.loadWidgets(1,2,3); expect(mockDash.dashboard.loadWidgets).toHaveBeenCalled(); - expect(mockDash.dashboard.loadWidgets.calls.first()).toEqual({object: mockDash.dashboard, args: [1,2,3]}); + var firstCall = mockDash.dashboard.loadWidgets.calls.first(); + expect(firstCall.object).toEqual(mockDash.dashboard); + expect(firstCall.args).toEqual([1,2,3]); }); it('should do nothing if there is no active layout', function() { @@ -369,7 +373,9 @@ describe('Directive: dashboard-layouts', function () { it('should call dashboard.saveDashboard of the current layout', function() { options.saveDashboard(1,2,3); expect(mockDash.dashboard.saveDashboard).toHaveBeenCalled(); - expect(mockDash.dashboard.saveDashboard.calls.first()).toEqual({object: mockDash.dashboard, args: [1,2,3]}); + var firstCall = mockDash.dashboard.saveDashboard.calls.first(); + expect(firstCall.object).toEqual(mockDash.dashboard); + expect(firstCall.args).toEqual([1,2,3]); }); it('should do nothing if there is no active layout', function() { From 214d3f8d1149d5c8910276583a5354e7ad65dfc5 Mon Sep 17 00:00:00 2001 From: Andy Perlitch Date: Wed, 11 Feb 2015 12:38:51 -0800 Subject: [PATCH 04/18] add angular resolution --- bower.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bower.json b/bower.json index 391c665..4412cc9 100644 --- a/bower.json +++ b/bower.json @@ -21,5 +21,8 @@ "showdown": { "main": "src/showdown.js" } + }, + "resolutions": { + "angular": "1.3.13" } -} \ No newline at end of file +} From ce8e98ff0382ab44561ff865f42f3621da25d72e Mon Sep 17 00:00:00 2001 From: Andy Perlitch Date: Wed, 11 Feb 2015 12:43:08 -0800 Subject: [PATCH 05/18] update travis config, removed yo-rc file --- .travis.yml | 2 +- .yo-rc.json | 65 ---------------------------------------------------- package.json | 5 +++- 3 files changed, 5 insertions(+), 67 deletions(-) delete mode 100644 .yo-rc.json diff --git a/.travis.yml b/.travis.yml index a80b6e0..80096f2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,5 +2,5 @@ language: node_js node_js: - '0.10' before_script: - - 'npm install -g bower grunt-cli' + - 'npm install -g bower gulp' - 'bower install' diff --git a/.yo-rc.json b/.yo-rc.json deleted file mode 100644 index e48afce..0000000 --- a/.yo-rc.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "generator-gulp-angular": { - "props": { - "paths": { - "src": "src", - "dist": "dist", - "e2e": "e2e", - "tmp": ".tmp" - }, - "angularVersion": "~1.3.4", - "angularModules": [], - "jQuery": { - "name": null, - "version": null - }, - "resource": { - "name": null, - "version": null, - "module": null - }, - "router": { - "name": null, - "version": null, - "module": null - }, - "ui": { - "name": "bootstrap-sass-official", - "version": "~3.3.1", - "key": "bootstrap", - "module": null - }, - "bootstrapComponents": { - "name": "angular-bootstrap", - "version": "0.12.x", - "key": "ui-bootstrap", - "module": "ui.bootstrap" - }, - "cssPreprocessor": { - "key": "less", - "extension": "less", - "module": "gulp-less", - "version": "~1.3.6" - }, - "jsPreprocessor": { - "key": "none", - "extension": "js", - "srcExtension": "js", - "module": null, - "version": null - }, - "htmlPreprocessor": { - "key": "none", - "extension": "html", - "module": null, - "version": null - }, - "foundationComponents": { - "name": null, - "version": null, - "key": null, - "module": null - } - } - } -} \ No newline at end of file diff --git a/package.json b/package.json index 40b7e1e..f913d54 100644 --- a/package.json +++ b/package.json @@ -50,5 +50,8 @@ "main": [ "dist/malhar-angular-dashboard.css", "dist/malhar-angular-dashboard.js" - ] + ], + "scripts": { + "test": "gulp test" + } } From 9f959721b4ec826d2c99b0b60e5f44a887a7b2bf Mon Sep 17 00:00:00 2001 From: Andy Perlitch Date: Wed, 11 Feb 2015 12:52:11 -0800 Subject: [PATCH 06/18] more updates to travis --- .travis.yml | 2 ++ gulp/unit-tests.js | 6 +++--- gulpfile.js | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 80096f2..c82580f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,3 +4,5 @@ node_js: before_script: - 'npm install -g bower gulp' - 'bower install' +script: + - 'gulp' \ No newline at end of file diff --git a/gulp/unit-tests.js b/gulp/unit-tests.js index c9a32e1..998a185 100644 --- a/gulp/unit-tests.js +++ b/gulp/unit-tests.js @@ -22,7 +22,7 @@ function runTests (singleRun, done) { paths.tmp + '/partials/templateCacheHtml.js' ]).pipe($.angularFilesort()); - merge(testFiles, srcFiles) + return merge(testFiles, srcFiles) .pipe($.karma({ configFile: 'karma.conf.js', action: (singleRun)? 'run': 'watch' @@ -33,5 +33,5 @@ function runTests (singleRun, done) { }); } -gulp.task('test', ['partials'], function (done) { runTests(true /* singleRun */, done) }); -gulp.task('test:auto', ['partials'], function (done) { runTests(false /* singleRun */, done) }); \ No newline at end of file +gulp.task('test', ['partials'], function () { return runTests(true /* singleRun */) }); +gulp.task('test:auto', ['partials'], function () { return runTests(false /* singleRun */) }); \ No newline at end of file diff --git a/gulpfile.js b/gulpfile.js index a1449fa..d179dbf 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -13,6 +13,6 @@ gulp.paths = { require('require-dir')('./gulp'); -gulp.task('default', ['clean'], function () { +gulp.task('default', ['clean','test'], function () { gulp.start('build'); }); From 50002e1c76799bb675367034d6dbd87d008ada81 Mon Sep 17 00:00:00 2001 From: Andy Perlitch Date: Thu, 19 Feb 2015 02:05:47 -0800 Subject: [PATCH 07/18] Arbitrary WDO data will be deep-copied to widget model instances Fixes what was being asked for in #112. Work towards #62 --- src/components/models/WidgetModel.js | 48 ++++++++----------- .../components/models/WidgetModel.spec.js | 17 ++++--- 2 files changed, 30 insertions(+), 35 deletions(-) rename test/spec/widgetModel.js => src/components/models/WidgetModel.spec.js (92%) diff --git a/src/components/models/WidgetModel.js b/src/components/models/WidgetModel.js index 7f0a533..fdf8840 100644 --- a/src/components/models/WidgetModel.js +++ b/src/components/models/WidgetModel.js @@ -18,38 +18,28 @@ angular.module('ui.dashboard') .factory('WidgetModel', function ($log) { + + function defaults() { + return { + title: 'Widget', + style: {}, + size: {}, + enableVerticalResize: true, + containerStyle: { width: '33%' }, // default width + contentStyle: {} + }; + }; + // 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 || {}, - size: Class.size || {}, - enableVerticalResize: (Class.enableVerticalResize === false) ? false : true - }; - - overrides = overrides || {}; - angular.extend(this, angular.copy(defaults), overrides); - this.containerStyle = { width: '33%' }; // default width - this.contentStyle = {}; + function WidgetModel(widgetDefinition, overrides) { + + // Extend this with the widget definition object and any overrides. + angular.extend(this, defaults(), angular.copy(widgetDefinition), overrides); + this.updateContainerStyle(this.style); - 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; + if (!this.templateUrl && !this.template && !this.directive) { + this.directive = widgetDefinition.name; } if (this.size && _.has(this.size, 'height')) { diff --git a/test/spec/widgetModel.js b/src/components/models/WidgetModel.spec.js similarity index 92% rename from test/spec/widgetModel.js rename to src/components/models/WidgetModel.spec.js index ec508c2..151e560 100644 --- a/test/spec/widgetModel.js +++ b/src/components/models/WidgetModel.spec.js @@ -28,7 +28,11 @@ describe('Factory: WidgetModel', function () { style: { width: '10em' }, settingsModalOptions: {}, onSettingsClose: function() {}, - onSettingsDismiss: function() {} + onSettingsDismiss: function() {}, + funkyChicken: { + cool: false, + fun: true + } }; Class2 = { @@ -64,14 +68,15 @@ describe('Factory: WidgetModel', function () { expect(m.style.width).toEqual('15em'); }); - it('should set templateUrl if and only if it is present on Class', function() { - var m2 = new WidgetModel(Class2, overrides); - expect(m2.templateUrl).toEqual('my/url.html'); + it('should copy arbitrary data from the widget definition', function() { + expect(m.funkyChicken.cool).toEqual(false); + expect(m.funkyChicken.fun).toEqual(true); + expect(m.funkyChicken===Class.funkyChicken).toEqual(false); }); - it('should NOT set template if templateUrl was specified', function() { + it('should set templateUrl if and only if it is present on Class', function() { var m2 = new WidgetModel(Class2, overrides); - expect(m2.template).toBeUndefined(); + expect(m2.templateUrl).toEqual('my/url.html'); }); it('should set template if and only if it is present on Class', function() { From 156d8ce8b5e4972126d31e849b9368646dc10956 Mon Sep 17 00:00:00 2001 From: Andy Perlitch Date: Thu, 19 Feb 2015 03:11:06 -0800 Subject: [PATCH 08/18] add serialize method as per #62 --- README.md | 19 +++++++++++++++++-- .../directives/dashboard/dashboard.js | 16 ++++------------ src/components/models/DashboardState.js | 12 +----------- src/components/models/WidgetModel.js | 7 +++++-- 4 files changed, 27 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index a43544a..aa493e8 100644 --- a/README.md +++ b/README.md @@ -186,14 +186,16 @@ You can think of Widget Definition Objects as a __class__ and the widgets on the | dataModelOptions | Object | n/a | false | Arbitrary values to supply to the dataModel. Available on dataModel instance as this.dataModelOptions. Serializable values in this object will also be saved if `storage` is being used (see the **Persistence** section below). | dataModelArgs | Object | n/a | false | Object to be passed to data model constructor function. This object is not serialized by default and if defined should be present in widget definitions. | dataAttrName | String | n/a | false | Name of attribute to bind `widgetData` model -| storageHash | String | n/a | false | This is analogous to the `storageHash` option on the dashboard, except at a widget-level instead of a dashboard-wide | level. This can be helpful if you would only like to invalidate stored state of one widget at a time instead of all widgets. +| storageHash | String | n/a | false | This is analogous to the `storageHash` option on the dashboard, except at a widget-level instead of a dashboard-wide level. This can be helpful if you would only like to invalidate stored state of one widget at a time instead of all widgets. | settingsModalOptions | Object | see below | no | Overrides same-named option in dashboard options for this widget. See the **Custom Widget Settings** section below. | | size | Object | n/a | false | Widget size, e.g { width: '50%', height: '250px' } | | style | Object | n/a | false | Widget style, e.g { float: 'right' } | | enableVerticalResize | Boolean | true | false | Option to enable/disable vertical resize. Should be provided in "widgetDefinitions" since it is not serialized by default. | | onSettingsClose | Function | see below | no | Overrides same-named option in dashboard options for this widget. See the **Custom Widget Settings** section below. | | onSettingsDismiss | Function | see below | no | Overrides same-named option in dashboard options for this widget. See the **Custom Widget Settings** section below. | +| serialize | Function | see below | no | Define this to override how this widget gets saved to storage. See **persistence** section below. | +As of v1.0.0, you can also add arbitrary data to your WDOs and this data will be copied to your widget. Keep in mind though, that if you want to SAVE some of this arbitrary info with storage, you will need to implement your own serialize method that includes this (see the **persistence** section below). ### Widget Resize @@ -263,7 +265,7 @@ This dashboard component offers a means to save the state of the user's dashboar - widget titles - any serializable data stored in `dataModelOptions` if the widget instance has a `ds` (instantiated `dataModelType`) -There are three options you can specify in the `dashboardOptions` object relating to persistence: +There are four options you can specify in the `dashboardOptions` object relating to persistence: ### `storage` (Object) This object will be used by the dashboard to save its state. It should implement the following three methods: @@ -284,6 +286,19 @@ This string will be stored along with the dashboard state. Then later, when stat ### `stringifyStorage` (Boolean) By default (`stringifyStorage=true`), the dashboard will convert its state (a JavaScript Object) to a string using `JSON.stringify` before passing it to `storage.setItem`. Additionally, the dashboard will assume that `storage.getItem` will return a JSON string and try to parse it with `JSON.parse`. This works with `window.localStorage` nicely, since objects cannot be used as `value` in `localStorage.setItem(key, value)`. However, if you are implementing your own `storage` and would not like this stringification business, set `stringifyStorage` to `false`. +There are also two options you can specify on WDOs that relate to persistence: + +### `storageHash` (String) +Analogous to the `storageHash` option on the dashboard, except at a widget-level instead of a dashboard-wide level. This can be helpful if you would only like to invalidate stored state of one widget at a time instead of all widgets. + +### `serialize` (Function) +This function will determine how the state of the widget gets saved. It takes no arguments and should return a `JSON.stringify`able object. The default implementation is as follows: +```js +serialize: function() { + return _.pick(this, ['title', 'name', 'style', 'size', 'dataModelOptions', 'attrs', 'storageHash']); +} +``` +See [_.pick](https://lodash.com/docs#pick) for more details. The most common use-case for this would be to add another key to this list, or remove a key. Custom Widget Settings ---------------------- diff --git a/src/components/directives/dashboard/dashboard.js b/src/components/directives/dashboard/dashboard.js index fb0d1c9..a7b28a0 100644 --- a/src/components/directives/dashboard/dashboard.js +++ b/src/components/directives/dashboard/dashboard.js @@ -113,22 +113,14 @@ angular.module('ui.dashboard') // 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++; + if (!widgetToInstantiate.title && !defaultWidgetDefinition.title) { + widgetToInstantiate.title = 'Widget ' + count++; } - // Deep extend a new object for instantiation - widgetToInstantiate = jQuery.extend(true, {}, defaultWidgetDefinition, widgetToInstantiate); - // Instantiation - var widget = new WidgetModel(widgetToInstantiate, { - title: title - }); + var widget = new WidgetModel(defaultWidgetDefinition, widgetToInstantiate); + // Add to the widgets array scope.widgets.push(widget); if (!doNotSave) { scope.saveDashboard(); diff --git a/src/components/models/DashboardState.js b/src/components/models/DashboardState.js index e28f538..67948ea 100644 --- a/src/components/models/DashboardState.js +++ b/src/components/models/DashboardState.js @@ -41,17 +41,7 @@ angular.module('ui.dashboard') } var serialized = _.map(widgets, function (widget) { - var widgetObject = { - title: widget.title, - name: widget.name, - style: widget.style, - size: widget.size, - dataModelOptions: widget.dataModelOptions, - storageHash: widget.storageHash, - attrs: widget.attrs - }; - - return widgetObject; + return widget.serialize(); }); var item = { widgets: serialized, hash: this.hash }; diff --git a/src/components/models/WidgetModel.js b/src/components/models/WidgetModel.js index fdf8840..3799b77 100644 --- a/src/components/models/WidgetModel.js +++ b/src/components/models/WidgetModel.js @@ -33,8 +33,8 @@ angular.module('ui.dashboard') // constructor for widget model instances function WidgetModel(widgetDefinition, overrides) { - // Extend this with the widget definition object and any overrides. - angular.extend(this, defaults(), angular.copy(widgetDefinition), overrides); + // Extend this with the widget definition object with overrides merged in (deep extended). + angular.extend(this, defaults(), _.merge(angular.copy(widgetDefinition), overrides)); this.updateContainerStyle(this.style); @@ -97,6 +97,9 @@ angular.module('ui.dashboard') updateContainerStyle: function (style) { angular.extend(this.containerStyle, style); + }, + serialize: function() { + return _.pick(this, ['title', 'name', 'style', 'size', 'dataModelOptions', 'attrs', 'storageHash']); } }; From 50c43ba702cc764e6e82427e83346267dddc0b5b Mon Sep 17 00:00:00 2001 From: Andy Perlitch Date: Thu, 19 Feb 2015 03:13:19 -0800 Subject: [PATCH 09/18] update dist --- dist/malhar-angular-dashboard.js | 77 +++++++++++--------------------- 1 file changed, 26 insertions(+), 51 deletions(-) diff --git a/dist/malhar-angular-dashboard.js b/dist/malhar-angular-dashboard.js index a044ac7..401d2a3 100644 --- a/dist/malhar-angular-dashboard.js +++ b/dist/malhar-angular-dashboard.js @@ -113,22 +113,14 @@ angular.module('ui.dashboard') // 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++; + if (!widgetToInstantiate.title && !defaultWidgetDefinition.title) { + widgetToInstantiate.title = 'Widget ' + count++; } - // Deep extend a new object for instantiation - widgetToInstantiate = jQuery.extend(true, {}, defaultWidgetDefinition, widgetToInstantiate); - // Instantiation - var widget = new WidgetModel(widgetToInstantiate, { - title: title - }); + var widget = new WidgetModel(defaultWidgetDefinition, widgetToInstantiate); + // Add to the widgets array scope.widgets.push(widget); if (!doNotSave) { scope.saveDashboard(); @@ -842,38 +834,28 @@ angular.module('ui.dashboard') angular.module('ui.dashboard') .factory('WidgetModel', ["$log", function ($log) { + + function defaults() { + return { + title: 'Widget', + style: {}, + size: {}, + enableVerticalResize: true, + containerStyle: { width: '33%' }, // default width + contentStyle: {} + }; + }; + // 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 || {}, - size: Class.size || {}, - enableVerticalResize: (Class.enableVerticalResize === false) ? false : true - }; + function WidgetModel(widgetDefinition, overrides) { + + // Extend this with the widget definition object with overrides merged in (deep extended). + angular.extend(this, defaults(), _.merge(angular.copy(widgetDefinition), overrides)); - overrides = overrides || {}; - angular.extend(this, angular.copy(defaults), overrides); - this.containerStyle = { width: '33%' }; // default width - this.contentStyle = {}; this.updateContainerStyle(this.style); - 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; + if (!this.templateUrl && !this.template && !this.directive) { + this.directive = widgetDefinition.name; } if (this.size && _.has(this.size, 'height')) { @@ -931,6 +913,9 @@ angular.module('ui.dashboard') updateContainerStyle: function (style) { angular.extend(this.containerStyle, style); + }, + serialize: function() { + return _.pick(this, ['title', 'name', 'style', 'size', 'dataModelOptions', 'attrs', 'storageHash']); } }; @@ -1333,17 +1318,7 @@ angular.module('ui.dashboard') } var serialized = _.map(widgets, function (widget) { - var widgetObject = { - title: widget.title, - name: widget.name, - style: widget.style, - size: widget.size, - dataModelOptions: widget.dataModelOptions, - storageHash: widget.storageHash, - attrs: widget.attrs - }; - - return widgetObject; + return widget.serialize(); }); var item = { widgets: serialized, hash: this.hash }; From 1f3b824d94cb3d530f0832b56fc057bbd0b0ee4b Mon Sep 17 00:00:00 2001 From: Andy Perlitch Date: Thu, 19 Feb 2015 03:23:01 -0800 Subject: [PATCH 10/18] Fixes #67 --- dist/malhar-angular-dashboard.js | 7 +++++++ src/components/directives/dashboard/dashboard.js | 7 +++++++ src/components/directives/dashboard/dashboard.spec.js | 7 +++++++ 3 files changed, 21 insertions(+) diff --git a/dist/malhar-angular-dashboard.js b/dist/malhar-angular-dashboard.js index 401d2a3..e87d8f4 100644 --- a/dist/malhar-angular-dashboard.js +++ b/dist/malhar-angular-dashboard.js @@ -106,6 +106,13 @@ angular.module('ui.dashboard') * @param {Object} widgetToInstantiate The definition object of the widget to be instantiated */ scope.addWidget = function (widgetToInstantiate, doNotSave) { + + if (typeof widgetToInstantiate === 'string') { + widgetToInstantiate = { + name: widgetToInstantiate + }; + } + var defaultWidgetDefinition = scope.widgetDefs.getByName(widgetToInstantiate.name); if (!defaultWidgetDefinition) { throw 'Widget ' + widgetToInstantiate.name + ' is not found.'; diff --git a/src/components/directives/dashboard/dashboard.js b/src/components/directives/dashboard/dashboard.js index a7b28a0..330bb68 100644 --- a/src/components/directives/dashboard/dashboard.js +++ b/src/components/directives/dashboard/dashboard.js @@ -106,6 +106,13 @@ angular.module('ui.dashboard') * @param {Object} widgetToInstantiate The definition object of the widget to be instantiated */ scope.addWidget = function (widgetToInstantiate, doNotSave) { + + if (typeof widgetToInstantiate === 'string') { + widgetToInstantiate = { + name: widgetToInstantiate + }; + } + var defaultWidgetDefinition = scope.widgetDefs.getByName(widgetToInstantiate.name); if (!defaultWidgetDefinition) { throw 'Widget ' + widgetToInstantiate.name + ' is not found.'; diff --git a/src/components/directives/dashboard/dashboard.spec.js b/src/components/directives/dashboard/dashboard.spec.js index 9eb7a93..610b348 100644 --- a/src/components/directives/dashboard/dashboard.spec.js +++ b/src/components/directives/dashboard/dashboard.spec.js @@ -214,6 +214,13 @@ describe('Directive: dashboard', function () { expect(childScope.saveDashboard).toHaveBeenCalled(); }); + it('should support passing just the widget name as a string', function() { + spyOn(childScope.widgetDefs, 'getByName').and.returnValue({ title: 'defaultTitle', name: 'A' }); + childScope.addWidget('A'); + expect(childScope.widgetDefs.getByName).toHaveBeenCalledWith('A'); + expect(widgetCreated.title).toEqual('defaultTitle'); + }); + describe('@awashbrook Test Case', function() { beforeEach(function() { spyOn(childScope.widgetDefs, 'getByName').and.returnValue(widgetDefault = { From 320d409a3302e36aabf63ed21cef29726acd1df0 Mon Sep 17 00:00:00 2001 From: Andy Perlitch Date: Thu, 19 Feb 2015 16:19:43 -0800 Subject: [PATCH 11/18] fix gulp serve --- gulp/inject.js | 5 +++-- gulp/server.js | 3 ++- gulp/watch.js | 9 ++++++++- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/gulp/inject.js b/gulp/inject.js index cc55cb7..0cbf793 100644 --- a/gulp/inject.js +++ b/gulp/inject.js @@ -17,12 +17,13 @@ gulp.task('inject', ['styles'], function () { var injectScripts = gulp.src([ paths.src + '/{app,components}/**/*.js', + paths.tmp + '/partials/templateCacheHtml.js', '!' + paths.src + '/{app,components}/**/*.spec.js', '!' + paths.src + '/{app,components}/**/*.mock.js' ]).pipe($.angularFilesort()); var injectOptions = { - ignorePath: [paths.src, paths.tmp + '/serve'], + ignorePath: [paths.src, paths.tmp + '/serve', paths.tmp + '/partials'], addRootSlash: false }; @@ -32,7 +33,7 @@ gulp.task('inject', ['styles'], function () { exclude: [/bootstrap\.css/, /bootstrap\.css/, /foundation\.css/] }; - return gulp.src(paths.src + '/*.html') + return gulp.src(paths.src + '/index.html') .pipe($.inject(injectStyles, injectOptions)) .pipe($.inject(injectScripts, injectOptions)) .pipe(wiredep(wiredepOptions)) diff --git a/gulp/server.js b/gulp/server.js index e5cf4d8..1cad49f 100644 --- a/gulp/server.js +++ b/gulp/server.js @@ -35,7 +35,8 @@ gulp.task('serve', ['watch'], function () { browserSyncInit([ paths.tmp + '/serve', paths.src, - paths.bower + '/bootstrap' + paths.bower + '/bootstrap', + paths.tmp + '/partials/' ], [ paths.tmp + '/serve/{app,components}/**/*.css', paths.src + '/{app,components}/**/*.js', diff --git a/gulp/watch.js b/gulp/watch.js index 61c90a2..e771c18 100644 --- a/gulp/watch.js +++ b/gulp/watch.js @@ -5,10 +5,17 @@ var gulp = require('gulp'); var paths = gulp.paths; gulp.task('watch', ['inject'], function () { + var globs = [ + paths.src + '/{app,components}/**/*.html', + paths.tmp + '/{app,components}/**/*.html' + ]; + + gulp.watch(globs, ['demo:partials']); + gulp.watch([ - paths.src + '/*.html', paths.src + '/{app,components}/**/*.less', paths.src + '/{app,components}/**/*.js', + paths.tmp + '/partials/**/*.js', 'bower.json' ], ['inject']); }); From 377c53d45925e4f9af4a9feba52162f79cf617c5 Mon Sep 17 00:00:00 2001 From: Andy Perlitch Date: Fri, 13 Mar 2015 10:37:38 -0700 Subject: [PATCH 12/18] fixes #83, allow layouts to have their own widgetDefinitions --- src/components/models/LayoutStorage.js | 2 +- src/components/models/LayoutStorage.spec.js | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/components/models/LayoutStorage.js b/src/components/models/LayoutStorage.js index 39662f8..3685fd3 100644 --- a/src/components/models/LayoutStorage.js +++ b/src/components/models/LayoutStorage.js @@ -77,7 +77,7 @@ angular.module('ui.dashboard') 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.widgetDefinitions = layout.widgetDefinitions || self.widgetDefinitions; layout.dashboard.stringifyStorage = false; layout.dashboard.defaultWidgets = layout.defaultWidgets || self.defaultWidgets; layout.dashboard.widgetButtons = self.widgetButtons; diff --git a/src/components/models/LayoutStorage.spec.js b/src/components/models/LayoutStorage.spec.js index c92ec7a..3310cad 100644 --- a/src/components/models/LayoutStorage.spec.js +++ b/src/components/models/LayoutStorage.spec.js @@ -340,6 +340,26 @@ describe('Factory: LayoutStorage', function () { expect(newLayouts[1].dashboard.defaultWidgets).toEqual(options.defaultWidgets); }); + it('should look for widgetDefinitions on storage options if not supplied on layout definition', function() { + options.widgetDefinitions = [{name: 'a'}, {name: 'b'}, {name: 'c'}]; + storage = new LayoutStorage(options); + + var newLayouts = [ { title: 'my-layout', widgetDefinitions: [] }, { title: 'my-layout-2' } ]; + storage.add(newLayouts); + expect(newLayouts[0].dashboard.widgetDefinitions === newLayouts[0].widgetDefinitions).toEqual(true); + expect(newLayouts[1].dashboard.widgetDefinitions === options.widgetDefinitions).toEqual(true); + }); + + it('should use widgetDefinitions if supplied in the layout definition', function() { + options.widgetDefinitions = [{name: 'a'}, {name: 'b'}, {name: 'c'}]; + storage = new LayoutStorage(options); + + var newLayouts = [ { title: 'my-layout', widgetDefinitions: [] }, { title: 'my-layout-2' } ]; + storage.add(newLayouts); + expect(newLayouts[0].dashboard.widgetDefinitions).toEqual([]); + expect(newLayouts[1].dashboard.widgetDefinitions).toEqual(options.widgetDefinitions); + }); + }); describe('the remove method', function() { From ec9e384c905e76afea43197e3df82806c10ccbb0 Mon Sep 17 00:00:00 2001 From: Andy Perlitch Date: Fri, 13 Mar 2015 10:43:02 -0700 Subject: [PATCH 13/18] remove some old commented code --- dist/malhar-angular-dashboard.js | 17 +---------------- .../directives/dashboard/dashboard.js | 15 --------------- 2 files changed, 1 insertion(+), 31 deletions(-) diff --git a/dist/malhar-angular-dashboard.js b/dist/malhar-angular-dashboard.js index e87d8f4..4e9ad05 100644 --- a/dist/malhar-angular-dashboard.js +++ b/dist/malhar-angular-dashboard.js @@ -44,18 +44,6 @@ angular.module('ui.dashboard') } }; - // 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); @@ -71,9 +59,6 @@ angular.module('ui.dashboard') // Shallow options _.defaults(scope.options, defaults); - // jQuery.extend(true, defaults, scope.options); - // jQuery.extend(scope.options, defaults); - var sortableDefaults = { stop: function () { scope.saveDashboard(); @@ -1108,7 +1093,7 @@ angular.module('ui.dashboard') 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.widgetDefinitions = layout.widgetDefinitions || self.widgetDefinitions; layout.dashboard.stringifyStorage = false; layout.dashboard.defaultWidgets = layout.defaultWidgets || self.defaultWidgets; layout.dashboard.widgetButtons = self.widgetButtons; diff --git a/src/components/directives/dashboard/dashboard.js b/src/components/directives/dashboard/dashboard.js index 330bb68..a4b6050 100644 --- a/src/components/directives/dashboard/dashboard.js +++ b/src/components/directives/dashboard/dashboard.js @@ -44,18 +44,6 @@ angular.module('ui.dashboard') } }; - // 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); @@ -71,9 +59,6 @@ angular.module('ui.dashboard') // Shallow options _.defaults(scope.options, defaults); - // jQuery.extend(true, defaults, scope.options); - // jQuery.extend(scope.options, defaults); - var sortableDefaults = { stop: function () { scope.saveDashboard(); From 7860813a1336580892e0f7e12b3c95f9d537e721 Mon Sep 17 00:00:00 2001 From: Andy Perlitch Date: Fri, 13 Mar 2015 11:07:51 -0700 Subject: [PATCH 14/18] fixes #99, add minimize button to default template --- dist/malhar-angular-dashboard.js | 20 +++++++++++-------- .../directives/dashboard/dashboard.html | 1 + .../directives/dashboard/dashboard.js | 18 ++++++++++------- 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/dist/malhar-angular-dashboard.js b/dist/malhar-angular-dashboard.js index 4e9ad05..faaad9d 100644 --- a/dist/malhar-angular-dashboard.js +++ b/dist/malhar-angular-dashboard.js @@ -18,7 +18,17 @@ 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) { + + var sortableDefaults = { + stop: function () { + scope.saveDashboard(); + }, + handle: '.widget-header', + distance: 5 + }; + return { restrict: 'A', templateUrl: function(element, attr) { @@ -59,12 +69,7 @@ angular.module('ui.dashboard') // Shallow options _.defaults(scope.options, defaults); - var sortableDefaults = { - stop: function () { - scope.saveDashboard(); - }, - handle: '.widget-header' - }; + // sortable options scope.sortableOptions = angular.extend({}, sortableDefaults, scope.options.sortableOptions || {}); }], @@ -73,7 +78,6 @@ angular.module('ui.dashboard') // 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; @@ -286,7 +290,7 @@ angular.module('ui.dashboard') }]); angular.module("ui.dashboard").run(["$templateCache", function($templateCache) {$templateCache.put("components/directives/dashboard/altDashboard.html","
\n
\n
\n \n \n \n \n
\n\n
\n \n
\n\n \n\n \n\n \n
\n\n
\n
\n
\n
\n

\n {{widget.title}}\n
\n \n
\n {{widget.name}}\n \n \n

\n
\n
\n
\n
\n
\n
\n
\n"); -$templateCache.put("components/directives/dashboard/dashboard.html","
\n
\n
\n \n \n \n \n
\n
\n \n
\n\n \n\n \n\n \n
\n\n
\n
\n
\n
\n

\n {{widget.title}}\n
\n \n
\n {{widget.name}}\n \n \n

\n
\n
\n
\n
\n
\n
\n
\n
"); +$templateCache.put("components/directives/dashboard/dashboard.html","
\n
\n
\n \n \n \n \n
\n
\n \n
\n\n \n\n \n\n \n
\n\n
\n
\n
\n
\n

\n {{widget.title}}\n
\n \n
\n {{widget.name}}\n \n \n \n

\n
\n
\n
\n
\n
\n
\n
\n
"); $templateCache.put("components/directives/dashboard/widget-settings-template.html","
\n \n

Widget Options {{widget.title}}

\n
\n\n
\n
\n
\n \n
\n \n
\n
\n
\n
\n
\n\n
\n \n \n
"); $templateCache.put("components/directives/dashboardLayouts/SaveChangesModal.html","
\n \n

Unsaved Changes to \"{{layout.title}}\"

\n
\n\n
\n

You have {{layout.dashboard.unsavedChangeCount}} unsaved changes on this dashboard. Would you like to save them?

\n
\n\n
\n \n \n
"); $templateCache.put("components/directives/dashboardLayouts/dashboardLayouts.html","\n
");}]); diff --git a/src/components/directives/dashboard/dashboard.html b/src/components/directives/dashboard/dashboard.html index dc0b665..08dbaff 100644 --- a/src/components/directives/dashboard/dashboard.html +++ b/src/components/directives/dashboard/dashboard.html @@ -38,6 +38,7 @@

{{widget.name}} +

diff --git a/src/components/directives/dashboard/dashboard.js b/src/components/directives/dashboard/dashboard.js index a4b6050..a36e991 100644 --- a/src/components/directives/dashboard/dashboard.js +++ b/src/components/directives/dashboard/dashboard.js @@ -18,7 +18,17 @@ 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) { + + var sortableDefaults = { + stop: function () { + scope.saveDashboard(); + }, + handle: '.widget-header', + distance: 5 + }; + return { restrict: 'A', templateUrl: function(element, attr) { @@ -59,12 +69,7 @@ angular.module('ui.dashboard') // Shallow options _.defaults(scope.options, defaults); - var sortableDefaults = { - stop: function () { - scope.saveDashboard(); - }, - handle: '.widget-header' - }; + // sortable options scope.sortableOptions = angular.extend({}, sortableDefaults, scope.options.sortableOptions || {}); }], @@ -73,7 +78,6 @@ angular.module('ui.dashboard') // 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; From df457f3a62534772fd91b825b3e5a4488a8690aa Mon Sep 17 00:00:00 2001 From: Andy Perlitch Date: Fri, 13 Mar 2015 11:25:49 -0700 Subject: [PATCH 15/18] v1.0.0 --- bower.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bower.json b/bower.json index 4412cc9..5f0b868 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "malhar-angular-dashboard", - "version": "0.8.2", + "version": "1.0.0", "dependencies": { "bootstrap": "~3.3.1", "angular-bootstrap": "0.12.x", diff --git a/package.json b/package.json index f913d54..fd35c65 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "malhar-angular-dashboard", - "version": "0.8.2", + "version": "1.0.0", "author": "https://github.com/DataTorrent/malhar-angular-dashboard/graphs/contributors", "homepage": "https://github.com/DataTorrent/malhar-angular-dashboard", "license": "Apache License, v2.0", From 23ac0b37385ff0aa650eae870a092b57a9a1514a Mon Sep 17 00:00:00 2001 From: Andy Perlitch Date: Fri, 13 Mar 2015 11:28:56 -0700 Subject: [PATCH 16/18] fix angular resolution --- bower.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bower.json b/bower.json index 5f0b868..216d8eb 100644 --- a/bower.json +++ b/bower.json @@ -23,6 +23,6 @@ } }, "resolutions": { - "angular": "1.3.13" + "angular": "~1.3" } } From a95e23dc2bf210bf1b0a12d8f07faa49c1fd4ab3 Mon Sep 17 00:00:00 2001 From: Andy Perlitch Date: Fri, 13 Mar 2015 11:37:05 -0700 Subject: [PATCH 17/18] update README --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index aa493e8..9b219ab 100644 --- a/README.md +++ b/README.md @@ -156,10 +156,10 @@ It is possible to use your own template for the dashboard and widget markup (rep | defaultWidgets | Array | n/a | yes | List of objects where an object is `{ name: [NAME_OF_WIDGET_DEFINITION] }`. TODO: Allow just list of names. | widgetButtons | Boolean | true | no | Display buttons for adding and removing widgets. | storage | Object | null | no | If defined, this object should implement three methods: `setItem`, `getItem`, and `removeItem`. See the **Persistence** section below. -| storageId | String | null | no (yes if `storage` is defined) | This is used as the first parameter passed to the three `storage` methods above. See the **Persistence** | section below. +| storageId | String | null | no (yes if `storage` is defined) | This is used as the first parameter passed to the three `storage` methods above. See the **Persistence** section below. | storageHash | String | '' | no | This is used to validate/invalidate loaded state. See the **Persistence** section below. -| stringifyStorage | Boolean | true | no | If set to true, the dashboard state will be converted to a JSON string before being passed to `storage.setItem`. Likewise, it will be | passed through JSON.parse after being retrieved from `storage.getItem`. See the **Persistence** section below. -| explicitSave | Boolean | false | no | The dashboard will not automatically save to storage for every change. Saves must instead be called explicitly using the `saveDashboard` | method that is attached to the option event upon initialization. +| stringifyStorage | Boolean | true | no | If set to true, the dashboard state will be converted to a JSON string before being passed to `storage.setItem`. Likewise, it will be passed through JSON.parse after being retrieved from `storage.getItem`. See the **Persistence** section below. +| explicitSave | Boolean | false | no | The dashboard will not automatically save to storage for every change. Saves must instead be called explicitly using the `saveDashboard` method that is attached to the option event upon initialization. | sortableOptions | Object | n/a | no | Allows to specify the various [sortable options](http://api.jqueryui.com/sortable/#options) of the underlying jQuery UI Sortable. | hideWidgetSettings | Boolean | false | no | If true, the cog button in the top right corner of each widget will not be present. | | hideWidgetClose | Boolean | false | no | If true, the "x" button in the top right corner of each widget will not be present. | @@ -392,7 +392,7 @@ key | type | default value | required | description --- | ---- | ------------- | -------- | ----------- widgetDefinitions | Array | n/a | yes | Same as in `dashboardOptions` lockDefaultLayouts | Boolean| false | no | `true` to lock default layouts (prevent from removing and renaming), layout lock can also be controlled with `locked` layout property - defaultLayouts | Array | n/a | yes | List of objects where an object is `{ title: [STRING_LAYOUT_TITLE], active: [BOOLEAN_ACTIVE_STATE], locked: [BOOLEAN], defaultWidgets: [ARRAY_DEFAULT_WIDGETS] }`. Note that `defaultWidgets` is the same as in `dashboardOptions`. + defaultLayouts | Array | n/a | yes | List of objects where an object is `{ title: [STRING_LAYOUT_TITLE], active: [BOOLEAN_ACTIVE_STATE], locked: [BOOLEAN], defaultWidgets: [ARRAY_DEFAULT_WIDGETS], widgetDefinitions: [ARRAY_OF_WIDGET_DEFS] }`. Note that `defaultWidgets` is the same as in `dashboardOptions`. Also note that the `widgetDefinitions` array is optional on individual default layouts. By default, layouts will use the `widgetDefintions` from the dashboardLayouts options object. See issue #83. widgetButtons | Boolean | true | no | Same as in `dashboardOptions` storage | Object | null | no | Same as in `dashboardOptions`, only the saved objects look like: `{ layouts: [...], states: {...}, storageHash: '' }` storageId | String | null | no (yes if `storage` is defined) | This is used as the first parameter passed to the three `storage` methods `setItem`, `getItem`, `removeItem`. See the **Persistence** section above. From f7f07194c261cc2332175501b8f3da651dfc4405 Mon Sep 17 00:00:00 2001 From: Andy Perlitch Date: Fri, 13 Mar 2015 11:39:24 -0700 Subject: [PATCH 18/18] revert sortable default options --- src/components/directives/dashboard/dashboard.js | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/components/directives/dashboard/dashboard.js b/src/components/directives/dashboard/dashboard.js index a36e991..204f835 100644 --- a/src/components/directives/dashboard/dashboard.js +++ b/src/components/directives/dashboard/dashboard.js @@ -21,14 +21,6 @@ angular.module('ui.dashboard') .directive('dashboard', ['WidgetModel', 'WidgetDefCollection', '$modal', 'DashboardState', '$log', function (WidgetModel, WidgetDefCollection, $modal, DashboardState, $log) { - var sortableDefaults = { - stop: function () { - scope.saveDashboard(); - }, - handle: '.widget-header', - distance: 5 - }; - return { restrict: 'A', templateUrl: function(element, attr) { @@ -70,6 +62,13 @@ angular.module('ui.dashboard') _.defaults(scope.options, defaults); // sortable options + var sortableDefaults = { + stop: function () { + scope.saveDashboard(); + }, + handle: '.widget-header', + distance: 5 + }; scope.sortableOptions = angular.extend({}, sortableDefaults, scope.options.sortableOptions || {}); }],