From a6bbcb8fef83c526e9ecf48466f5132efc2ab283 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 13 Jun 2017 16:10:56 -0400 Subject: [PATCH] ux: gridstack poc --- .../app/core/components/dashgrid/dashgrid.ts | 202 +- public/app/core/core.ts | 4 +- public/app/features/panel/panel_ctrl.ts | 25 +- public/app/features/panel/panel_directive.ts | 1 - public/app/features/panel/panel_menu.js | 211 +- public/app/headers/common.d.ts | 1 + public/app/system.conf.js | 29 +- public/sass/_grafana.scss | 1 + public/sass/components/_griddash.scss | 2 + public/sass/pages/_dashboard.scss | 6 +- public/vendor/jquery-ui/custom.js | 4034 +++++++++++++++++ 11 files changed, 4328 insertions(+), 188 deletions(-) create mode 100644 public/sass/components/_griddash.scss create mode 100644 public/vendor/jquery-ui/custom.js diff --git a/public/app/core/components/dashgrid/dashgrid.ts b/public/app/core/components/dashgrid/dashgrid.ts index bd0e2a1772a..db15ce32dbb 100644 --- a/public/app/core/components/dashgrid/dashgrid.ts +++ b/public/app/core/components/dashgrid/dashgrid.ts @@ -7,25 +7,47 @@ import 'jquery-ui'; import 'gridstack'; import 'gridstack.jquery-ui'; -export class DashGridCtrl { -} - const template = ` -
-
-
-
-
-
-
+
+ +
+ + +
`; -export function dashGrid() { +export class DashGridCtrl { + options: any; + + /** @ngInject */ + constructor(private $rootScope) { + this.options = { + animate: true, + }; + } + + onResizeStop() { + this.$rootScope.$broadcast('render'); + } +} + +export function dashGrid($timeout) { return { restrict: 'E', template: template, @@ -35,10 +57,156 @@ export function dashGrid() { scope: { dashboard: "=" }, - link: function(scope, elem) { - $('.grid-stack').gridstack(); + link: function(scope, elem, attrs, ctrl) { + + ctrl.panels = []; + ctrl.dashboard.forEachPanel((panel, panelIndex, row, rowIndex) => { + panel.width = 4; + panel.height = 4; + panel.x = panelIndex * 4; + panel.y = rowIndex * 4; + ctrl.panels.push(panel); + }); + } }; } +/** @ngInject */ +coreModule.controller('GridstackController', ['$scope', function($scope) { + + var gridstack = null; + + this.init = function(element, options) { + gridstack = element.gridstack(options).data('gridstack'); + return gridstack; + }; + + this.removeItem = function(element) { + if (gridstack) { + return gridstack.removeWidget(element, false); + } + return null; + }; + + this.addItem = function(element) { + if (gridstack) { + gridstack.makeWidget(element); + return element; + } + return null; + }; + +}]); + +/** @ngInject */ +coreModule.directive('gridstack', ['$timeout', function($timeout) { + return { + restrict: "A", + controller: 'GridstackController', + scope: { + onChange: '&', + onDragStart: '&', + onDragStop: '&', + onResizeStart: '&', + onResizeStop: '&', + gridstackHandler: '=', + options: '=' + }, + link: function (scope, element, attrs, controller, ngModel) { + + var gridstack = controller.init(element, scope.options); + scope.gridstackHandler = gridstack; + + element.on('change', function (e, items) { + $timeout(function() { + scope.$apply(); + scope.onChange({event: e, items: items}); + }); + }); + + element.on('dragstart', function(e, ui) { + scope.onDragStart({event: e, ui: ui}); + }); + + element.on('dragstop', function(e, ui) { + $timeout(function() { + scope.$apply(); + scope.onDragStop({event: e, ui: ui}); + }); + }); + + element.on('resizestart', function(e, ui) { + scope.onResizeStart({event: e, ui: ui}); + }); + + element.on('resizestop', function(e, ui) { + $timeout(function() { + scope.$apply(); + scope.onResizeStop({event: e, ui: ui}); + }); + }); + + } + }; +}]); + +/** @ngInject */ +coreModule.directive('gridstackItem', ['$timeout', function($timeout) { + + return { + restrict: "A", + controller: 'GridstackController', + require: '^gridstack', + scope: { + gridstackItem: '=', + onItemAdded: '&', + onItemRemoved: '&', + gsItemId: '=', + gsItemX: '=', + gsItemY: '=', + gsItemWidth: '=', + gsItemHeight: '=', + gsItemAutopos: '=' + }, + link: function (scope, element, attrs, controller) { + $(element).attr('data-gs-id', scope.gsItemId); + $(element).attr('data-gs-x', scope.gsItemX); + $(element).attr('data-gs-y', scope.gsItemY); + $(element).attr('data-gs-width', scope.gsItemWidth); + $(element).attr('data-gs-height', scope.gsItemHeight); + $(element).attr('data-gs-auto-position', scope.gsItemAutopos); + var widget = controller.addItem(element); + var item = element.data('_gridstack_node'); + $timeout(function() { + scope.onItemAdded({item: item}); + }); + scope.$watch(function () { return $(element).attr('data-gs-id'); }, function (val) { + scope.gsItemId = val; + }); + scope.$watch(function(){ return $(element).attr('data-gs-x'); }, function(val) { + scope.gsItemX = val; + }); + + scope.$watch(function(){ return $(element).attr('data-gs-y'); }, function(val) { + scope.gsItemY = val; + }); + + scope.$watch(function(){ return $(element).attr('data-gs-width'); }, function(val) { + scope.gsItemWidth = val; + }); + + scope.$watch(function(){ return $(element).attr('data-gs-height'); }, function(val) { + scope.gsItemHeight = val; + }); + + element.bind('$destroy', function() { + var item = element.data('_gridstack_node'); + scope.onItemRemoved({item: item}); + controller.removeItem(element); + }); + } + }; +}]); + coreModule.directive('dashGrid', dashGrid); diff --git a/public/app/core/core.ts b/public/app/core/core.ts index 26087c1cbda..670adc30191 100644 --- a/public/app/core/core.ts +++ b/public/app/core/core.ts @@ -46,7 +46,7 @@ import {contextSrv} from './services/context_srv'; import {KeybindingSrv} from './services/keybindingSrv'; import {helpModal} from './components/help/help'; import {NavModelSrv, NavModel} from './nav_model_srv'; -import {DashGridCtrl} from './components/dashgrid/dashgrid'; +import {dashGrid} from './components/dashgrid/dashgrid'; export { arrayJoin, @@ -72,5 +72,5 @@ export { helpModal, NavModelSrv, NavModel, - DashGridCtrl, + dashGrid, }; diff --git a/public/app/features/panel/panel_ctrl.ts b/public/app/features/panel/panel_ctrl.ts index 83c79f4123b..ad0ab046dd8 100644 --- a/public/app/features/panel/panel_ctrl.ts +++ b/public/app/features/panel/panel_ctrl.ts @@ -160,18 +160,19 @@ export class PanelCtrl { } calculatePanelHeight() { - if (this.fullscreen) { - var docHeight = $(window).height(); - var editHeight = Math.floor(docHeight * 0.4); - var fullscreenHeight = Math.floor(docHeight * 0.8); - this.containerHeight = this.editMode ? editHeight : fullscreenHeight; - } else { - this.containerHeight = this.panel.height || this.row.height; - if (_.isString(this.containerHeight)) { - this.containerHeight = parseInt(this.containerHeight.replace('px', ''), 10); - } - } - + // if (this.fullscreen) { + // var docHeight = $(window).height(); + // var editHeight = Math.floor(docHeight * 0.4); + // var fullscreenHeight = Math.floor(docHeight * 0.8); + // this.containerHeight = this.editMode ? editHeight : fullscreenHeight; + // } else { + // this.containerHeight = this.panel.height || this.row.height; + // if (_.isString(this.containerHeight)) { + // this.containerHeight = parseInt(this.containerHeight.replace('px', ''), 10); + // } + // } + const rowSpan = this.panel.height || 4; + this.containerHeight = rowSpan * 60 + ((rowSpan-1) * 20); this.height = this.containerHeight - (PANEL_BORDER + PANEL_PADDING + (this.panel.title ? TITLE_HEIGHT : EMPTY_TITLE_HEIGHT)); } diff --git a/public/app/features/panel/panel_directive.ts b/public/app/features/panel/panel_directive.ts index 1b6805179c0..9a8e343ba34 100644 --- a/public/app/features/panel/panel_directive.ts +++ b/public/app/features/panel/panel_directive.ts @@ -26,7 +26,6 @@ var panelTemplate = `
-
diff --git a/public/app/features/panel/panel_menu.js b/public/app/features/panel/panel_menu.js index 2e71fd19ff5..2acb2bbb4a4 100644 --- a/public/app/features/panel/panel_menu.js +++ b/public/app/features/panel/panel_menu.js @@ -1,10 +1,8 @@ define([ 'angular', 'jquery', - 'lodash', - 'tether', ], -function (angular, $, _, Tether) { +function (angular, $) { 'use strict'; angular @@ -17,139 +15,96 @@ function (angular, $, _, Tether) { ' {{ctrl.timeInfo}}' + ''; - function createMenuTemplate(ctrl) { - var template = '
'; - - if (ctrl.dashboard.meta.canEdit) { - template += '
'; - template += '
'; - if (!ctrl.dashboard.meta.fullscreen) { - template += ''; - template += ''; - } - template += ''; - template += '
'; - template += '
'; - } - - template += '
'; - template += ''; - - _.each(ctrl.getMenu(), function(item) { - // skip edit actions if not editor - if (item.role === 'Editor' && !ctrl.dashboard.meta.canEdit) { - return; - } - - template += ''; - }); - - template += '
'; - template += '
'; - template += '
'; - return template; - } - - function getExtendedMenu(ctrl) { - return ctrl.getExtendedMenu(); - } - return { restrict: 'A', link: function($scope, elem) { var $link = $(linkTemplate); - var $panelContainer = elem.parents(".panel-container"); - var menuScope = null; - var ctrl = $scope.ctrl; - var timeout = null; - var $menu = null; - var teather; + //var menuScope = null; + //var timeout = null; + //var $menu = null; + //var teather; elem.append($link); - function dismiss(time, force) { - clearTimeout(timeout); - timeout = null; + // function dismiss(time, force) { + // clearTimeout(timeout); + // timeout = null; + // + // if (time) { + // timeout = setTimeout(dismiss, time); + // return; + // } + // + // // if hovering or draging pospone close + // if (force !== true) { + // if ($menu.is(':hover') || $scope.ctrl.dashboard.$$panelDragging) { + // dismiss(2200); + // return; + // } + // } + // + // if (menuScope) { + // teather.destroy(); + // $menu.unbind(); + // $menu.remove(); + // menuScope.$destroy(); + // menuScope = null; + // $menu = null; + // $panelContainer.removeClass('panel-highlight'); + // } + // } - if (time) { - timeout = setTimeout(dismiss, time); - return; - } - - // if hovering or draging pospone close - if (force !== true) { - if ($menu.is(':hover') || $scope.ctrl.dashboard.$$panelDragging) { - dismiss(2200); - return; - } - } - - if (menuScope) { - teather.destroy(); - $menu.unbind(); - $menu.remove(); - menuScope.$destroy(); - menuScope = null; - $menu = null; - $panelContainer.removeClass('panel-highlight'); - } - } - - var showMenu = function(e) { - // if menu item is clicked and menu was just removed from dom ignore this event - if (!$.contains(document, e.target)) { - return; - } - - if ($menu) { - dismiss(); - return; - } - - var menuTemplate; - menuTemplate = createMenuTemplate(ctrl); - - $menu = $(menuTemplate); - $menu.mouseleave(function() { - dismiss(1000); - }); - - menuScope = $scope.$new(); - menuScope.extendedMenu = getExtendedMenu(ctrl); - menuScope.dismiss = function() { - dismiss(null, true); - }; - - $(".panel-container").removeClass('panel-highlight'); - $panelContainer.toggleClass('panel-highlight'); - - $('.panel-menu').remove(); - - elem.append($menu); - - $scope.$apply(function() { - $compile($menu.contents())(menuScope); - - teather = new Tether({ - element: $menu, - target: $panelContainer, - attachment: 'bottom center', - targetAttachment: 'top center', - constraints: [ - { - to: 'window', - attachment: 'together', - pin: true - } - ] - }); - }); - - dismiss(2200); + var showMenu = function() { + // // if menu item is clicked and menu was just removed from dom ignore this event + // if (!$.contains(document, e.target)) { + // return; + // } + // + // if ($menu) { + // dismiss(); + // return; + // } + // + // var menuTemplate; + // menuTemplate = createMenuTemplate(ctrl); + // + // $menu = $(menuTemplate); + // $menu.mouseleave(function() { + // dismiss(1000); + // }); + // + // menuScope = $scope.$new(); + // menuScope.extendedMenu = getExtendedMenu(ctrl); + // menuScope.dismiss = function() { + // dismiss(null, true); + // }; + // + // $(".panel-container").removeClass('panel-highlight'); + // $panelContainer.toggleClass('panel-highlight'); + // + // $('.panel-menu').remove(); + // + // elem.append($menu); + // + // $scope.$apply(function() { + // $compile($menu.contents())(menuScope); + // + // teather = new Tether({ + // element: $menu, + // target: $panelContainer, + // attachment: 'bottom center', + // targetAttachment: 'top center', + // constraints: [ + // { + // to: 'window', + // attachment: 'together', + // pin: true + // } + // ] + // }); + // }); + // + // dismiss(2200); }; elem.click(showMenu); diff --git a/public/app/headers/common.d.ts b/public/app/headers/common.d.ts index edc263b7db1..c36725a2c03 100644 --- a/public/app/headers/common.d.ts +++ b/public/app/headers/common.d.ts @@ -77,3 +77,4 @@ declare module 'gridstack' { var gridstack: any; export default gridstack; } + diff --git a/public/app/system.conf.js b/public/app/system.conf.js index f2a3b12a512..7af6f1c2177 100644 --- a/public/app/system.conf.js +++ b/public/app/system.conf.js @@ -33,30 +33,9 @@ System.config({ "jquery.flot.gauge": "vendor/flot/jquery.flot.gauge", "d3": "vendor/d3/d3.js", "jquery.flot.dashes": "vendor/flot/jquery.flot.dashes", - "jquery-ui": "vendor/npm/jquery-ui-dist/jquery-ui.js", + "jquery-ui": "vendor/jquery-ui/custom.js", "gridstack": "vendor/npm/gridstack/dist/gridstack.js", "gridstack.jquery-ui": "vendor/npm/gridstack/dist/gridstack.jQueryUI.js", - - 'jquery-ui/data': 'vendor/npm/jquery-ui-dist/jquery-ui.js', - 'jquery-ui/disable-selection': 'vendor/npm/jquery-ui-dist/jquery-ui.js', - 'jquery-ui/focusable': 'vendor/npm/jquery-ui-dist/jquery-ui.js', - 'jquery-ui/form': 'vendor/npm/jquery-ui-dist/jquery-ui.js', - 'jquery-ui/ie': 'vendor/npm/jquery-ui-dist/jquery-ui.js', - 'jquery-ui/keycode': 'vendor/npm/jquery-ui-dist/jquery-ui.js', - 'jquery-ui/labels': 'vendor/npm/jquery-ui-dist/jquery-ui.js', - 'jquery-ui/jquery-1-7': 'vendor/npm/jquery-ui-dist/jquery-ui.js', - 'jquery-ui/plugin': 'vendor/npm/jquery-ui-dist/jquery-ui.js', - 'jquery-ui/safe-active-element': 'vendor/npm/jquery-ui-dist/jquery-ui.js', - 'jquery-ui/safe-blur': 'vendor/npm/jquery-ui-dist/jquery-ui.js', - 'jquery-ui/scroll-parent': 'vendor/npm/jquery-ui-dist/jquery-ui.js', - 'jquery-ui/tabbable': 'vendor/npm/jquery-ui-dist/jquery-ui.js', - 'jquery-ui/unique-id': 'vendor/npm/jquery-ui-dist/jquery-ui.js', - 'jquery-ui/version': 'vendor/npm/jquery-ui-dist/jquery-ui.js', - 'jquery-ui/widget': 'vendor/npm/jquery-ui-dist/jquery-ui.js', - 'jquery-ui/widgets/mouse': 'vendor/npm/jquery-ui-dist/jquery-ui.js', - 'jquery-ui/widgets/draggable': 'vendor/npm/jquery-ui-dist/jquery-ui.js', - 'jquery-ui/widgets/droppable': 'vendor/npm/jquery-ui-dist/jquery-ui.js', - 'jquery-ui/widgets/resizable': 'vendor/npm/jquery-ui-dist/jquery-ui.js', }, packages: { @@ -85,12 +64,12 @@ System.config({ deps: ['jquery'], }, 'vendor/npm/gridstack/dist/gridstack.js': { - format: 'amd', + format: 'global', deps: ['jquery', 'jquery-ui', 'lodash'], }, "vendor/npm/gridstack/dist/gridstack.jQueryUI.js": { - format: 'amd', - deps: ['gridstack.jquery-ui'], + format: 'global', + deps: ['gridstack'], }, 'vendor/npm/virtual-scroll/src/indx.js': { format: 'cjs', diff --git a/public/sass/_grafana.scss b/public/sass/_grafana.scss index d001132d718..50758e0fdf2 100644 --- a/public/sass/_grafana.scss +++ b/public/sass/_grafana.scss @@ -76,6 +76,7 @@ @import "components/edit_sidemenu.scss"; @import "components/row.scss"; @import "components/gridstack.scss"; +@import "components/griddash.scss"; // PAGES @import "pages/login"; diff --git a/public/sass/components/_griddash.scss b/public/sass/components/_griddash.scss new file mode 100644 index 00000000000..98059f2a96c --- /dev/null +++ b/public/sass/components/_griddash.scss @@ -0,0 +1,2 @@ +.grid-stack-item-content { +} diff --git a/public/sass/pages/_dashboard.scss b/public/sass/pages/_dashboard.scss index 242f76b41bc..8269d77b35f 100644 --- a/public/sass/pages/_dashboard.scss +++ b/public/sass/pages/_dashboard.scss @@ -20,8 +20,8 @@ div.flot-text { } .panel { - display: inline-block; - float: left; + //display: inline-block; + //float: left; &--solo { .resize-panel-handle { @@ -35,7 +35,7 @@ div.flot-text { .panel-margin { margin: 0 0.4rem 0.8rem 0.4rem; - display: block; + //display: block; } .panel-container { diff --git a/public/vendor/jquery-ui/custom.js b/public/vendor/jquery-ui/custom.js new file mode 100644 index 00000000000..aa3e5d8b6ba --- /dev/null +++ b/public/vendor/jquery-ui/custom.js @@ -0,0 +1,4034 @@ +/*! jQuery UI - v1.12.1 - 2017-06-13 +* http://jqueryui.com +* Includes: widget.js, data.js, disable-selection.js, scroll-parent.js, widgets/draggable.js, widgets/droppable.js, widgets/resizable.js, widgets/mouse.js +* Copyright jQuery Foundation and other contributors; Licensed MIT */ + +(function( factory ) { + if ( typeof define === "function" && define.amd ) { + + // AMD. Register as an anonymous module. + define([ "jquery" ], factory ); + } else { + + // Browser globals + factory( jQuery ); + } +}(function( $ ) { + +$.ui = $.ui || {}; + +var version = $.ui.version = "1.12.1"; + + +/*! + * jQuery UI Widget 1.12.1 + * http://jqueryui.com + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + */ + +//>>label: Widget +//>>group: Core +//>>description: Provides a factory for creating stateful widgets with a common API. +//>>docs: http://api.jqueryui.com/jQuery.widget/ +//>>demos: http://jqueryui.com/widget/ + + + +var widgetUuid = 0; +var widgetSlice = Array.prototype.slice; + +$.cleanData = ( function( orig ) { + return function( elems ) { + var events, elem, i; + for ( i = 0; ( elem = elems[ i ] ) != null; i++ ) { + try { + + // Only trigger remove when necessary to save time + events = $._data( elem, "events" ); + if ( events && events.remove ) { + $( elem ).triggerHandler( "remove" ); + } + + // Http://bugs.jquery.com/ticket/8235 + } catch ( e ) {} + } + orig( elems ); + }; +} )( $.cleanData ); + +$.widget = function( name, base, prototype ) { + var existingConstructor, constructor, basePrototype; + + // ProxiedPrototype allows the provided prototype to remain unmodified + // so that it can be used as a mixin for multiple widgets (#8876) + var proxiedPrototype = {}; + + var namespace = name.split( "." )[ 0 ]; + name = name.split( "." )[ 1 ]; + var fullName = namespace + "-" + name; + + if ( !prototype ) { + prototype = base; + base = $.Widget; + } + + if ( $.isArray( prototype ) ) { + prototype = $.extend.apply( null, [ {} ].concat( prototype ) ); + } + + // Create selector for plugin + $.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) { + return !!$.data( elem, fullName ); + }; + + $[ namespace ] = $[ namespace ] || {}; + existingConstructor = $[ namespace ][ name ]; + constructor = $[ namespace ][ name ] = function( options, element ) { + + // Allow instantiation without "new" keyword + if ( !this._createWidget ) { + return new constructor( options, element ); + } + + // Allow instantiation without initializing for simple inheritance + // must use "new" keyword (the code above always passes args) + if ( arguments.length ) { + this._createWidget( options, element ); + } + }; + + // Extend with the existing constructor to carry over any static properties + $.extend( constructor, existingConstructor, { + version: prototype.version, + + // Copy the object used to create the prototype in case we need to + // redefine the widget later + _proto: $.extend( {}, prototype ), + + // Track widgets that inherit from this widget in case this widget is + // redefined after a widget inherits from it + _childConstructors: [] + } ); + + basePrototype = new base(); + + // We need to make the options hash a property directly on the new instance + // otherwise we'll modify the options hash on the prototype that we're + // inheriting from + basePrototype.options = $.widget.extend( {}, basePrototype.options ); + $.each( prototype, function( prop, value ) { + if ( !$.isFunction( value ) ) { + proxiedPrototype[ prop ] = value; + return; + } + proxiedPrototype[ prop ] = ( function() { + function _super() { + return base.prototype[ prop ].apply( this, arguments ); + } + + function _superApply( args ) { + return base.prototype[ prop ].apply( this, args ); + } + + return function() { + var __super = this._super; + var __superApply = this._superApply; + var returnValue; + + this._super = _super; + this._superApply = _superApply; + + returnValue = value.apply( this, arguments ); + + this._super = __super; + this._superApply = __superApply; + + return returnValue; + }; + } )(); + } ); + constructor.prototype = $.widget.extend( basePrototype, { + + // TODO: remove support for widgetEventPrefix + // always use the name + a colon as the prefix, e.g., draggable:start + // don't prefix for widgets that aren't DOM-based + widgetEventPrefix: existingConstructor ? ( basePrototype.widgetEventPrefix || name ) : name + }, proxiedPrototype, { + constructor: constructor, + namespace: namespace, + widgetName: name, + widgetFullName: fullName + } ); + + // If this widget is being redefined then we need to find all widgets that + // are inheriting from it and redefine all of them so that they inherit from + // the new version of this widget. We're essentially trying to replace one + // level in the prototype chain. + if ( existingConstructor ) { + $.each( existingConstructor._childConstructors, function( i, child ) { + var childPrototype = child.prototype; + + // Redefine the child widget using the same prototype that was + // originally used, but inherit from the new version of the base + $.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, + child._proto ); + } ); + + // Remove the list of existing child constructors from the old constructor + // so the old child constructors can be garbage collected + delete existingConstructor._childConstructors; + } else { + base._childConstructors.push( constructor ); + } + + $.widget.bridge( name, constructor ); + + return constructor; +}; + +$.widget.extend = function( target ) { + var input = widgetSlice.call( arguments, 1 ); + var inputIndex = 0; + var inputLength = input.length; + var key; + var value; + + for ( ; inputIndex < inputLength; inputIndex++ ) { + for ( key in input[ inputIndex ] ) { + value = input[ inputIndex ][ key ]; + if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) { + + // Clone objects + if ( $.isPlainObject( value ) ) { + target[ key ] = $.isPlainObject( target[ key ] ) ? + $.widget.extend( {}, target[ key ], value ) : + + // Don't extend strings, arrays, etc. with objects + $.widget.extend( {}, value ); + + // Copy everything else by reference + } else { + target[ key ] = value; + } + } + } + } + return target; +}; + +$.widget.bridge = function( name, object ) { + var fullName = object.prototype.widgetFullName || name; + $.fn[ name ] = function( options ) { + var isMethodCall = typeof options === "string"; + var args = widgetSlice.call( arguments, 1 ); + var returnValue = this; + + if ( isMethodCall ) { + + // If this is an empty collection, we need to have the instance method + // return undefined instead of the jQuery instance + if ( !this.length && options === "instance" ) { + returnValue = undefined; + } else { + this.each( function() { + var methodValue; + var instance = $.data( this, fullName ); + + if ( options === "instance" ) { + returnValue = instance; + return false; + } + + if ( !instance ) { + return $.error( "cannot call methods on " + name + + " prior to initialization; " + + "attempted to call method '" + options + "'" ); + } + + if ( !$.isFunction( instance[ options ] ) || options.charAt( 0 ) === "_" ) { + return $.error( "no such method '" + options + "' for " + name + + " widget instance" ); + } + + methodValue = instance[ options ].apply( instance, args ); + + if ( methodValue !== instance && methodValue !== undefined ) { + returnValue = methodValue && methodValue.jquery ? + returnValue.pushStack( methodValue.get() ) : + methodValue; + return false; + } + } ); + } + } else { + + // Allow multiple hashes to be passed on init + if ( args.length ) { + options = $.widget.extend.apply( null, [ options ].concat( args ) ); + } + + this.each( function() { + var instance = $.data( this, fullName ); + if ( instance ) { + instance.option( options || {} ); + if ( instance._init ) { + instance._init(); + } + } else { + $.data( this, fullName, new object( options, this ) ); + } + } ); + } + + return returnValue; + }; +}; + +$.Widget = function( /* options, element */ ) {}; +$.Widget._childConstructors = []; + +$.Widget.prototype = { + widgetName: "widget", + widgetEventPrefix: "", + defaultElement: "
", + + options: { + classes: {}, + disabled: false, + + // Callbacks + create: null + }, + + _createWidget: function( options, element ) { + element = $( element || this.defaultElement || this )[ 0 ]; + this.element = $( element ); + this.uuid = widgetUuid++; + this.eventNamespace = "." + this.widgetName + this.uuid; + + this.bindings = $(); + this.hoverable = $(); + this.focusable = $(); + this.classesElementLookup = {}; + + if ( element !== this ) { + $.data( element, this.widgetFullName, this ); + this._on( true, this.element, { + remove: function( event ) { + if ( event.target === element ) { + this.destroy(); + } + } + } ); + this.document = $( element.style ? + + // Element within the document + element.ownerDocument : + + // Element is window or document + element.document || element ); + this.window = $( this.document[ 0 ].defaultView || this.document[ 0 ].parentWindow ); + } + + this.options = $.widget.extend( {}, + this.options, + this._getCreateOptions(), + options ); + + this._create(); + + if ( this.options.disabled ) { + this._setOptionDisabled( this.options.disabled ); + } + + this._trigger( "create", null, this._getCreateEventData() ); + this._init(); + }, + + _getCreateOptions: function() { + return {}; + }, + + _getCreateEventData: $.noop, + + _create: $.noop, + + _init: $.noop, + + destroy: function() { + var that = this; + + this._destroy(); + $.each( this.classesElementLookup, function( key, value ) { + that._removeClass( value, key ); + } ); + + // We can probably remove the unbind calls in 2.0 + // all event bindings should go through this._on() + this.element + .off( this.eventNamespace ) + .removeData( this.widgetFullName ); + this.widget() + .off( this.eventNamespace ) + .removeAttr( "aria-disabled" ); + + // Clean up events and states + this.bindings.off( this.eventNamespace ); + }, + + _destroy: $.noop, + + widget: function() { + return this.element; + }, + + option: function( key, value ) { + var options = key; + var parts; + var curOption; + var i; + + if ( arguments.length === 0 ) { + + // Don't return a reference to the internal hash + return $.widget.extend( {}, this.options ); + } + + if ( typeof key === "string" ) { + + // Handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } } + options = {}; + parts = key.split( "." ); + key = parts.shift(); + if ( parts.length ) { + curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] ); + for ( i = 0; i < parts.length - 1; i++ ) { + curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {}; + curOption = curOption[ parts[ i ] ]; + } + key = parts.pop(); + if ( arguments.length === 1 ) { + return curOption[ key ] === undefined ? null : curOption[ key ]; + } + curOption[ key ] = value; + } else { + if ( arguments.length === 1 ) { + return this.options[ key ] === undefined ? null : this.options[ key ]; + } + options[ key ] = value; + } + } + + this._setOptions( options ); + + return this; + }, + + _setOptions: function( options ) { + var key; + + for ( key in options ) { + this._setOption( key, options[ key ] ); + } + + return this; + }, + + _setOption: function( key, value ) { + if ( key === "classes" ) { + this._setOptionClasses( value ); + } + + this.options[ key ] = value; + + if ( key === "disabled" ) { + this._setOptionDisabled( value ); + } + + return this; + }, + + _setOptionClasses: function( value ) { + var classKey, elements, currentElements; + + for ( classKey in value ) { + currentElements = this.classesElementLookup[ classKey ]; + if ( value[ classKey ] === this.options.classes[ classKey ] || + !currentElements || + !currentElements.length ) { + continue; + } + + // We are doing this to create a new jQuery object because the _removeClass() call + // on the next line is going to destroy the reference to the current elements being + // tracked. We need to save a copy of this collection so that we can add the new classes + // below. + elements = $( currentElements.get() ); + this._removeClass( currentElements, classKey ); + + // We don't use _addClass() here, because that uses this.options.classes + // for generating the string of classes. We want to use the value passed in from + // _setOption(), this is the new value of the classes option which was passed to + // _setOption(). We pass this value directly to _classes(). + elements.addClass( this._classes( { + element: elements, + keys: classKey, + classes: value, + add: true + } ) ); + } + }, + + _setOptionDisabled: function( value ) { + this._toggleClass( this.widget(), this.widgetFullName + "-disabled", null, !!value ); + + // If the widget is becoming disabled, then nothing is interactive + if ( value ) { + this._removeClass( this.hoverable, null, "ui-state-hover" ); + this._removeClass( this.focusable, null, "ui-state-focus" ); + } + }, + + enable: function() { + return this._setOptions( { disabled: false } ); + }, + + disable: function() { + return this._setOptions( { disabled: true } ); + }, + + _classes: function( options ) { + var full = []; + var that = this; + + options = $.extend( { + element: this.element, + classes: this.options.classes || {} + }, options ); + + function processClassString( classes, checkOption ) { + var current, i; + for ( i = 0; i < classes.length; i++ ) { + current = that.classesElementLookup[ classes[ i ] ] || $(); + if ( options.add ) { + current = $( $.unique( current.get().concat( options.element.get() ) ) ); + } else { + current = $( current.not( options.element ).get() ); + } + that.classesElementLookup[ classes[ i ] ] = current; + full.push( classes[ i ] ); + if ( checkOption && options.classes[ classes[ i ] ] ) { + full.push( options.classes[ classes[ i ] ] ); + } + } + } + + this._on( options.element, { + "remove": "_untrackClassesElement" + } ); + + if ( options.keys ) { + processClassString( options.keys.match( /\S+/g ) || [], true ); + } + if ( options.extra ) { + processClassString( options.extra.match( /\S+/g ) || [] ); + } + + return full.join( " " ); + }, + + _untrackClassesElement: function( event ) { + var that = this; + $.each( that.classesElementLookup, function( key, value ) { + if ( $.inArray( event.target, value ) !== -1 ) { + that.classesElementLookup[ key ] = $( value.not( event.target ).get() ); + } + } ); + }, + + _removeClass: function( element, keys, extra ) { + return this._toggleClass( element, keys, extra, false ); + }, + + _addClass: function( element, keys, extra ) { + return this._toggleClass( element, keys, extra, true ); + }, + + _toggleClass: function( element, keys, extra, add ) { + add = ( typeof add === "boolean" ) ? add : extra; + var shift = ( typeof element === "string" || element === null ), + options = { + extra: shift ? keys : extra, + keys: shift ? element : keys, + element: shift ? this.element : element, + add: add + }; + options.element.toggleClass( this._classes( options ), add ); + return this; + }, + + _on: function( suppressDisabledCheck, element, handlers ) { + var delegateElement; + var instance = this; + + // No suppressDisabledCheck flag, shuffle arguments + if ( typeof suppressDisabledCheck !== "boolean" ) { + handlers = element; + element = suppressDisabledCheck; + suppressDisabledCheck = false; + } + + // No element argument, shuffle and use this.element + if ( !handlers ) { + handlers = element; + element = this.element; + delegateElement = this.widget(); + } else { + element = delegateElement = $( element ); + this.bindings = this.bindings.add( element ); + } + + $.each( handlers, function( event, handler ) { + function handlerProxy() { + + // Allow widgets to customize the disabled handling + // - disabled as an array instead of boolean + // - disabled class as method for disabling individual parts + if ( !suppressDisabledCheck && + ( instance.options.disabled === true || + $( this ).hasClass( "ui-state-disabled" ) ) ) { + return; + } + return ( typeof handler === "string" ? instance[ handler ] : handler ) + .apply( instance, arguments ); + } + + // Copy the guid so direct unbinding works + if ( typeof handler !== "string" ) { + handlerProxy.guid = handler.guid = + handler.guid || handlerProxy.guid || $.guid++; + } + + var match = event.match( /^([\w:-]*)\s*(.*)$/ ); + var eventName = match[ 1 ] + instance.eventNamespace; + var selector = match[ 2 ]; + + if ( selector ) { + delegateElement.on( eventName, selector, handlerProxy ); + } else { + element.on( eventName, handlerProxy ); + } + } ); + }, + + _off: function( element, eventName ) { + eventName = ( eventName || "" ).split( " " ).join( this.eventNamespace + " " ) + + this.eventNamespace; + element.off( eventName ).off( eventName ); + + // Clear the stack to avoid memory leaks (#10056) + this.bindings = $( this.bindings.not( element ).get() ); + this.focusable = $( this.focusable.not( element ).get() ); + this.hoverable = $( this.hoverable.not( element ).get() ); + }, + + _delay: function( handler, delay ) { + function handlerProxy() { + return ( typeof handler === "string" ? instance[ handler ] : handler ) + .apply( instance, arguments ); + } + var instance = this; + return setTimeout( handlerProxy, delay || 0 ); + }, + + _hoverable: function( element ) { + this.hoverable = this.hoverable.add( element ); + this._on( element, { + mouseenter: function( event ) { + this._addClass( $( event.currentTarget ), null, "ui-state-hover" ); + }, + mouseleave: function( event ) { + this._removeClass( $( event.currentTarget ), null, "ui-state-hover" ); + } + } ); + }, + + _focusable: function( element ) { + this.focusable = this.focusable.add( element ); + this._on( element, { + focusin: function( event ) { + this._addClass( $( event.currentTarget ), null, "ui-state-focus" ); + }, + focusout: function( event ) { + this._removeClass( $( event.currentTarget ), null, "ui-state-focus" ); + } + } ); + }, + + _trigger: function( type, event, data ) { + var prop, orig; + var callback = this.options[ type ]; + + data = data || {}; + event = $.Event( event ); + event.type = ( type === this.widgetEventPrefix ? + type : + this.widgetEventPrefix + type ).toLowerCase(); + + // The original event may come from any element + // so we need to reset the target on the new event + event.target = this.element[ 0 ]; + + // Copy original event properties over to the new event + orig = event.originalEvent; + if ( orig ) { + for ( prop in orig ) { + if ( !( prop in event ) ) { + event[ prop ] = orig[ prop ]; + } + } + } + + this.element.trigger( event, data ); + return !( $.isFunction( callback ) && + callback.apply( this.element[ 0 ], [ event ].concat( data ) ) === false || + event.isDefaultPrevented() ); + } +}; + +$.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) { + $.Widget.prototype[ "_" + method ] = function( element, options, callback ) { + if ( typeof options === "string" ) { + options = { effect: options }; + } + + var hasOptions; + var effectName = !options ? + method : + options === true || typeof options === "number" ? + defaultEffect : + options.effect || defaultEffect; + + options = options || {}; + if ( typeof options === "number" ) { + options = { duration: options }; + } + + hasOptions = !$.isEmptyObject( options ); + options.complete = callback; + + if ( options.delay ) { + element.delay( options.delay ); + } + + if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) { + element[ method ]( options ); + } else if ( effectName !== method && element[ effectName ] ) { + element[ effectName ]( options.duration, options.easing, callback ); + } else { + element.queue( function( next ) { + $( this )[ method ](); + if ( callback ) { + callback.call( element[ 0 ] ); + } + next(); + } ); + } + }; +} ); + +var widget = $.widget; + + +/*! + * jQuery UI :data 1.12.1 + * http://jqueryui.com + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + */ + +//>>label: :data Selector +//>>group: Core +//>>description: Selects elements which have data stored under the specified key. +//>>docs: http://api.jqueryui.com/data-selector/ + + +var data = $.extend( $.expr[ ":" ], { + data: $.expr.createPseudo ? + $.expr.createPseudo( function( dataName ) { + return function( elem ) { + return !!$.data( elem, dataName ); + }; + } ) : + + // Support: jQuery <1.8 + function( elem, i, match ) { + return !!$.data( elem, match[ 3 ] ); + } +} ); + +/*! + * jQuery UI Disable Selection 1.12.1 + * http://jqueryui.com + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + */ + +//>>label: disableSelection +//>>group: Core +//>>description: Disable selection of text content within the set of matched elements. +//>>docs: http://api.jqueryui.com/disableSelection/ + +// This file is deprecated + + +var disableSelection = $.fn.extend( { + disableSelection: ( function() { + var eventType = "onselectstart" in document.createElement( "div" ) ? + "selectstart" : + "mousedown"; + + return function() { + return this.on( eventType + ".ui-disableSelection", function( event ) { + event.preventDefault(); + } ); + }; + } )(), + + enableSelection: function() { + return this.off( ".ui-disableSelection" ); + } +} ); + + +/*! + * jQuery UI Scroll Parent 1.12.1 + * http://jqueryui.com + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + */ + +//>>label: scrollParent +//>>group: Core +//>>description: Get the closest ancestor element that is scrollable. +//>>docs: http://api.jqueryui.com/scrollParent/ + + + +var scrollParent = $.fn.scrollParent = function( includeHidden ) { + var position = this.css( "position" ), + excludeStaticParent = position === "absolute", + overflowRegex = includeHidden ? /(auto|scroll|hidden)/ : /(auto|scroll)/, + scrollParent = this.parents().filter( function() { + var parent = $( this ); + if ( excludeStaticParent && parent.css( "position" ) === "static" ) { + return false; + } + return overflowRegex.test( parent.css( "overflow" ) + parent.css( "overflow-y" ) + + parent.css( "overflow-x" ) ); + } ).eq( 0 ); + + return position === "fixed" || !scrollParent.length ? + $( this[ 0 ].ownerDocument || document ) : + scrollParent; +}; + + + + +// This file is deprecated +var ie = $.ui.ie = !!/msie [\w.]+/.exec( navigator.userAgent.toLowerCase() ); + +/*! + * jQuery UI Mouse 1.12.1 + * http://jqueryui.com + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + */ + +//>>label: Mouse +//>>group: Widgets +//>>description: Abstracts mouse-based interactions to assist in creating certain widgets. +//>>docs: http://api.jqueryui.com/mouse/ + + + +var mouseHandled = false; +$( document ).on( "mouseup", function() { + mouseHandled = false; +} ); + +var widgetsMouse = $.widget( "ui.mouse", { + version: "1.12.1", + options: { + cancel: "input, textarea, button, select, option", + distance: 1, + delay: 0 + }, + _mouseInit: function() { + var that = this; + + this.element + .on( "mousedown." + this.widgetName, function( event ) { + return that._mouseDown( event ); + } ) + .on( "click." + this.widgetName, function( event ) { + if ( true === $.data( event.target, that.widgetName + ".preventClickEvent" ) ) { + $.removeData( event.target, that.widgetName + ".preventClickEvent" ); + event.stopImmediatePropagation(); + return false; + } + } ); + + this.started = false; + }, + + // TODO: make sure destroying one instance of mouse doesn't mess with + // other instances of mouse + _mouseDestroy: function() { + this.element.off( "." + this.widgetName ); + if ( this._mouseMoveDelegate ) { + this.document + .off( "mousemove." + this.widgetName, this._mouseMoveDelegate ) + .off( "mouseup." + this.widgetName, this._mouseUpDelegate ); + } + }, + + _mouseDown: function( event ) { + + // don't let more than one widget handle mouseStart + if ( mouseHandled ) { + return; + } + + this._mouseMoved = false; + + // We may have missed mouseup (out of window) + ( this._mouseStarted && this._mouseUp( event ) ); + + this._mouseDownEvent = event; + + var that = this, + btnIsLeft = ( event.which === 1 ), + + // event.target.nodeName works around a bug in IE 8 with + // disabled inputs (#7620) + elIsCancel = ( typeof this.options.cancel === "string" && event.target.nodeName ? + $( event.target ).closest( this.options.cancel ).length : false ); + if ( !btnIsLeft || elIsCancel || !this._mouseCapture( event ) ) { + return true; + } + + this.mouseDelayMet = !this.options.delay; + if ( !this.mouseDelayMet ) { + this._mouseDelayTimer = setTimeout( function() { + that.mouseDelayMet = true; + }, this.options.delay ); + } + + if ( this._mouseDistanceMet( event ) && this._mouseDelayMet( event ) ) { + this._mouseStarted = ( this._mouseStart( event ) !== false ); + if ( !this._mouseStarted ) { + event.preventDefault(); + return true; + } + } + + // Click event may never have fired (Gecko & Opera) + if ( true === $.data( event.target, this.widgetName + ".preventClickEvent" ) ) { + $.removeData( event.target, this.widgetName + ".preventClickEvent" ); + } + + // These delegates are required to keep context + this._mouseMoveDelegate = function( event ) { + return that._mouseMove( event ); + }; + this._mouseUpDelegate = function( event ) { + return that._mouseUp( event ); + }; + + this.document + .on( "mousemove." + this.widgetName, this._mouseMoveDelegate ) + .on( "mouseup." + this.widgetName, this._mouseUpDelegate ); + + event.preventDefault(); + + mouseHandled = true; + return true; + }, + + _mouseMove: function( event ) { + + // Only check for mouseups outside the document if you've moved inside the document + // at least once. This prevents the firing of mouseup in the case of IE<9, which will + // fire a mousemove event if content is placed under the cursor. See #7778 + // Support: IE <9 + if ( this._mouseMoved ) { + + // IE mouseup check - mouseup happened when mouse was out of window + if ( $.ui.ie && ( !document.documentMode || document.documentMode < 9 ) && + !event.button ) { + return this._mouseUp( event ); + + // Iframe mouseup check - mouseup occurred in another document + } else if ( !event.which ) { + + // Support: Safari <=8 - 9 + // Safari sets which to 0 if you press any of the following keys + // during a drag (#14461) + if ( event.originalEvent.altKey || event.originalEvent.ctrlKey || + event.originalEvent.metaKey || event.originalEvent.shiftKey ) { + this.ignoreMissingWhich = true; + } else if ( !this.ignoreMissingWhich ) { + return this._mouseUp( event ); + } + } + } + + if ( event.which || event.button ) { + this._mouseMoved = true; + } + + if ( this._mouseStarted ) { + this._mouseDrag( event ); + return event.preventDefault(); + } + + if ( this._mouseDistanceMet( event ) && this._mouseDelayMet( event ) ) { + this._mouseStarted = + ( this._mouseStart( this._mouseDownEvent, event ) !== false ); + ( this._mouseStarted ? this._mouseDrag( event ) : this._mouseUp( event ) ); + } + + return !this._mouseStarted; + }, + + _mouseUp: function( event ) { + this.document + .off( "mousemove." + this.widgetName, this._mouseMoveDelegate ) + .off( "mouseup." + this.widgetName, this._mouseUpDelegate ); + + if ( this._mouseStarted ) { + this._mouseStarted = false; + + if ( event.target === this._mouseDownEvent.target ) { + $.data( event.target, this.widgetName + ".preventClickEvent", true ); + } + + this._mouseStop( event ); + } + + if ( this._mouseDelayTimer ) { + clearTimeout( this._mouseDelayTimer ); + delete this._mouseDelayTimer; + } + + this.ignoreMissingWhich = false; + mouseHandled = false; + event.preventDefault(); + }, + + _mouseDistanceMet: function( event ) { + return ( Math.max( + Math.abs( this._mouseDownEvent.pageX - event.pageX ), + Math.abs( this._mouseDownEvent.pageY - event.pageY ) + ) >= this.options.distance + ); + }, + + _mouseDelayMet: function( /* event */ ) { + return this.mouseDelayMet; + }, + + // These are placeholder methods, to be overriden by extending plugin + _mouseStart: function( /* event */ ) {}, + _mouseDrag: function( /* event */ ) {}, + _mouseStop: function( /* event */ ) {}, + _mouseCapture: function( /* event */ ) { return true; } +} ); + + + + +// $.ui.plugin is deprecated. Use $.widget() extensions instead. +var plugin = $.ui.plugin = { + add: function( module, option, set ) { + var i, + proto = $.ui[ module ].prototype; + for ( i in set ) { + proto.plugins[ i ] = proto.plugins[ i ] || []; + proto.plugins[ i ].push( [ option, set[ i ] ] ); + } + }, + call: function( instance, name, args, allowDisconnected ) { + var i, + set = instance.plugins[ name ]; + + if ( !set ) { + return; + } + + if ( !allowDisconnected && ( !instance.element[ 0 ].parentNode || + instance.element[ 0 ].parentNode.nodeType === 11 ) ) { + return; + } + + for ( i = 0; i < set.length; i++ ) { + if ( instance.options[ set[ i ][ 0 ] ] ) { + set[ i ][ 1 ].apply( instance.element, args ); + } + } + } +}; + + + +var safeActiveElement = $.ui.safeActiveElement = function( document ) { + var activeElement; + + // Support: IE 9 only + // IE9 throws an "Unspecified error" accessing document.activeElement from an