diff --git a/CHANGELOG.md b/CHANGELOG.md index e7e6df626a5..090af032f30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,8 +3,36 @@ **UI Improvements* - [Issue #770](https://github.com/grafana/grafana/issues/770). UI: Panel dropdown menu replaced with a new panel menu +**Graph** - [Issue #877](https://github.com/grafana/grafana/issues/877). Graph: Smart auto decimal precision when using scaled unit formats - [Issue #850](https://github.com/grafana/grafana/issues/850). Graph: Shared tooltip that shows multiple series & crosshair line, thx @toni-moreno +- [Issue #940](https://github.com/grafana/grafana/issues/940). Graph: New series style override option "Fill below to", useful to visualize max & min as a shadow for the mean +- [Issue #1030](https://github.com/grafana/grafana/issues/1030). Graph: Legend table display/look changed, now includes column headers for min/max/avg, and full width (unless on right side) +- [Issue #861](https://github.com/grafana/grafana/issues/861). Graph: Export graph time series data as csv file + +**New Panels** +- [Issue #951](https://github.com/grafana/grafana/issues/951). SingleStat: New singlestat panel + +**Misc** +- [Issue #938](https://github.com/grafana/grafana/issues/938). Panel: Plugin panels now reside outside of app/panels directory +- [Issue #952](https://github.com/grafana/grafana/issues/952). Help: Shortcut "?" to open help modal with list of all shortcuts +- [Issue #991](https://github.com/grafana/grafana/issues/991). ScriptedDashboard: datasource services are now available in scripted dashboards, you can query datasource for metric keys, generate dashboards, and even save them in a scripted dashboard (see scripted_gen_and_save.js for example) +- [Issue #1041](https://github.com/grafana/grafana/issues/1041). Panel: All panels can now have links to other dashboards or absolute links, these links are available in the panel menu. + +**Changes** +- [Issue #1007](https://github.com/grafana/grafana/issues/1007). Graph: Series hide/show toggle changed to be default exclusive, so clicking on a series name will show only that series. (SHIFT or meta)+click will toggle hide/show. + +**OpenTSDB** +- [Issue #930](https://github.com/grafana/grafana/issues/930). OpenTSDB: Adding counter max and counter reset value to open tsdb query editor, thx @rsimiciuc +- [Issue #917](https://github.com/grafana/grafana/issues/917). OpenTSDB: Templating support for OpenTSDB series name and tags, thx @mchataigner + +**InfluxDB** +- [Issue #714](https://github.com/grafana/grafana/issues/714). InfluxDB: Support for sub second resolution graphs + +**Fixes** +- [Issue #925](https://github.com/grafana/grafana/issues/925). Graph: bar width calculation fix for some edge cases (bars would render on top of each other) +- [Issue #505](https://github.com/grafana/grafana/issues/505). Graph: fix for second y axis tick unit labels wrapping on the next line +- [Issue #987](https://github.com/grafana/grafana/issues/987). Dashboard: Collapsed rows became invisible when hide controls was enabled ======= # 1.8.1 (2014-09-30) diff --git a/src/app/app.js b/src/app/app.js index 91094ec38db..267d0081a90 100644 --- a/src/app/app.js +++ b/src/app/app.js @@ -56,7 +56,6 @@ function (angular, $, _, appLevelRequire, config) { register_fns.factory = $provide.factory; register_fns.service = $provide.service; register_fns.filter = $filterProvider.register; - }); var apps_deps = [ @@ -78,6 +77,8 @@ function (angular, $, _, appLevelRequire, config) { }); var preBootRequires = [ + 'services/all', + 'features/all', 'controllers/all', 'directives/all', 'filters/all', diff --git a/src/app/components/kbn.js b/src/app/components/kbn.js index cb084b1c166..ecef4bd164e 100644 --- a/src/app/components/kbn.js +++ b/src/app/components/kbn.js @@ -316,6 +316,10 @@ function($, _, moment) { kbn.formatFuncCreator = function(factor, extArray) { return function(size, decimals, scaledDecimals) { + if (size === null) { + return ""; + } + var steps = 0; while (Math.abs(size) >= factor) { @@ -331,6 +335,10 @@ function($, _, moment) { }; kbn.toFixed = function(value, decimals) { + if (value === null) { + return ""; + } + var factor = decimals ? Math.pow(10, decimals) : 1; var formatted = String(Math.round(value * factor) / factor); @@ -359,6 +367,8 @@ function($, _, moment) { kbn.valueFormats.none = kbn.toFixed; kbn.valueFormats.ms = function(size, decimals, scaledDecimals) { + if (size === null) { return ""; } + if (Math.abs(size) < 1000) { return kbn.toFixed(size, decimals) + " ms"; } @@ -383,6 +393,8 @@ function($, _, moment) { }; kbn.valueFormats.s = function(size, decimals, scaledDecimals) { + if (size === null) { return ""; } + if (Math.abs(size) < 600) { return kbn.toFixed(size, decimals) + " s"; } @@ -407,6 +419,8 @@ function($, _, moment) { }; kbn.valueFormats['µs'] = function(size, decimals, scaledDecimals) { + if (size === null) { return ""; } + if (Math.abs(size) < 1000) { return kbn.toFixed(size, decimals) + " µs"; } @@ -419,6 +433,8 @@ function($, _, moment) { }; kbn.valueFormats.ns = function(size, decimals, scaledDecimals) { + if (size === null) { return ""; } + if (Math.abs(size) < 1000) { return kbn.toFixed(size, decimals) + " ns"; } @@ -443,6 +459,17 @@ function($, _, moment) { .replace(/ +/g,'-'); }; + kbn.exportSeriesListToCsv = function(seriesList) { + var text = 'Series;Time;Value\n'; + _.each(seriesList, function(series) { + _.each(series.datapoints, function(dp) { + text += series.alias + ';' + new Date(dp[1]).toISOString() + ';' + dp[0] + '\n'; + }); + }); + var blob = new Blob([text], { type: "text/csv;charset=utf-8" }); + window.saveAs(blob, 'grafana_data_export.csv'); + }; + kbn.stringToJsRegex = function(str) { if (str[0] !== '/') { return new RegExp(str); diff --git a/src/app/components/panelmeta.js b/src/app/components/panelmeta.js new file mode 100644 index 00000000000..4fd97e9d02d --- /dev/null +++ b/src/app/components/panelmeta.js @@ -0,0 +1,45 @@ +define([ +], +function () { + "use strict"; + + function PanelMeta(options) { + this.description = options.description; + this.titlePos = options.titlePos; + this.fullscreen = options.fullscreen; + this.menu = []; + this.editorTabs = []; + this.extendedMenu = []; + + if (options.fullscreen) { + this.addMenuItem('view', 'icon-eye-open', 'toggleFullscreen(false)'); + } + + this.addMenuItem('edit', 'icon-cog', 'editPanel()'); + this.addMenuItem('duplicate', 'icon-copy', 'duplicatePanel()'); + this.addMenuItem('share', 'icon-share', 'sharePanel()'); + + this.addEditorTab('General', 'app/partials/panelgeneral.html'); + + if (options.metricsEditor) { + this.addEditorTab('Metrics', 'app/partials/metrics.html'); + } + + this.addExtendedMenuItem('Panel JSON', '', 'editPanelJson()'); + } + + PanelMeta.prototype.addMenuItem = function(text, icon, click) { + this.menu.push({text: text, icon: icon, click: click}); + }; + + PanelMeta.prototype.addExtendedMenuItem = function(text, icon, click) { + this.extendedMenu.push({text: text, icon: icon, click: click}); + }; + + PanelMeta.prototype.addEditorTab = function(title, src) { + this.editorTabs.push({title: title, src: src}); + }; + + return PanelMeta; + +}); diff --git a/src/app/components/require.config.js b/src/app/components/require.config.js index 9e0c271a40f..ff8540e5c40 100644 --- a/src/app/components/require.config.js +++ b/src/app/components/require.config.js @@ -30,7 +30,6 @@ require.config({ bootstrap: '../vendor/bootstrap/bootstrap', jquery: '../vendor/jquery/jquery-2.1.1.min', - 'jquery-ui': '../vendor/jquery/jquery-ui-1.10.3', 'extend-jquery': 'components/extend-jquery', @@ -42,6 +41,7 @@ require.config({ 'jquery.flot.stackpercent':'../vendor/jquery/jquery.flot.stackpercent', 'jquery.flot.time': '../vendor/jquery/jquery.flot.time', 'jquery.flot.crosshair': '../vendor/jquery/jquery.flot.crosshair', + 'jquery.flot.fillbelow': '../vendor/jquery/jquery.flot.fillbelow', modernizr: '../vendor/modernizr-2.6.1', @@ -77,7 +77,6 @@ require.config({ // simple dependency declaration // - 'jquery-ui': ['jquery'], 'jquery.flot': ['jquery'], 'jquery.flot.pie': ['jquery', 'jquery.flot'], 'jquery.flot.events': ['jquery', 'jquery.flot'], @@ -86,8 +85,9 @@ require.config({ 'jquery.flot.stackpercent':['jquery', 'jquery.flot'], 'jquery.flot.time': ['jquery', 'jquery.flot'], 'jquery.flot.crosshair':['jquery', 'jquery.flot'], + 'jquery.flot.fillbelow':['jquery', 'jquery.flot'], 'angular-cookies': ['angular'], - 'angular-dragdrop': ['jquery','jquery-ui','angular'], + 'angular-dragdrop': ['jquery', 'angular'], 'angular-loader': ['angular'], 'angular-mocks': ['angular'], 'angular-resource': ['angular'], diff --git a/src/app/components/settings.js b/src/app/components/settings.js index 6afba222b4b..532b2e2b133 100644 --- a/src/app/components/settings.js +++ b/src/app/components/settings.js @@ -15,12 +15,16 @@ function (_, crypto) { var defaults = { datasources : {}, window_title_prefix : 'Grafana - ', - panels : ['graph', 'text'], + panels : { + 'graph': { path: 'panels/graph' }, + 'singlestat': { path: 'panels/singlestat' }, + 'text': { path: 'panels/text' } + }, plugins : {}, default_route : '/dashboard/file/default.json', playlist_timespan : "1m", unsaved_changes_warning : true, - search : { max_results: 16 }, + search : { max_results: 100 }, admin : {} }; @@ -76,7 +80,7 @@ function (_, crypto) { }); if (settings.plugins.panels) { - settings.panels = _.union(settings.panels, settings.plugins.panels); + _.extend(settings.panels, settings.plugins.panels); } if (!settings.plugins.dependencies) { diff --git a/src/app/components/timeSeries.js b/src/app/components/timeSeries.js index a448a93649c..f9329141640 100644 --- a/src/app/components/timeSeries.js +++ b/src/app/components/timeSeries.js @@ -7,8 +7,12 @@ function (_, kbn) { function TimeSeries(opts) { this.datapoints = opts.datapoints; - this.info = opts.info; - this.label = opts.info.alias; + this.label = opts.alias; + this.id = opts.alias; + this.alias = opts.alias; + this.color = opts.color; + this.valueFormater = kbn.valueFormats.none; + this.stats = {}; } function matchSeriesOverride(aliasOrRegex, seriesAlias) { @@ -30,13 +34,13 @@ function (_, kbn) { this.lines = {}; this.points = {}; this.bars = {}; - this.info.yaxis = 1; + this.yaxis = 1; this.zindex = 0; delete this.stack; for (var i = 0; i < overrides.length; i++) { var override = overrides[i]; - if (!matchSeriesOverride(override.alias, this.info.alias)) { + if (!matchSeriesOverride(override.alias, this.alias)) { continue; } if (override.lines !== void 0) { this.lines.show = override.lines; } @@ -48,8 +52,10 @@ function (_, kbn) { if (override.pointradius !== void 0) { this.points.radius = override.pointradius; } if (override.steppedLine !== void 0) { this.lines.steps = override.steppedLine; } if (override.zindex !== void 0) { this.zindex = override.zindex; } + if (override.fillBelowTo !== void 0) { this.fillBelowTo = override.fillBelowTo; } + if (override.yaxis !== void 0) { - this.info.yaxis = override.yaxis; + this.yaxis = override.yaxis; } } }; @@ -57,12 +63,12 @@ function (_, kbn) { TimeSeries.prototype.getFlotPairs = function (fillStyle) { var result = []; - this.color = this.info.color; - this.yaxis = this.info.yaxis; - - this.info.total = 0; - this.info.max = -212312321312; - this.info.min = 212312321312; + this.stats.total = 0; + this.stats.max = Number.MIN_VALUE; + this.stats.min = Number.MAX_VALUE; + this.stats.avg = null; + this.stats.current = null; + this.allIsNull = true; var ignoreNulls = fillStyle === 'connected'; var nullAsZero = fillStyle === 'null as zero'; @@ -81,38 +87,47 @@ function (_, kbn) { } if (_.isNumber(currentValue)) { - this.info.total += currentValue; + this.stats.total += currentValue; + this.allIsNull = false; } - if (currentValue > this.info.max) { - this.info.max = currentValue; + if (currentValue > this.stats.max) { + this.stats.max = currentValue; } - if (currentValue < this.info.min) { - this.info.min = currentValue; + if (currentValue < this.stats.min) { + this.stats.min = currentValue; } - result.push([currentTime * 1000, currentValue]); + result.push([currentTime, currentValue]); } - if (result.length > 2) { - this.info.timeStep = result[1][0] - result[0][0]; + if (this.datapoints.length >= 2) { + this.stats.timeStep = this.datapoints[1][1] - this.datapoints[0][1]; } + if (this.stats.max === Number.MIN_VALUE) { this.stats.max = null; } + if (this.stats.min === Number.MAX_VALUE) { this.stats.min = null; } + if (result.length) { - this.info.avg = (this.info.total / result.length); - this.info.current = result[result.length-1][1]; + this.stats.avg = (this.stats.total / result.length); + this.stats.current = result[result.length-1][1]; + if (this.stats.current === null && result.length > 1) { + this.stats.current = result[result.length-2][1]; + } } return result; }; TimeSeries.prototype.updateLegendValues = function(formater, decimals, scaledDecimals) { - this.info.avg = this.info.avg != null ? formater(this.info.avg, decimals, scaledDecimals) : null; - this.info.current = this.info.current != null ? formater(this.info.current, decimals, scaledDecimals) : null; - this.info.min = this.info.min != null ? formater(this.info.min, decimals, scaledDecimals) : null; - this.info.max = this.info.max != null ? formater(this.info.max, decimals, scaledDecimals) : null; - this.info.total = this.info.total != null ? formater(this.info.total, decimals, scaledDecimals) : null; + this.valueFormater = formater; + this.decimals = decimals; + this.scaledDecimals = scaledDecimals; + }; + + TimeSeries.prototype.formatValue = function(value) { + return this.valueFormater(value, this.decimals, this.scaledDecimals); }; return TimeSeries; diff --git a/src/app/controllers/dashboardCtrl.js b/src/app/controllers/dashboardCtrl.js index dfe97682aa0..0aee19d82e4 100644 --- a/src/app/controllers/dashboardCtrl.js +++ b/src/app/controllers/dashboardCtrl.js @@ -3,7 +3,6 @@ define([ 'jquery', 'config', 'lodash', - 'services/all', ], function (angular, $, config, _) { "use strict"; @@ -18,11 +17,10 @@ function (angular, $, config, _) { templateValuesSrv, dashboardSrv, dashboardViewStateSrv, - panelMoveSrv, $timeout) { $scope.editor = { index: 0 }; - $scope.panelNames = config.panels; + $scope.panelNames = _.map(config.panels, function(value, key) { return key; }); var resizeEventTimeout; this.init = function(dashboardData) { @@ -51,7 +49,6 @@ function (angular, $, config, _) { // init services timeSrv.init($scope.dashboard); templateValuesSrv.init($scope.dashboard, $scope.dashboardViewState); - panelMoveSrv.init($scope.dashboard, $scope); $scope.checkFeatureToggles(); dashboardKeybindings.shortcuts($scope); @@ -92,21 +89,12 @@ function (angular, $, config, _) { }; }; - $scope.edit_path = function(type) { - var p = $scope.panel_path(type); - if(p) { - return p+'/editor.html'; - } else { - return false; - } + $scope.panelEditorPath = function(type) { + return 'app/' + config.panels[type].path + '/editor.html'; }; - $scope.panel_path =function(type) { - if(type) { - return 'app/panels/'+type.replace(".","/"); - } else { - return false; - } + $scope.pulldownEditorPath = function(type) { + return 'app/panels/'+type+'/editor.html'; }; $scope.showJsonEditor = function(evt, options) { @@ -120,12 +108,23 @@ function (angular, $, config, _) { $scope.submenuEnabled = $scope.dashboard.templating.enable || $scope.dashboard.annotations.enable; }; - $scope.setEditorTabs = function(panelMeta) { - $scope.editorTabs = ['General','Panel']; - if(!_.isUndefined(panelMeta.editorTabs)) { - $scope.editorTabs = _.union($scope.editorTabs,_.pluck(panelMeta.editorTabs,'title')); + $scope.onDrop = function(panelId, row, dropTarget) { + var info = $scope.dashboard.getPanelInfoById(panelId); + if (dropTarget) { + var dropInfo = $scope.dashboard.getPanelInfoById(dropTarget.id); + dropInfo.row.panels[dropInfo.index] = info.panel; + info.row.panels[info.index] = dropTarget; + var dragSpan = info.panel.span; + info.panel.span = dropTarget.span; + dropTarget.span = dragSpan; } - return $scope.editorTabs; + else { + info.row.panels.splice(info.index, 1); + info.panel.span = 12 - $scope.dashboard.rowSpan(row); + row.panels.push(info.panel); + } + + $rootScope.$broadcast('render'); }; }); diff --git a/src/app/controllers/dashboardNavCtrl.js b/src/app/controllers/dashboardNavCtrl.js index 084d1e215a6..3e3321b7d0d 100644 --- a/src/app/controllers/dashboardNavCtrl.js +++ b/src/app/controllers/dashboardNavCtrl.js @@ -93,12 +93,18 @@ function (angular, _, moment, config, store) { }; $scope.deleteDashboard = function(evt, options) { - if (!confirm('Do you want to delete dashboard ' + options.title + ' ?')) { - return; - } - if (!$scope.isAdmin()) { return false; } + $scope.appEvent('confirm-modal', { + title: 'Delete dashboard', + text: 'Do you want to delete dashboard ' + options.title + '?', + onConfirm: function() { + $scope.deleteDashboardConfirmed(options); + } + }); + }; + + $scope.deleteDashboardConfirmed = function(options) { var id = options.id; $scope.db.deleteDashboard(id).then(function(id) { $scope.appEvent('alert-success', ['Dashboard Deleted', id + ' has been deleted']); diff --git a/src/app/controllers/graphiteTarget.js b/src/app/controllers/graphiteTarget.js index 27299474bc0..3d899ac152f 100644 --- a/src/app/controllers/graphiteTarget.js +++ b/src/app/controllers/graphiteTarget.js @@ -201,7 +201,7 @@ function (angular, _, config, gfunc, Parser) { $scope.targetTextChanged = function() { parseTarget(); - $scope.$parent.get_data(); + $scope.get_data(); }; $scope.targetChanged = function() { @@ -275,6 +275,10 @@ function (angular, _, config, gfunc, Parser) { } }; + $scope.moveMetricQuery = function(fromIndex, toIndex) { + _.move($scope.panel.targets, fromIndex, toIndex); + }; + $scope.duplicate = function() { var clone = angular.copy($scope.target); $scope.panel.targets.push(clone); diff --git a/src/app/controllers/influxTargetCtrl.js b/src/app/controllers/influxTargetCtrl.js index b8101ab9577..e2b1a5234d4 100644 --- a/src/app/controllers/influxTargetCtrl.js +++ b/src/app/controllers/influxTargetCtrl.js @@ -1,7 +1,8 @@ define([ - 'angular' + 'angular', + 'lodash' ], -function (angular) { +function (angular, _) { 'use strict'; var module = angular.module('grafana.controllers'); @@ -83,10 +84,11 @@ function (angular) { }; $scope.listSeries = function(query, callback) { - if (!seriesList || query === '') { + if (query !== '') { seriesList = []; - $scope.datasource.listSeries().then(function(series) { + $scope.datasource.listSeries(query).then(function(series) { seriesList = series; + console.log(series); callback(seriesList); }); } @@ -95,6 +97,10 @@ function (angular) { } }; + $scope.moveMetricQuery = function(fromIndex, toIndex) { + _.move($scope.panel.targets, fromIndex, toIndex); + }; + $scope.duplicate = function() { var clone = angular.copy($scope.target); $scope.panel.targets.push(clone); diff --git a/src/app/controllers/row.js b/src/app/controllers/row.js index 55a838826bc..c92155d8d7e 100644 --- a/src/app/controllers/row.js +++ b/src/app/controllers/row.js @@ -47,9 +47,13 @@ function (angular, app, _) { }; $scope.delete_row = function() { - if (confirm("Are you sure you want to delete this row?")) { - $scope.dashboard.rows = _.without($scope.dashboard.rows, $scope.row); - } + $scope.appEvent('confirm-modal', { + title: 'Delete row', + text: 'Are you sure you want to delete this row?', + onConfirm: function() { + $scope.dashboard.rows = _.without($scope.dashboard.rows, $scope.row); + } + }); }; $scope.move_row = function(direction) { @@ -76,9 +80,13 @@ function (angular, app, _) { }; $scope.remove_panel_from_row = function(row, panel) { - if (confirm('Are you sure you want to remove this ' + panel.type + ' panel?')) { - row.panels = _.without(row.panels,panel); - } + $scope.appEvent('confirm-modal', { + title: 'Remove panel', + text: 'Are you sure you want to remove this panel?', + onConfirm: function() { + row.panels = _.without(row.panels, panel); + } + }); }; $scope.replacePanel = function(newPanel, oldPanel) { @@ -94,15 +102,12 @@ function (angular, app, _) { }); }; - $scope.duplicatePanel = function(panel, row) { - $scope.dashboard.duplicatePanel(panel, row || $scope.row); - }; - $scope.reset_panel = function(type) { var defaultSpan = 12; var _as = 12 - $scope.dashboard.rowSpan($scope.row); $scope.panel = { + title: 'no title [click here]', error : false, span : _as < defaultSpan && _as > 0 ? _as : defaultSpan, editable: true, @@ -144,13 +149,18 @@ function (angular, app, _) { module.directive('panelDropZone', function() { return function(scope, element) { - scope.$watch('dashboard.$$panelDragging', function(newVal) { - if (newVal && scope.dashboard.rowSpan(scope.row) < 10) { + scope.$on("ANGULAR_DRAG_START", function() { + var dropZoneSpan = 12 - scope.dashboard.rowSpan(scope.row); + + if (dropZoneSpan > 0) { + element.find('.panel-container').css('height', scope.row.height); + element[0].style.width = ((dropZoneSpan / 1.2) * 10) + '%'; element.show(); } - else { - element.hide(); - } + }); + + scope.$on("ANGULAR_DRAG_END", function() { + element.hide(); }); }; }); diff --git a/src/app/controllers/sharePanelCtrl.js b/src/app/controllers/sharePanelCtrl.js index e52dc297152..39909b20e19 100644 --- a/src/app/controllers/sharePanelCtrl.js +++ b/src/app/controllers/sharePanelCtrl.js @@ -27,19 +27,12 @@ function (angular, _) { } var panelId = $scope.panel.id; - var range = timeSrv.timeRange(false); var params = angular.copy($location.search()); - if (_.isString(range.to) && range.to.indexOf('now')) { - range = timeSrv.timeRange(); - } - + var range = timeSrv.timeRangeForUrl(); params.from = range.from; params.to = range.to; - if (_.isDate(params.from)) { params.from = params.from.getTime(); } - if (_.isDate(params.to)) { params.to = params.to.getTime(); } - if ($scope.includeTemplateVars) { _.each(templateSrv.variables, function(variable) { params['var-' + variable.name] = variable.current.text; @@ -66,11 +59,13 @@ function (angular, _) { var paramsArray = []; _.each(params, function(value, key) { - var str = key; - if (value !== true) { - str += '=' + encodeURIComponent(value); + if (value === null) { return; } + if (value === true) { + paramsArray.push(key); + } else { + key += '=' + encodeURIComponent(value); + paramsArray.push(key); } - paramsArray.push(str); }); $scope.shareUrl = baseUrl + "?" + paramsArray.join('&') ; diff --git a/src/app/dashboards/default.json b/src/app/dashboards/default.json index 241e663d5ea..931d4c6f802 100644 --- a/src/app/dashboards/default.json +++ b/src/app/dashboards/default.json @@ -101,7 +101,6 @@ "legend_counts": true, "timezone": "browser", "percentage": false, - "zerofill": true, "nullPointMode": "connected", "steppedLine": false, "tooltip": { diff --git a/src/app/dashboards/scripted.js b/src/app/dashboards/scripted.js index b8f428a9834..f4a438b7854 100644 --- a/src/app/dashboards/scripted.js +++ b/src/app/dashboards/scripted.js @@ -68,6 +68,17 @@ for (var i = 0; i < rows; i++) { 'target': "randomWalk('random walk2')" } ], + seriesOverrides: [ + { + alias: '/random/', + yaxis: 2, + fill: 0, + linewidth: 5 + } + ], + tooltip: { + shared: true + } } ] }); diff --git a/src/app/dashboards/scripted_gen_and_save.js b/src/app/dashboards/scripted_gen_and_save.js new file mode 100644 index 00000000000..d874b3fc28e --- /dev/null +++ b/src/app/dashboards/scripted_gen_and_save.js @@ -0,0 +1,95 @@ +/* global _ */ + +/* + * Complex scripted dashboard + * This script generates a dashboard object that Grafana can load. It also takes a number of user + * supplied URL parameters (int ARGS variable) + * + * Return a dashboard object, or a function + * + * For async scripts, return a function, this function must take a single callback function as argument, + * call this callback function with the dashboard object (look at scripted_async.js for an example) + */ + +'use strict'; + +// accessable variables in this scope +var window, document, ARGS, $, jQuery, moment, kbn, services, _; + +// default datasource +var datasource = services.datasourceSrv.default; +// get datasource used for saving dashboards +var dashboardDB = services.datasourceSrv.getGrafanaDB(); + +var targets = []; + +function getTargets(path) { + return datasource.metricFindQuery(path + '.*').then(function(result) { + if (!result) { + return null; + } + + if (targets.length === 10) { + return null; + } + + var promises = _.map(result, function(metric) { + if (metric.expandable) { + return getTargets(path + "." + metric.text); + } + else { + targets.push(path + '.' + metric.text); + } + return null; + }); + + return services.$q.all(promises); + }); +} + +function createDashboard(target, index) { + // Intialize a skeleton with nothing but a rows array and service object + var dashboard = { rows : [] }; + dashboard.title = 'Scripted dash ' + index; + dashboard.time = { + from: "now-6h", + to: "now" + }; + + dashboard.rows.push({ + title: 'Chart', + height: '300px', + panels: [ + { + title: 'Events', + type: 'graph', + span: 12, + targets: [ {target: target} ] + } + ] + }); + + return dashboard; +} + +function saveDashboard(dashboard) { + var model = services.dashboardSrv.create(dashboard); + dashboardDB.saveDashboard(model); +} + +return function(callback) { + + getTargets('apps').then(function() { + console.log('targets: ', targets); + _.each(targets, function(target, index) { + var dashboard = createDashboard(target, index); + saveDashboard(dashboard); + + if (index === targets.length - 1) { + callback(dashboard); + } + }); + }); + +}; + diff --git a/src/app/dashboards/template_vars.json b/src/app/dashboards/template_vars.json index affe7727ce2..43e9e37836c 100644 --- a/src/app/dashboards/template_vars.json +++ b/src/app/dashboards/template_vars.json @@ -65,7 +65,6 @@ "avg": false }, "percentage": false, - "zerofill": true, "nullPointMode": "connected", "steppedLine": false, "tooltip": { diff --git a/src/app/directives/addGraphiteFunc.js b/src/app/directives/addGraphiteFunc.js index e66689969ca..b585e7d003f 100644 --- a/src/app/directives/addGraphiteFunc.js +++ b/src/app/directives/addGraphiteFunc.js @@ -68,13 +68,12 @@ function (angular, app, _, $, gfunc) { }); $input.blur(function() { - $input.hide(); - $input.val(''); - $button.show(); - $button.focus(); // clicking the function dropdown menu wont // work if you remove class at once setTimeout(function() { + $input.val(''); + $input.hide(); + $button.show(); elem.removeClass('open'); }, 200); }); diff --git a/src/app/directives/all.js b/src/app/directives/all.js index 35d718fc942..d75410f3d33 100644 --- a/src/app/directives/all.js +++ b/src/app/directives/all.js @@ -10,7 +10,6 @@ define([ './confirmClick', './configModal', './spectrumPicker', - './grafanaGraph', './bootstrap-tagsinput', './bodyClass', './addGraphiteFunc', @@ -18,5 +17,6 @@ define([ './templateParamSelector', './graphiteSegment', './grafanaVersionCheck', + './dropdown.typeahead', './influxdbFuncEditor' ], function () {}); diff --git a/src/app/directives/dropdown.typeahead.js b/src/app/directives/dropdown.typeahead.js new file mode 100644 index 00000000000..f46de052c06 --- /dev/null +++ b/src/app/directives/dropdown.typeahead.js @@ -0,0 +1,105 @@ +define([ + 'angular', + 'app', + 'lodash', + 'jquery', +], +function (angular, app, _, $) { + 'use strict'; + + angular + .module('grafana.directives') + .directive('dropdownTypeahead', function($compile) { + + var inputTemplate = ''; + + var buttonTemplate = ''; + + return { + scope: { + "menuItems": "=dropdownTypeahead", + "dropdownTypeaheadOnSelect": "&dropdownTypeaheadOnSelect" + }, + link: function($scope, elem) { + var $input = $(inputTemplate); + var $button = $(buttonTemplate); + $input.appendTo(elem); + $button.appendTo(elem); + + var typeaheadValues = _.reduce($scope.menuItems, function(memo, value) { + _.each(value.submenu, function(item) { + memo.push(value.text + ' ' + item.text); + }); + return memo; + }, []); + + $scope.menuItemSelected = function(optionIndex, valueIndex) { + var option = $scope.menuItems[optionIndex]; + var result = { + $item: option.submenu[valueIndex], + $optionIndex: optionIndex, + $valueIndex: valueIndex + }; + + $scope.dropdownTypeaheadOnSelect(result); + }; + + $input.attr('data-provide', 'typeahead'); + $input.typeahead({ + source: typeaheadValues, + minLength: 1, + items: 10, + updater: function (value) { + var result = {}; + _.each($scope.menuItems, function(menuItem, optionIndex) { + _.each(menuItem.submenu, function(submenuItem, valueIndex) { + if (value === (menuItem.text + ' ' + submenuItem.text)) { + result.$item = submenuItem; + result.$optionIndex = optionIndex; + result.$valueIndex = valueIndex; + } + }); + }); + + if (result.$item) { + $scope.$apply(function() { + $scope.dropdownTypeaheadOnSelect(result); + }); + } + + $input.trigger('blur'); + return ''; + } + }); + + $button.click(function() { + $button.hide(); + $input.show(); + $input.focus(); + }); + + $input.keyup(function() { + elem.toggleClass('open', $input.val() === ''); + }); + + $input.blur(function() { + $input.hide(); + $input.val(''); + $button.show(); + $button.focus(); + // clicking the function dropdown menu wont + // work if you remove class at once + setTimeout(function() { + elem.removeClass('open'); + }, 200); + }); + + $compile(elem.contents())($scope); + } + }; + }); +}); diff --git a/src/app/directives/grafanaGraph.tooltip.js b/src/app/directives/grafanaGraph.tooltip.js deleted file mode 100644 index 9c98643a20e..00000000000 --- a/src/app/directives/grafanaGraph.tooltip.js +++ /dev/null @@ -1,132 +0,0 @@ -define([ - 'jquery', - 'kbn', -], -function ($, kbn) { - 'use strict'; - - function registerTooltipFeatures(elem, dashboard, scope) { - - var $tooltip = $('
'); - - elem.mouseleave(function () { - if (scope.panel.tooltip.shared || dashboard.sharedCrosshair) { - var plot = elem.data().plot; - if (plot) { - $tooltip.detach(); - plot.unhighlight(); - scope.appEvent('clearCrosshair'); - } - } - }); - - function findHoverIndex(posX, series) { - for (var j = 0; j < series.data.length; j++) { - if (series.data[j][0] > posX) { - return Math.max(j - 1, 0); - } - } - return j - 1; - } - - function showTooltip(title, innerHtml, pos) { - var body = '
'+ title + '
' ; - body += innerHtml + '
'; - $tooltip.html(body).place_tt(pos.pageX + 20, pos.pageY); - } - - elem.bind("plothover", function (event, pos, item) { - var plot = elem.data().plot; - var data = plot.getData(); - var group, value, timestamp, seriesInfo, format, i, series, hoverIndex, seriesHtml; - - if(dashboard.sharedCrosshair){ - scope.appEvent('setCrosshair', { pos: pos, scope: scope }); - } - - if (scope.panel.tooltip.shared) { - plot.unhighlight(); - - //check if all series has same length if so, only one x index will - //be checked and only for exact timestamp values - var pointCount = data[0].data.length; - for (i = 1; i < data.length; i++) { - if (data[i].data.length !== pointCount) { - showTooltip('Shared tooltip error', '', pos); - return; - } - } - - seriesHtml = ''; - series = data[0]; - hoverIndex = findHoverIndex(pos.x, series); - - //now we know the current X (j) position for X and Y values - timestamp = dashboard.formatDate(series.data[hoverIndex][0]); - var last_value = 0; //needed for stacked values - - for (i = data.length-1; i >= 0; --i) { - //stacked values should be added in reverse order - series = data[i]; - seriesInfo = series.info; - format = scope.panel.y_formats[seriesInfo.yaxis - 1]; - - if (scope.panel.stack) { - if (scope.panel.stack && scope.panel.tooltip.value_type === 'individual') { - value = series.data[hoverIndex][1]; - } else { - last_value += series.data[hoverIndex][1]; - value = last_value; - } - } else { - value = series.data[hoverIndex][1]; - } - - value = kbn.valueFormats[format](value, series.yaxis.tickDecimals); - - if (seriesInfo.alias) { - group = ' ' + seriesInfo.alias; - } else { - group = kbn.query_color_dot(series.color, 15) + ' '; - } - - //pre-pending new values - seriesHtml = group + ': ' + value + '
' + seriesHtml; - - plot.highlight(i, hoverIndex); - } - - showTooltip(timestamp, seriesHtml, pos); - return; - } - if (item) { - seriesInfo = item.series.info; - format = scope.panel.y_formats[seriesInfo.yaxis - 1]; - group = ' ' + seriesInfo.alias; - - if (scope.panel.stack && scope.panel.tooltip.value_type === 'individual') { - value = item.datapoint[1] - item.datapoint[2]; - } - else { - value = item.datapoint[1]; - } - - value = kbn.valueFormats[format](value, item.series.yaxis.tickDecimals); - timestamp = dashboard.formatDate(item.datapoint[0]); - group += ': ' + value + ''; - - showTooltip(timestamp, group, pos); - } else { - $tooltip.detach(); - } - }); - - } - - return { - register: registerTooltipFeatures - }; -}); diff --git a/src/app/directives/grafanaPanel.js b/src/app/directives/grafanaPanel.js index 2a8493727bc..9e3c87fde3d 100644 --- a/src/app/directives/grafanaPanel.js +++ b/src/app/directives/grafanaPanel.js @@ -1,9 +1,10 @@ define([ 'angular', 'jquery', + 'config', './panelMenu', ], -function (angular, $) { +function (angular, $, config) { 'use strict'; angular @@ -26,7 +27,7 @@ function (angular, $) { '' + '' + - '
' + + '
' + '
'+ ''; @@ -68,10 +69,12 @@ function (angular, $) { elem.addClass('ng-cloak'); + var panelPath = config.panels[panelType].path; + $scope.require([ 'jquery', - 'text!panels/'+panelType+'/module.html', - 'panels/' + panelType + "/module", + 'text!'+panelPath+'/module.html', + panelPath + "/module", ], function ($, moduleTemplate) { var $module = $(moduleTemplate); $module.prepend(panelHeader); diff --git a/src/app/directives/panelMenu.js b/src/app/directives/panelMenu.js index aac7e04f989..a4eb3bd210d 100644 --- a/src/app/directives/panelMenu.js +++ b/src/app/directives/panelMenu.js @@ -8,16 +8,12 @@ function (angular, $, _) { angular .module('grafana.directives') - .directive('panelMenu', function($compile) { - var linkTemplate = '{{panel.title | interpolateTemplateVars}}'; - var moveAttributes = ' data-drag=true data-jqyoui-options="kbnJqUiDraggableOptions"'+ - ' jqyoui-draggable="{'+ - 'animate:false,'+ - 'mutate:false,'+ - 'index:{{$index}},'+ - 'onStart:\'panelMoveStart\','+ - 'onStop:\'panelMoveStop\''+ - '}" ng-model="panel" '; + .directive('panelMenu', function($compile, linkSrv) { + var linkTemplate = + '' + + '{{panel.title | interpolateTemplateVars}}' + + '' + + ''; function createMenuTemplate($scope) { var template = '
'; @@ -26,11 +22,11 @@ function (angular, $, _) { template += ''; template += ''; template += ''; - template += ''; template += '
'; template += '
'; template += '
'; + template += ''; _.each($scope.panelMeta.menu, function(item) { template += ' 0 : false) && $scope.panel.title !== ''; + $link.toggleClass('has-panel-links', showIcon); + }); + function dismiss(time) { clearTimeout(timeout); timeout = null; @@ -109,6 +123,7 @@ function (angular, $, _) { }); menuScope = $scope.$new(); + menuScope.extendedMenu = getExtendedMenu($scope); $('.panel-menu').remove(); elem.append($menu); @@ -122,6 +137,11 @@ function (angular, $, _) { dismiss(2500); }; + if ($scope.panelMeta.titlePos && $scope.panel.title) { + elem.css('text-align', 'left'); + $link.css('padding-left', '10px'); + } + elem.click(showMenu); $compile(elem.contents())($scope); } diff --git a/src/app/directives/spectrumPicker.js b/src/app/directives/spectrumPicker.js index daec6b043aa..c6d3de6f884 100644 --- a/src/app/directives/spectrumPicker.js +++ b/src/app/directives/spectrumPicker.js @@ -32,7 +32,11 @@ function (angular) { }; input.spectrum(options); + + scope.$on('$destroy', function() { + input.spectrum('destroy'); + }); } }; }); -}); \ No newline at end of file +}); diff --git a/src/app/features/all.js b/src/app/features/all.js new file mode 100644 index 00000000000..5e11ae038f6 --- /dev/null +++ b/src/app/features/all.js @@ -0,0 +1,3 @@ +define([ + './panellinkeditor/module', +], function () {}); diff --git a/src/app/features/panellinkeditor/linkSrv.js b/src/app/features/panellinkeditor/linkSrv.js new file mode 100644 index 00000000000..eacdf1d2c35 --- /dev/null +++ b/src/app/features/panellinkeditor/linkSrv.js @@ -0,0 +1,39 @@ +define([ + 'angular', + 'kbn', +], +function (angular, kbn) { + 'use strict'; + + angular + .module('grafana.services') + .service('linkSrv', function(templateSrv, timeSrv) { + + this.getPanelLinkAnchorInfo = function(link) { + var info = {}; + if (link.type === 'absolute') { + info.target = '_blank'; + info.href = templateSrv.replace(link.url || ''); + info.title = templateSrv.replace(link.title || ''); + info.href += '?'; + + } + else { + info.title = templateSrv.replace(link.title || ''); + var slug = kbn.slugifyForUrl(link.dashboard || ''); + info.href = '#dashboard/db/' + slug + '?'; + } + + var range = timeSrv.timeRangeForUrl(); + info.href += 'from=' + range.from; + info.href += '&to=' + range.to; + + if (link.params) { + info.href += "&" + link.params; + } + + return info; + }; + + }); +}); diff --git a/src/app/features/panellinkeditor/module.html b/src/app/features/panellinkeditor/module.html new file mode 100644 index 00000000000..11558f7c90c --- /dev/null +++ b/src/app/features/panellinkeditor/module.html @@ -0,0 +1,49 @@ +
+
+
Drilldown / detail linkThese links appear in the dropdown menu in the panel menu
+ +
+
+
    +
  • + +
  • + +
  • title
  • +
  • + +
  • + +
  • type
  • +
  • + +
  • + +
  • dashboard
  • +
  • + +
  • + +
  • url
  • +
  • + +
  • + +
  • params
  • +
  • + +
  • +
+
+
+
+
+
+ +
+
+ +
diff --git a/src/app/features/panellinkeditor/module.js b/src/app/features/panellinkeditor/module.js new file mode 100644 index 00000000000..3052466d1d1 --- /dev/null +++ b/src/app/features/panellinkeditor/module.js @@ -0,0 +1,51 @@ +define([ + 'angular', + 'lodash', + './linkSrv', +], +function (angular, _) { + 'use strict'; + + angular + .module('grafana.directives') + .directive('panelLinkEditor', function() { + return { + scope: { + panel: "=" + }, + restrict: 'E', + controller: 'PanelLinkEditorCtrl', + templateUrl: 'app/features/panellinkeditor/module.html', + link: function() { + } + }; + }).controller('PanelLinkEditorCtrl', function($scope, datasourceSrv) { + + $scope.panel.links = $scope.panel.links || []; + + $scope.addLink = function() { + $scope.panel.links.push({ + type: 'dashboard', + name: 'Drilldown dashboard' + }); + }; + + $scope.searchDashboards = function(query, callback) { + var ds = datasourceSrv.getGrafanaDB(); + if (ds === null) { return; } + + ds.searchDashboards(query).then(function(result) { + var dashboards = _.map(result.dashboards, function(dash) { + return dash.title; + }); + + callback(dashboards); + }); + }; + + $scope.deleteLink = function(link) { + $scope.panel.links = _.without($scope.panel.links, link); + }; + + }); +}); diff --git a/src/app/filters/all.js b/src/app/filters/all.js index eb9a736d0ce..e75d70043d8 100755 --- a/src/app/filters/all.js +++ b/src/app/filters/all.js @@ -56,9 +56,13 @@ define(['angular', 'jquery', 'lodash', 'moment'], function (angular, $, _, momen }); module.filter('interpolateTemplateVars', function(templateSrv) { - return function(text) { + function interpolateTemplateVars(text) { return templateSrv.replaceWithText(text); - }; + } + + interpolateTemplateVars.$stateful = true; + + return interpolateTemplateVars; }); }); diff --git a/src/app/panels/graph/axisEditor.html b/src/app/panels/graph/axisEditor.html index 3aec233f6be..7c1b3d73b04 100644 --- a/src/app/panels/graph/axisEditor.html +++ b/src/app/panels/graph/axisEditor.html @@ -40,11 +40,11 @@
Legend styles
- - - + + + -
+
Legend values
diff --git a/src/app/directives/grafanaGraph.js b/src/app/panels/graph/graph.js similarity index 94% rename from src/app/directives/grafanaGraph.js rename to src/app/panels/graph/graph.js index 685ac7e0b83..e0ddfc76402 100755 --- a/src/app/directives/grafanaGraph.js +++ b/src/app/panels/graph/graph.js @@ -4,9 +4,17 @@ define([ 'kbn', 'moment', 'lodash', - './grafanaGraph.tooltip' + './graph.tooltip', + 'jquery.flot', + 'jquery.flot.events', + 'jquery.flot.selection', + 'jquery.flot.time', + 'jquery.flot.stack', + 'jquery.flot.stackpercent', + 'jquery.flot.fillbelow', + 'jquery.flot.crosshair' ], -function (angular, $, kbn, moment, _, graphTooltip) { +function (angular, $, kbn, moment, _, GraphTooltip) { 'use strict'; var module = angular.module('grafana.directives'); @@ -46,10 +54,6 @@ function (angular, $, kbn, moment, _, graphTooltip) { scope.get_data(); }); - scope.$on('toggleLegend', function() { - render_panel(); - }); - // Receive render events scope.$on('render',function(event, renderData) { data = renderData || data; @@ -68,7 +72,7 @@ function (angular, $, kbn, moment, _, graphTooltip) { height = parseInt(height.replace('px', ''), 10); } - height = height - 32; // subtract panel title bar + height -= scope.panel.title ? 24 : 9; // subtract panel title bar if (scope.panel.legend.show && !scope.panel.legend.rightSide) { height = height - 21; // subtract one line legend @@ -110,9 +114,9 @@ function (angular, $, kbn, moment, _, graphTooltip) { var series = data[i]; var axis = yaxis[series.yaxis - 1]; var formater = kbn.valueFormats[scope.panel.y_formats[series.yaxis - 1]]; - series.updateLegendValues(formater, axis.tickDecimals, axis.scaledDecimals); + series.updateLegendValues(formater, axis.tickDecimals, axis.scaledDecimals + 2); + if(!scope.$$phase) { scope.$digest(); } } - } // Function for rendering panel @@ -177,15 +181,16 @@ function (angular, $, kbn, moment, _, graphTooltip) { var series = data[i]; series.applySeriesOverrides(panel.seriesOverrides); series.data = series.getFlotPairs(panel.nullPointMode, panel.y_formats); + // if hidden remove points and disable stack - if (scope.hiddenSeries[series.info.alias]) { + if (scope.hiddenSeries[series.alias]) { series.data = []; series.stack = false; } } - if (data.length && data[0].info.timeStep) { - options.series.bars.barWidth = data[0].info.timeStep / 1.5; + if (data.length && data[0].stats.timeStep) { + options.series.bars.barWidth = data[0].stats.timeStep / 1.5; } addTimeAxis(options); @@ -206,6 +211,8 @@ function (angular, $, kbn, moment, _, graphTooltip) { } if (shouldDelayDraw(panel)) { + // temp fix for legends on the side, need to render twice to get dimensions right + callPlot(); setTimeout(callPlot, 50); legendSideLastValue = panel.legend.rightSide; } @@ -416,7 +423,9 @@ function (angular, $, kbn, moment, _, graphTooltip) { elem.html(''); } - graphTooltip.register(elem, dashboard, scope, $rootScope); + new GraphTooltip(elem, dashboard, scope, function() { + return data; + }); elem.bind("plotselected", function (event, ranges) { scope.$apply(function() { diff --git a/src/app/panels/graph/graph.tooltip.js b/src/app/panels/graph/graph.tooltip.js new file mode 100644 index 00000000000..e0fede2f62d --- /dev/null +++ b/src/app/panels/graph/graph.tooltip.js @@ -0,0 +1,182 @@ +define([ + 'jquery', +], +function ($) { + 'use strict'; + + function GraphTooltip(elem, dashboard, scope, getSeriesFn) { + var self = this; + + var $tooltip = $('
'); + + this.findHoverIndexFromDataPoints = function(posX, series,last) { + var ps = series.datapoints.pointsize; + var initial = last*ps; + var len = series.datapoints.points.length; + for (var j = initial; j < len; j += ps) { + if (series.datapoints.points[j] > posX) { + return Math.max(j - ps, 0)/ps; + } + } + return j/ps - 1; + }; + + this.findHoverIndexFromData = function(posX, series) { + var len = series.data.length; + for (var j = 0; j < len; j++) { + if (series.data[j][0] > posX) { + return Math.max(j - 1, 0); + } + } + return j - 1; + }; + + this.showTooltip = function(title, innerHtml, pos) { + var body = '
'+ title + '
' ; + body += innerHtml + '
'; + $tooltip.html(body).place_tt(pos.pageX + 20, pos.pageY); + }; + + this.getMultiSeriesPlotHoverInfo = function(seriesList, pos) { + var value, i, series, hoverIndex; + var results = []; + + var pointCount = seriesList[0].data.length; + for (i = 1; i < seriesList.length; i++) { + if (seriesList[i].data.length !== pointCount) { + results.pointCountMismatch = true; + return results; + } + } + + series = seriesList[0]; + hoverIndex = this.findHoverIndexFromData(pos.x, series); + var lasthoverIndex = 0; + if(!scope.panel.steppedLine) { + lasthoverIndex = hoverIndex; + } + + //now we know the current X (j) position for X and Y values + results.time = series.data[hoverIndex][0]; + var last_value = 0; //needed for stacked values + + for (i = 0; i < seriesList.length; i++) { + series = seriesList[i]; + + if (scope.panel.stack) { + if (scope.panel.tooltip.value_type === 'individual') { + value = series.data[hoverIndex][1]; + } else { + last_value += series.data[hoverIndex][1]; + value = last_value; + } + } else { + value = series.data[hoverIndex][1]; + } + + // Highlighting multiple Points depending on the plot type + if (scope.panel.steppedLine || (scope.panel.stack && scope.panel.nullPointMode == "null")) { + // stacked and steppedLine plots can have series with different length. + // Stacked series can increase its length on each new stacked serie if null points found, + // to speed the index search we begin always on the las found hoverIndex. + var newhoverIndex = this.findHoverIndexFromDataPoints(pos.x, series,lasthoverIndex); + // update lasthoverIndex depends also on the plot type. + if(!scope.panel.steppedLine) { + // on stacked graphs new will be always greater than last + lasthoverIndex = newhoverIndex; + } else { + // if steppeLine, not always series increases its length, so we should begin + // to search correct index from the original hoverIndex on each serie. + lasthoverIndex = hoverIndex; + } + + results.push({ value: value, hoverIndex: newhoverIndex }); + } else { + results.push({ value: value, hoverIndex: hoverIndex }); + } + } + + return results; + }; + + elem.mouseleave(function () { + if (scope.panel.tooltip.shared || dashboard.sharedCrosshair) { + var plot = elem.data().plot; + if (plot) { + $tooltip.detach(); + plot.unhighlight(); + scope.appEvent('clearCrosshair'); + } + } + }); + + elem.bind("plothover", function (event, pos, item) { + var plot = elem.data().plot; + var plotData = plot.getData(); + var seriesList = getSeriesFn(); + var group, value, timestamp, hoverInfo, i, series, seriesHtml; + + if(dashboard.sharedCrosshair){ + scope.appEvent('setCrosshair', { pos: pos, scope: scope }); + } + + if (seriesList.length === 0) { + return; + } + + if (scope.panel.tooltip.shared) { + plot.unhighlight(); + + var seriesHoverInfo = self.getMultiSeriesPlotHoverInfo(plotData, pos); + if (seriesHoverInfo.pointCountMismatch) { + self.showTooltip('Shared tooltip error', '
    ' + + '
  • Series point counts are not the same
  • ' + + '
  • Set null point mode to null or null as zero
  • ' + + '
  • For influxdb users set fill(0) in your query
', pos); + return; + } + + seriesHtml = ''; + timestamp = dashboard.formatDate(seriesHoverInfo.time); + + for (i = 0; i < seriesHoverInfo.length; i++) { + series = seriesList[i]; + hoverInfo = seriesHoverInfo[i]; + value = series.formatValue(hoverInfo.value); + + seriesHtml += '
'; + seriesHtml += ' ' + series.label + ':
'; + seriesHtml += '
' + value + '
'; + plot.highlight(i, hoverInfo.hoverIndex); + } + + self.showTooltip(timestamp, seriesHtml, pos); + } + // single series tooltip + else if (item) { + series = seriesList[item.seriesIndex]; + group = '
'; + group += ' ' + series.label + ':
'; + + if (scope.panel.stack && scope.panel.tooltip.value_type === 'individual') { + value = item.datapoint[1] - item.datapoint[2]; + } + else { + value = item.datapoint[1]; + } + + value = series.formatValue(value); + timestamp = dashboard.formatDate(item.datapoint[0]); + group += '
' + value + '
'; + + self.showTooltip(timestamp, group, pos); + } + // no hit + else { + $tooltip.detach(); + } + }); + } + + return GraphTooltip; +}); diff --git a/src/app/panels/graph/legend.html b/src/app/panels/graph/legend.html deleted file mode 100755 index 31458060803..00000000000 --- a/src/app/panels/graph/legend.html +++ /dev/null @@ -1,58 +0,0 @@ -
- - - -
- - - diff --git a/src/app/panels/graph/legend.js b/src/app/panels/graph/legend.js new file mode 100644 index 00000000000..f1367df8617 --- /dev/null +++ b/src/app/panels/graph/legend.js @@ -0,0 +1,162 @@ +define([ + 'angular', + 'app', + 'lodash', + 'kbn', + 'jquery', + 'jquery.flot', + 'jquery.flot.time', +], +function (angular, app, _, kbn, $) { + 'use strict'; + + var module = angular.module('grafana.panels.graph'); + + module.directive('graphLegend', function(popoverSrv) { + + return { + link: function(scope, elem) { + var $container = $('
'); + var firstRender = true; + var panel = scope.panel; + var data; + var seriesList; + var i; + + scope.$on('render', function() { + data = scope.seriesList; + if (data) { + render(); + } + }); + + function getSeriesIndexForElement(el) { + return el.parents('[data-series-index]').data('series-index'); + } + + function openColorSelector(e) { + var el = $(e.currentTarget); + var index = getSeriesIndexForElement(el); + var seriesInfo = seriesList[index]; + var popoverScope = scope.$new(); + popoverScope.series = seriesInfo; + popoverSrv.show({ + element: $(':first-child', el), + templateUrl: 'app/panels/graph/legend.popover.html', + scope: popoverScope + }); + } + + function toggleSeries(e) { + var el = $(e.currentTarget); + var index = getSeriesIndexForElement(el); + var seriesInfo = seriesList[index]; + scope.toggleSeries(seriesInfo, e); + } + + function sortLegend(e) { + var el = $(e.currentTarget); + var stat = el.data('stat'); + + if (stat !== panel.legend.sort) { panel.legend.sortDesc = null; } + + // if already sort ascending, disable sorting + if (panel.legend.sortDesc === false) { + panel.legend.sort = null; + panel.legend.sortDesc = null; + render(); + return; + } + + panel.legend.sortDesc = !panel.legend.sortDesc; + panel.legend.sort = stat; + render(); + } + + function getTableHeaderHtml(statName) { + if (!panel.legend[statName]) { return ""; } + var html = '' + statName; + + if (panel.legend.sort === statName) { + var cssClass = panel.legend.sortDesc ? 'icon-caret-down' : 'icon-caret-up' ; + html += ' '; + } + + return html + ''; + } + + function render() { + if (firstRender) { + elem.append($container); + $container.on('click', '.graph-legend-icon', openColorSelector); + $container.on('click', '.graph-legend-alias', toggleSeries); + $container.on('click', 'th', sortLegend); + firstRender = false; + } + + seriesList = data; + + $container.empty(); + + $container.toggleClass('graph-legend-table', panel.legend.alignAsTable === true); + + if (panel.legend.alignAsTable) { + var header = ''; + header += ''; + if (panel.legend.values) { + header += getTableHeaderHtml('min'); + header += getTableHeaderHtml('max'); + header += getTableHeaderHtml('avg'); + header += getTableHeaderHtml('current'); + header += getTableHeaderHtml('total'); + } + header += ''; + $container.append($(header)); + } + + if (panel.legend.sort) { + seriesList = _.sortBy(seriesList, function(series) { + return series.stats[panel.legend.sort]; + }); + if (panel.legend.sortDesc) { + seriesList = seriesList.reverse(); + } + } + + for (i = 0; i < seriesList.length; i++) { + var series = seriesList[i]; + var html = '
'; + html += '
'; + html += ''; + html += '
'; + + html += '
'; + html += '' + series.label + ''; + html += '
'; + + var avg = series.formatValue(series.stats.avg); + var current = series.formatValue(series.stats.current); + var min = series.formatValue(series.stats.min); + var max = series.formatValue(series.stats.max); + var total = series.formatValue(series.stats.total); + + if (panel.legend.values) { + if (panel.legend.min) { html += '
' + min + '
'; } + if (panel.legend.max) { html += '
' + max + '
'; } + if (panel.legend.avg) { html += '
' + avg + '
'; } + if (panel.legend.current) { html += '
' + current + '
'; } + if (panel.legend.total) { html += '
' + total + '
'; } + } + + html += '
'; + $container.append($(html)); + } + } + } + }; + }); + +}); diff --git a/src/app/panels/graph/legend.popover.html b/src/app/panels/graph/legend.popover.html new file mode 100644 index 00000000000..631f4cfeabc --- /dev/null +++ b/src/app/panels/graph/legend.popover.html @@ -0,0 +1,26 @@ +
+ × + +
+ + + +
+ +
+   +
+
+ diff --git a/src/app/panels/graph/module.html b/src/app/panels/graph/module.html index 3a6e0c8a0d1..dd9459ab6f9 100644 --- a/src/app/panels/graph/module.html +++ b/src/app/panels/graph/module.html @@ -1,25 +1,22 @@ -
+
-
-
+
+
-
- No datapoints Can be caused by timezone mismatch between browser and graphite server - Datapoints outside time range Can be caused by timezone mismatch between browser and graphite server -
+
+ No datapoints Can be caused by timezone mismatch between browser and graphite server + Datapoints outside time range Can be caused by timezone mismatch between browser and graphite server +
-
-
+
+
-
+
-
-
-
+
+
-
+
@@ -29,13 +26,13 @@
-
+
-
+
diff --git a/src/app/panels/graph/module.js b/src/app/panels/graph/module.js index bc15b851f9d..90b6957b8b5 100644 --- a/src/app/panels/graph/module.js +++ b/src/app/panels/graph/module.js @@ -6,82 +6,46 @@ define([ 'kbn', 'moment', 'components/timeSeries', - './seriesOverridesCtrl', + 'components/panelmeta', 'services/panelSrv', 'services/annotationsSrv', 'services/datasourceSrv', - 'jquery.flot', - 'jquery.flot.events', - 'jquery.flot.selection', - 'jquery.flot.time', - 'jquery.flot.stack', - 'jquery.flot.stackpercent', - 'jquery.flot.crosshair' + './seriesOverridesCtrl', + './graph', + './legend', ], -function (angular, app, $, _, kbn, moment, TimeSeries) { +function (angular, app, $, _, kbn, moment, TimeSeries, PanelMeta) { 'use strict'; var module = angular.module('grafana.panels.graph'); - app.useModule(module); module.controller('GraphCtrl', function($scope, $rootScope, panelSrv, annotationsSrv, timeSrv) { - $scope.panelMeta = { - modals : [], - editorTabs: [], - fullEditorTabs : [ - { - title: 'General', - src:'app/partials/panelgeneral.html' - }, - { - title: 'Metrics', - src:'app/partials/metrics.html' - }, - { - title:'Axes & Grid', - src:'app/panels/graph/axisEditor.html' - }, - { - title:'Display Styles', - src:'app/panels/graph/styleEditor.html' - } - ], - fullscreenEdit: true, - fullscreenView: true, - description : "Graphing" - }; + $scope.panelMeta = new PanelMeta({ + description: 'Graph panel', + fullscreen: true, + metricsEditor: true + }); + + $scope.panelMeta.addEditorTab('Axes & Grid', 'app/panels/graph/axisEditor.html'); + $scope.panelMeta.addEditorTab('Display Styles', 'app/panels/graph/styleEditor.html'); + + $scope.panelMeta.addExtendedMenuItem('Export CSV', '', 'exportCsv()'); + $scope.panelMeta.addExtendedMenuItem('Toggle legend', '', 'toggleLegend()'); // Set and populate defaults var _d = { - + // datasource name, null = default datasource datasource: null, - - /** @scratch /panels/histogram/3 - * renderer:: sets client side (flot) or native graphite png renderer (png) - */ + // sets client side (flot) or native graphite png renderer (png) renderer: 'flot', - /** @scratch /panels/histogram/3 - * x-axis:: Show the x-axis - */ + // Show/hide the x-axis 'x-axis' : true, - /** @scratch /panels/histogram/3 - * y-axis:: Show the y-axis - */ + // Show/hide y-axis 'y-axis' : true, - /** @scratch /panels/histogram/3 - * scale:: Scale the y-axis by this factor - */ - scale : 1, - /** @scratch /panels/histogram/3 - * y_formats :: 'none','bytes','bits','bps','short', 's', 'ms' - */ + // y axis formats, [left axis,right axis] y_formats : ['short', 'short'], - /** @scratch /panels/histogram/5 - * grid object:: Min and max y-axis values - * grid.min::: Minimum y-axis value - * grid.ma1::: Maximum y-axis value - */ + // grid options grid : { leftMax: null, rightMax: null, @@ -92,48 +56,23 @@ function (angular, app, $, _, kbn, moment, TimeSeries) { threshold1Color: 'rgba(216, 200, 27, 0.27)', threshold2Color: 'rgba(234, 112, 112, 0.22)' }, - - annotate : { - enable : false, - }, - - /** @scratch /panels/histogram/3 - * resolution:: If auto_int is true, shoot for this many bars. - */ - resolution : 100, - - /** @scratch /panels/histogram/3 - * ==== Drawing options - * lines:: Show line chart - */ + // show/hide lines lines : true, - /** @scratch /panels/histogram/3 - * fill:: Area fill factor for line charts, 1-10 - */ + // fill factor fill : 0, - /** @scratch /panels/histogram/3 - * linewidth:: Weight of lines in pixels - */ + // line width in pixels linewidth : 1, - /** @scratch /panels/histogram/3 - * points:: Show points on chart - */ + // show hide points points : false, - /** @scratch /panels/histogram/3 - * pointradius:: Size of points in pixels - */ + // point radius in pixels pointradius : 5, - /** @scratch /panels/histogram/3 - * bars:: Show bars on chart - */ + // show hide bars bars : false, - /** @scratch /panels/histogram/3 - * stack:: Stack multiple series - */ + // enable/disable stacking stack : false, - /** @scratch /panels/histogram/3 - * legend:: Display the legend - */ + // stack percentage mode + percentage : false, + // legend options legend: { show: true, // disable/enable legend values: false, // disable/enable legend values @@ -143,31 +82,20 @@ function (angular, app, $, _, kbn, moment, TimeSeries) { total: false, avg: false }, - /** @scratch /panels/histogram/3 - * ==== Transformations - /** @scratch /panels/histogram/3 - * percentage:: Show the y-axis as a percentage of the axis total. Only makes sense for multiple - * queries - */ - percentage : false, - /** @scratch /panels/histogram/3 - * zerofill:: Improves the accuracy of line charts at a small performance cost. - */ - zerofill : true, - + // how null points should be handled nullPointMode : 'connected', - + // staircase line mode steppedLine: false, - + // tooltip options tooltip : { value_type: 'cumulative', shared: false, }, - + // metric queries targets: [{}], - + // series color overrides aliasColors: {}, - + // other style overrides seriesOverrides: [], }; @@ -178,11 +106,17 @@ function (angular, app, $, _, kbn, moment, TimeSeries) { _.defaults($scope.panel.legend, _d.legend); $scope.hiddenSeries = {}; + $scope.seriesList = []; $scope.updateTimeRange = function () { $scope.range = timeSrv.timeRange(); $scope.rangeUnparsed = timeSrv.timeRange(false); - $scope.resolution = Math.ceil($(window).width() * ($scope.panel.span / 12)); + if ($scope.panel.maxDataPoints) { + $scope.resolution = $scope.panel.maxDataPoints; + } + else { + $scope.resolution = Math.ceil($(window).width() * ($scope.panel.span / 12)); + } $scope.interval = kbn.calculateInterval($scope.range, $scope.resolution, $scope.panel.interval); }; @@ -206,13 +140,13 @@ function (angular, app, $, _, kbn, moment, TimeSeries) { $scope.panelMeta.loading = false; $scope.panelMeta.error = err.message || "Timeseries data request error"; $scope.inspector.error = err; + $scope.seriesList = []; $scope.render([]); }); }; $scope.dataHandler = function(results) { $scope.panelMeta.loading = false; - $scope.legend = []; // png renderer returns just a url if (_.isString(results)) { @@ -224,16 +158,16 @@ function (angular, app, $, _, kbn, moment, TimeSeries) { $scope.datapointsCount = 0; $scope.datapointsOutside = false; - var data = _.map(results.data, $scope.seriesHandler); + $scope.seriesList = _.map(results.data, $scope.seriesHandler); $scope.datapointsWarning = $scope.datapointsCount === 0 || $scope.datapointsOutside; $scope.annotationsPromise .then(function(annotations) { - data.annotations = annotations; - $scope.render(data); + $scope.seriesList.annotations = annotations; + $scope.render($scope.seriesList); }, function() { - $scope.render(data); + $scope.render($scope.seriesList); }); }; @@ -242,20 +176,14 @@ function (angular, app, $, _, kbn, moment, TimeSeries) { var alias = seriesData.target; var color = $scope.panel.aliasColors[alias] || $rootScope.colors[index]; - var seriesInfo = { - alias: alias, - color: color, - }; - - $scope.legend.push(seriesInfo); - var series = new TimeSeries({ datapoints: datapoints, - info: seriesInfo, + alias: alias, + color: color, }); if (datapoints && datapoints.length > 0) { - var last = moment.utc(datapoints[datapoints.length - 1][1] * 1000); + var last = moment.utc(datapoints[datapoints.length - 1][1]); var from = moment.utc($scope.range.from); if (last - from < -10000) { $scope.datapointsOutside = true; @@ -268,7 +196,7 @@ function (angular, app, $, _, kbn, moment, TimeSeries) { }; $scope.render = function(data) { - $scope.$emit('render', data); + $scope.$broadcast('render', data); }; $scope.changeSeriesColor = function(series, color) { @@ -278,18 +206,18 @@ function (angular, app, $, _, kbn, moment, TimeSeries) { }; $scope.toggleSeries = function(serie, event) { - if ($scope.hiddenSeries[serie.alias]) { - delete $scope.hiddenSeries[serie.alias]; - } - else { - $scope.hiddenSeries[serie.alias] = true; - } - if (event.ctrlKey || event.metaKey || event.shiftKey) { + if ($scope.hiddenSeries[serie.alias]) { + delete $scope.hiddenSeries[serie.alias]; + } + else { + $scope.hiddenSeries[serie.alias] = true; + } + } else { $scope.toggleSeriesExclusiveMode(serie); } - $scope.$emit('toggleLegend', $scope.legend); + $scope.render(); }; $scope.toggleSeriesExclusiveMode = function(serie) { @@ -300,7 +228,7 @@ function (angular, app, $, _, kbn, moment, TimeSeries) { } // check if every other series is hidden - var alreadyExclusive = _.every($scope.legend, function(value) { + var alreadyExclusive = _.every($scope.seriesList, function(value) { if (value.alias === serie.alias) { return true; } @@ -310,13 +238,13 @@ function (angular, app, $, _, kbn, moment, TimeSeries) { if (alreadyExclusive) { // remove all hidden series - _.each($scope.legend, function(value) { + _.each($scope.seriesList, function(value) { delete $scope.hiddenSeries[value.alias]; }); } else { // hide all but this serie - _.each($scope.legend, function(value) { + _.each($scope.seriesList, function(value) { if (value.alias === serie.alias) { return; } @@ -341,8 +269,8 @@ function (angular, app, $, _, kbn, moment, TimeSeries) { $scope.render(); }; - $scope.addSeriesOverride = function() { - $scope.panel.seriesOverrides.push({}); + $scope.addSeriesOverride = function(override) { + $scope.panel.seriesOverrides.push(override || {}); }; $scope.removeSeriesOverride = function(override) { @@ -350,12 +278,14 @@ function (angular, app, $, _, kbn, moment, TimeSeries) { $scope.render(); }; - $scope.toggleEditorHelp = function(index) { - if ($scope.editorHelpIndex === index) { - $scope.editorHelpIndex = null; - return; - } - $scope.editorHelpIndex = index; + // Called from panel menu + $scope.toggleLegend = function() { + $scope.panel.legend.show = !$scope.panel.legend.show; + $scope.get_data(); + }; + + $scope.exportCsv = function() { + kbn.exportSeriesListToCsv($scope.seriesList); }; panelSrv.init($scope); diff --git a/src/app/panels/graph/seriesOverridesCtrl.js b/src/app/panels/graph/seriesOverridesCtrl.js index 1b6b1dfc144..04d66ee5e73 100644 --- a/src/app/panels/graph/seriesOverridesCtrl.js +++ b/src/app/panels/graph/seriesOverridesCtrl.js @@ -23,7 +23,7 @@ define([ option.submenu = _.map(values, function(value, index) { return { text: String(value), - click: 'setOverride(' + option.index + ',' + index + ')' + click: 'menuItemSelected(' + option.index + ',' + index + ')' }; }); @@ -34,6 +34,14 @@ define([ var option = $scope.overrideMenu[optionIndex]; var value = option.values[valueIndex]; $scope.override[option.propertyName] = value; + + // automatically disable lines for this series and the fill bellow to series + // can be removed by the user if they still want lines + if (option.propertyName === 'fillBelowTo') { + $scope.override['lines'] = false; + $scope.addSeriesOverride({ alias: value, lines: false }); + } + $scope.updateCurrentOverrides(); $scope.render(); }; @@ -45,8 +53,8 @@ define([ }; $scope.getSeriesNames = function() { - return _.map($scope.legend, function(info) { - return info.alias; + return _.map($scope.seriesList, function(series) { + return series.alias; }); }; @@ -67,6 +75,7 @@ define([ $scope.addOverrideOption('Lines', 'lines', [true, false]); $scope.addOverrideOption('Line fill', 'fill', [0,1,2,3,4,5,6,7,8,9,10]); $scope.addOverrideOption('Line width', 'linewidth', [0,1,2,3,4,5,6,7,8,9,10]); + $scope.addOverrideOption('Fill below to', 'fillBelowTo', $scope.getSeriesNames()); $scope.addOverrideOption('Staircase line', 'steppedLine', [true, false]); $scope.addOverrideOption('Points', 'points', [true, false]); $scope.addOverrideOption('Points Radius', 'pointradius', [1,2,3,4,5]); diff --git a/src/app/panels/graph/styleEditor.html b/src/app/panels/graph/styleEditor.html index 494d6bc1f7f..f230c2ba20a 100644 --- a/src/app/panels/graph/styleEditor.html +++ b/src/app/panels/graph/styleEditor.html @@ -88,11 +88,10 @@ {{option.name}}: {{option.value}} - +
diff --git a/src/app/panels/singlestat/editor.html b/src/app/panels/singlestat/editor.html new file mode 100644 index 00000000000..885b1f20077 --- /dev/null +++ b/src/app/panels/singlestat/editor.html @@ -0,0 +1,76 @@ +
+
+
Big value
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
Big value font size
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
Formats
+
+ + +
+
+
+
Coloring
+ + +
+ + +
+
+ + + + + invert order +
+
+
+ +
+
+
Spark lines
+ + +
+ + +
+
+ + +
+
+
+ +
+ diff --git a/src/app/panels/singlestat/module.html b/src/app/panels/singlestat/module.html new file mode 100644 index 00000000000..e59fb82f303 --- /dev/null +++ b/src/app/panels/singlestat/module.html @@ -0,0 +1,26 @@ +
+ +
+ +
+ +
+
+
+ + Singlestat +
+ +
+
+
+
+
+ +
+
+
+
+
+
+
diff --git a/src/app/panels/singlestat/module.js b/src/app/panels/singlestat/module.js new file mode 100644 index 00000000000..c17b5afea38 --- /dev/null +++ b/src/app/panels/singlestat/module.js @@ -0,0 +1,197 @@ +define([ + 'angular', + 'app', + 'lodash', + 'components/timeSeries', + 'kbn', + 'components/panelmeta', + 'services/panelSrv', + './singleStatPanel', +], +function (angular, app, _, TimeSeries, kbn, PanelMeta) { + 'use strict'; + + var module = angular.module('grafana.panels.singlestat'); + app.useModule(module); + + module.controller('SingleStatCtrl', function($scope, panelSrv, timeSrv) { + + $scope.panelMeta = new PanelMeta({ + description: 'Singlestat panel', + titlePos: 'left', + fullscreen: true, + metricsEditor: true + }); + + $scope.panelMeta.addEditorTab('Options', 'app/panels/singlestat/editor.html'); + + // Set and populate defaults + var _d = { + links: [], + maxDataPoints: 100, + interval: null, + targets: [{}], + cacheTimeout: null, + format: 'none', + prefix: '', + postfix: '', + valueName: 'avg', + prefixFontSize: '50%', + valueFontSize: '100%', + postfixFontSize: '50%', + thresholds: '', + colorBackground: false, + colorValue: false, + colors: ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"], + sparkline: { + show: false, + full: false, + lineColor: 'rgb(31, 120, 193)', + fillColor: 'rgba(31, 118, 189, 0.18)', + } + }; + + _.defaults($scope.panel, _d); + + $scope.init = function() { + panelSrv.init($scope); + $scope.$on('refresh', $scope.get_data); + }; + + $scope.updateTimeRange = function () { + $scope.range = timeSrv.timeRange(); + $scope.rangeUnparsed = timeSrv.timeRange(false); + $scope.resolution = $scope.panel.maxDataPoints; + $scope.interval = kbn.calculateInterval($scope.range, $scope.resolution, $scope.panel.interval); + }; + + $scope.get_data = function() { + $scope.updateTimeRange(); + + var metricsQuery = { + range: $scope.rangeUnparsed, + interval: $scope.interval, + targets: $scope.panel.targets, + maxDataPoints: $scope.resolution, + cacheTimeout: $scope.panel.cacheTimeout + }; + + return $scope.datasource.query(metricsQuery) + .then($scope.dataHandler) + .then(null, function(err) { + console.log("err"); + $scope.panelMeta.loading = false; + $scope.panelMeta.error = err.message || "Timeseries data request error"; + $scope.inspector.error = err; + $scope.render(); + }); + }; + + $scope.dataHandler = function(results) { + $scope.panelMeta.loading = false; + $scope.series = _.map(results.data, $scope.seriesHandler); + $scope.render(); + }; + + $scope.seriesHandler = function(seriesData) { + var series = new TimeSeries({ + datapoints: seriesData.datapoints, + alias: seriesData.target, + }); + + series.flotpairs = series.getFlotPairs('connected'); + + return series; + }; + + $scope.setColoring = function(options) { + if (options.background) { + $scope.panel.colorValue = false; + $scope.panel.colors = ['rgba(71, 212, 59, 0.4)', 'rgba(245, 150, 40, 0.73)', 'rgba(225, 40, 40, 0.59)']; + } + else { + $scope.panel.colorBackground = false; + $scope.panel.colors = ['rgba(50, 172, 45, 0.97)', 'rgba(237, 129, 40, 0.89)', 'rgba(245, 54, 54, 0.9)']; + } + $scope.render(); + }; + + $scope.invertColorOrder = function() { + var tmp = $scope.panel.colors[0]; + $scope.panel.colors[0] = $scope.panel.colors[2]; + $scope.panel.colors[2] = tmp; + $scope.render(); + }; + + $scope.getDecimalsForValue = function(value) { + var opts = {}; + if (value === 0) { + return { decimals: 0, scaledDecimals: 0 }; + } + + var delta = value / 2; + var dec = -Math.floor(Math.log(delta) / Math.LN10); + + var magn = Math.pow(10, -dec), + norm = delta / magn, // norm is between 1.0 and 10.0 + size; + + if (norm < 1.5) { + size = 1; + } else if (norm < 3) { + size = 2; + // special case for 2.5, requires an extra decimal + if (norm > 2.25) { + size = 2.5; + ++dec; + } + } else if (norm < 7.5) { + size = 5; + } else { + size = 10; + } + + size *= magn; + + if (opts.minTickSize != null && size < opts.minTickSize) { + size = opts.minTickSize; + } + + var result = {}; + result.decimals = Math.max(0, dec); + result.scaledDecimals = result.decimals - Math.floor(Math.log(size) / Math.LN11) + 2; + + return result; + }; + + $scope.render = function() { + var data = {}; + + if (!$scope.series || $scope.series.length === 0) { + data.flotpairs = []; + data.mainValue = Number.NaN; + data.mainValueFormated = 'NaN'; + } + else { + var series = $scope.series[0]; + data.mainValue = series.stats[$scope.panel.valueName]; + var decimalInfo = $scope.getDecimalsForValue(data.mainValue); + var formatFunc = kbn.valueFormats[$scope.panel.format]; + + data.mainValueFormated = formatFunc(data.mainValue, decimalInfo.decimals, decimalInfo.scaledDecimals); + data.flotpairs = series.flotpairs; + } + + data.thresholds = $scope.panel.thresholds.split(',').map(function(strVale) { + return Number(strVale.trim()); + }); + + data.colorMap = $scope.panel.colors; + + $scope.data = data; + $scope.$emit('render'); + }; + + $scope.init(); + }); +}); diff --git a/src/app/panels/singlestat/singleStatPanel.js b/src/app/panels/singlestat/singleStatPanel.js new file mode 100644 index 00000000000..d1131f29e94 --- /dev/null +++ b/src/app/panels/singlestat/singleStatPanel.js @@ -0,0 +1,204 @@ +define([ + 'angular', + 'app', + 'lodash', + 'jquery', + 'jquery.flot', +], +function (angular, app, _, $) { + 'use strict'; + + var module = angular.module('grafana.panels.singlestat', []); + app.useModule(module); + + module.directive('singlestatPanel', function($location, linkSrv, $timeout) { + + return { + link: function(scope, elem) { + var data, panel; + var $panelContainer = elem.parents('.panel-container'); + + scope.$on('render', function() { + render(); + }); + + function setElementHeight() { + try { + var height = scope.height || panel.height || scope.row.height; + if (_.isString(height)) { + height = parseInt(height.replace('px', ''), 10); + } + + height -= panel.title ? 24 : 9; // subtract panel title bar + + elem.css('height', height + 'px'); + + return true; + } catch(e) { // IE throws errors sometimes + return false; + } + } + + function applyColoringThresholds(value, valueString) { + if (!panel.colorValue) { + return valueString; + } + + var color = getColorForValue(value); + if (color) { + return ''+ valueString + ''; + } + + return valueString; + } + + function getColorForValue(value) { + for (var i = data.thresholds.length - 1; i >= 0 ; i--) { + if (value > data.thresholds[i]) { + return data.colorMap[i]; + } + } + return null; + } + + function getSpan(className, fontSize, value) { + return '' + + value + ''; + } + + function getBigValueHtml() { + var body = '
'; + + if (panel.prefix) { body += getSpan('singlestat-panel-prefix', panel.prefixFontSize, scope.panel.prefix); } + + var value = applyColoringThresholds(data.mainValue, data.mainValueFormated); + body += getSpan('singlestat-panel-value', panel.valueFontSize, value); + + if (panel.postfix) { body += getSpan('singlestat-panel-postfix', panel.postfixFontSize, panel.postfix); } + + body += '
'; + + return body; + } + + function addSparkline() { + var panel = scope.panel; + var width = elem.width() + 20; + var height = elem.height() || 100; + + var plotCanvas = $('
'); + var plotCss = {}; + plotCss.position = 'absolute'; + + if (panel.sparkline.full) { + plotCss.bottom = '5px'; + plotCss.left = '-5px'; + plotCss.width = (width - 10) + 'px'; + plotCss.height = (height - 45) + 'px'; + } + else { + plotCss.bottom = "0px"; + plotCss.left = "-5px"; + plotCss.width = (width - 10) + 'px'; + plotCss.height = Math.floor(height * 0.25) + "px"; + } + + plotCanvas.css(plotCss); + + var options = { + legend: { show: false }, + series: { + lines: { + show: true, + fill: 1, + lineWidth: 1, + fillColor: panel.sparkline.fillColor, + }, + }, + yaxes: { show: false }, + xaxis: { + show: false, + mode: "time", + min: scope.range.from.getTime(), + max: scope.range.to.getTime(), + }, + grid: { hoverable: false, show: false }, + }; + + elem.append(plotCanvas); + + var plotSeries = { + data: data.flotpairs, + color: panel.sparkline.lineColor + }; + + setTimeout(function() { + $.plot(plotCanvas, [plotSeries], options); + }, 10); + } + + function render() { + if (!scope.data) { return; } + + data = scope.data; + panel = scope.panel; + + setElementHeight(); + + var body = getBigValueHtml(); + + if (panel.colorBackground && data.mainValue) { + var color = getColorForValue(data.mainValue); + if (color) { + $panelContainer.css('background-color', color); + if (scope.fullscreen) { + elem.css('background-color', color); + } else { + elem.css('background-color', ''); + } + } + } else { + $panelContainer.css('background-color', ''); + elem.css('background-color', ''); + } + + elem.html(body); + + if (panel.sparkline.show) { + addSparkline(); + } + + elem.toggleClass('pointer', panel.links.length > 0); + } + + // drilldown link tooltip + var drilldownTooltip = $('
gello
"'); + + elem.mouseleave(function() { + if (panel.links.length === 0) { return;} + drilldownTooltip.detach(); + }); + + elem.click(function() { + if (panel.links.length === 0) { return; } + + var linkInfo = linkSrv.getPanelLinkAnchorInfo(panel.links[0]); + if (linkInfo.href[0] === '#') { linkInfo.href = linkInfo.href.substring(1); } + + $timeout(function() { $location.url(linkInfo.href); }); + + drilldownTooltip.detach(); + }); + + elem.mousemove(function(e) { + if (panel.links.length === 0) { return;} + + drilldownTooltip.text('click to go to: ' + panel.links[0].title); + + drilldownTooltip.place_tt(e.clientX+20, e.clientY-15); + }); + } + }; + }); + +}); diff --git a/src/app/panels/text/module.js b/src/app/panels/text/module.js index d778e877031..ceb6bcf745c 100644 --- a/src/app/panels/text/module.js +++ b/src/app/panels/text/module.js @@ -3,8 +3,9 @@ define([ 'app', 'lodash', 'require', + 'components/panelmeta', ], -function (angular, app, _, require) { +function (angular, app, _, require, PanelMeta) { 'use strict'; var module = angular.module('grafana.panels.text', []); @@ -14,13 +15,15 @@ function (angular, app, _, require) { module.controller('text', function($scope, templateSrv, $sce, panelSrv) { - $scope.panelMeta = { + $scope.panelMeta = new PanelMeta({ description : "A static text panel that can use plain text, markdown, or (sanitized) HTML" - }; + }); + + $scope.panelMeta.addEditorTab('Edit text', 'app/panels/text/editor.html'); // Set and populate defaults var _d = { - title: 'default title', + title : 'default title', mode : "markdown", // 'html', 'markdown', 'text' content : "", style: {}, @@ -29,7 +32,7 @@ function (angular, app, _, require) { _.defaults($scope.panel, _d); $scope.init = function() { - panelSrv.init(this); + panelSrv.init($scope); $scope.ready = false; $scope.$on('refresh', $scope.render); $scope.render(); diff --git a/src/app/panels/timepicker/module.html b/src/app/panels/timepicker/module.html index 9371eb16f2e..c4cd4069e91 100644 --- a/src/app/panels/timepicker/module.html +++ b/src/app/panels/timepicker/module.html @@ -10,19 +10,26 @@ }
-
@@ -177,7 +208,18 @@
+
+
Max data points
+
    +
  • Every graphite request is issued with a maxDataPoints parameter
  • +
  • Graphite uses this parameter to consolidate the real number of values down to this number
  • +
  • If there are more real values, then by default they will be consolidated using averages
  • +
  • This could hide real peaks and max values in your series
  • +
  • You can change how point consolidation is made using the consolidateBy graphite function
  • +
  • Point consolidation will effect series legend values (min,max,total,current)
  • +
  • If you override maxDataPoint and set a high value performance can be severely effected
  • +
+
+
- - diff --git a/src/app/partials/help_modal.html b/src/app/partials/help_modal.html new file mode 100644 index 00000000000..866d44898eb --- /dev/null +++ b/src/app/partials/help_modal.html @@ -0,0 +1,50 @@ + + + diff --git a/src/app/partials/influxdb/editor.html b/src/app/partials/influxdb/editor.html index 4fd13645d3d..7fe41c1d268 100644 --- a/src/app/partials/influxdb/editor.html +++ b/src/app/partials/influxdb/editor.html @@ -15,26 +15,26 @@ tabindex="1"> - - -
  • - - - -
  • - + + +
  • + + + +
  • + -
    +
    diff --git a/src/app/partials/opentsdb/editor.html b/src/app/partials/opentsdb/editor.html index b74b1d7a269..51a575d933c 100644 --- a/src/app/partials/opentsdb/editor.html +++ b/src/app/partials/opentsdb/editor.html @@ -89,6 +89,32 @@ ng-model="target.isCounter" ng-change="targetBlur()"> +
  • + Counter Max: +
  • +
  • + +
  • +
  • + Counter Reset Value: +
  • +
  • + +
  • Alias:
  • diff --git a/src/app/partials/paneleditor.html b/src/app/partials/paneleditor.html index ebc8a40ac63..cd2555fbf8a 100644 --- a/src/app/partials/paneleditor.html +++ b/src/app/partials/paneleditor.html @@ -5,22 +5,14 @@
    -
    +
    -
    -
    -
    - -
    -
    -
    - -
    +
    diff --git a/src/app/partials/panelgeneral.html b/src/app/partials/panelgeneral.html index e0df648db0a..21fbf68c68f 100644 --- a/src/app/partials/panelgeneral.html +++ b/src/app/partials/panelgeneral.html @@ -12,3 +12,7 @@
    + + + + diff --git a/src/app/partials/search.html b/src/app/partials/search.html index ccddfb14474..2d220b206fd 100644 --- a/src/app/partials/search.html +++ b/src/app/partials/search.html @@ -22,7 +22,7 @@ + ng-keydown="keyDown($event)" ng-model="query.query" ng-model-options="{ debounce: 500 }" spellcheck='false' ng-change="search()" /> tags diff --git a/src/app/routes/dashboard-from-script.js b/src/app/routes/dashboard-from-script.js index fa3abd36e81..b73a49b0cbe 100644 --- a/src/app/routes/dashboard-from-script.js +++ b/src/app/routes/dashboard-from-script.js @@ -16,21 +16,28 @@ function (angular, $, config, _, kbn, moment) { .when('/dashboard/script/:jsFile', { templateUrl: 'app/partials/dashboard.html', controller : 'DashFromScriptProvider', + reloadOnSearch: false, }); }); - module.controller('DashFromScriptProvider', function($scope, $rootScope, $http, $routeParams, alertSrv, $q) { + module.controller('DashFromScriptProvider', function($scope, $rootScope, $http, $routeParams, $q, dashboardSrv, datasourceSrv, $timeout) { var execute_script = function(result) { + var services = { + dashboardSrv: dashboardSrv, + datasourceSrv: datasourceSrv, + $q: $q, + }; + /*jshint -W054 */ - var script_func = new Function('ARGS','kbn','_','moment','window','document','$','jQuery', result.data); - var script_result = script_func($routeParams, kbn, _ , moment, window, document, $, $); + var script_func = new Function('ARGS','kbn','_','moment','window','document','$','jQuery', 'services', result.data); + var script_result = script_func($routeParams, kbn, _ , moment, window, document, $, $, services); // Handle async dashboard scripts if (_.isFunction(script_result)) { var deferred = $q.defer(); script_result(function(dashboard) { - $rootScope.$apply(function() { + $timeout(function() { deferred.resolve({ data: dashboard }); }); }); @@ -47,7 +54,7 @@ function (angular, $, config, _, kbn, moment) { .then(execute_script) .then(null,function(err) { console.log('Script dashboard error '+ err); - alertSrv.set('Error', "Could not load scripts/"+file+". Please make sure it exists and returns a valid dashboard", 'error'); + $scope.appEvent('alert-error', ["Script Error", "Please make sure it exists and returns a valid dashboard"]); return false; }); }; diff --git a/src/app/services/alertSrv.js b/src/app/services/alertSrv.js index f41e490f374..74b3308c4e8 100644 --- a/src/app/services/alertSrv.js +++ b/src/app/services/alertSrv.js @@ -7,7 +7,7 @@ function (angular, _) { var module = angular.module('grafana.services'); - module.service('alertSrv', function($timeout, $sce, $rootScope) { + module.service('alertSrv', function($timeout, $sce, $rootScope, $modal, $q) { var self = this; this.init = function() { @@ -20,6 +20,7 @@ function (angular, _) { $rootScope.onAppEvent('alert-success', function(e, alert) { self.set(alert[0], alert[1], 'success', 3000); }); + $rootScope.onAppEvent('confirm-modal', this.showConfirmModal); }; // List of all alert objects @@ -57,5 +58,28 @@ function (angular, _) { this.clearAll = function() { self.list = []; }; + + this.showConfirmModal = function(e, payload) { + var scope = $rootScope.$new(); + + scope.title = payload.title; + scope.text = payload.text; + scope.onConfirm = payload.onConfirm; + + var confirmModal = $modal({ + template: './app/partials/confirm_modal.html', + persist: true, + modalClass: 'confirm-modal', + show: false, + scope: scope, + keyboard: false + }); + + $q.when(confirmModal).then(function(modalEl) { + modalEl.modal('show'); + }); + + }; + }); }); diff --git a/src/app/services/all.js b/src/app/services/all.js index f715361b4cd..efb8c79fb15 100644 --- a/src/app/services/all.js +++ b/src/app/services/all.js @@ -7,9 +7,9 @@ define([ './templateValuesSrv', './panelSrv', './timer', - './panelMove', './keyboardManager', './annotationsSrv', + './popoverSrv', './playlistSrv', './unsavedChangesSrv', './dashboard/dashboardKeyBindings', diff --git a/src/app/services/annotationsSrv.js b/src/app/services/annotationsSrv.js index 25c76caeb77..fdc5e9ca745 100644 --- a/src/app/services/annotationsSrv.js +++ b/src/app/services/annotationsSrv.js @@ -58,7 +58,7 @@ define([ function errorHandler(err) { console.log('Annotation error: ', err); - var message = err.message || "Aannotation query failed"; + var message = err.message || "Annotation query failed"; alertSrv.set('Annotations error', message,'error'); } diff --git a/src/app/services/dashboard/dashboardKeyBindings.js b/src/app/services/dashboard/dashboardKeyBindings.js index 987bb5a71ad..1557a3848d3 100644 --- a/src/app/services/dashboard/dashboardKeyBindings.js +++ b/src/app/services/dashboard/dashboardKeyBindings.js @@ -1,14 +1,13 @@ define([ 'angular', 'jquery', - 'services/all' ], function(angular, $) { "use strict"; var module = angular.module('grafana.services'); - module.service('dashboardKeybindings', function($rootScope, keyboardManager) { + module.service('dashboardKeybindings', function($rootScope, keyboardManager, $modal, $q) { this.shortcuts = function(scope) { @@ -22,6 +21,24 @@ function(angular, $) { keyboardManager.unbind('esc'); }); + var helpModalScope = null; + keyboardManager.bind('shift+?', function() { + if (helpModalScope) { return; } + + helpModalScope = $rootScope.$new(); + var helpModal = $modal({ + template: './app/partials/help_modal.html', + persist: false, + show: false, + scope: helpModalScope, + keyboard: false + }); + + helpModalScope.$on('$destroy', function() { helpModalScope = null; }); + $q.when(helpModal).then(function(modalEl) { modalEl.modal('show'); }); + + }, { inputDisabled: true }); + keyboardManager.bind('ctrl+f', function() { scope.appEvent('show-dash-editor', { src: 'app/partials/search.html' }); }, { inputDisabled: true }); @@ -32,6 +49,10 @@ function(angular, $) { scope.dashboard.emit_refresh('refresh'); }, { inputDisabled: true }); + keyboardManager.bind('ctrl+l', function() { + scope.$broadcast('toggle-all-legends'); + }, { inputDisabled: true }); + keyboardManager.bind('ctrl+h', function() { var current = scope.dashboard.hideControls; scope.dashboard.hideControls = !current; diff --git a/src/app/services/dashboard/dashboardSrv.js b/src/app/services/dashboard/dashboardSrv.js index cb33421a3e8..e8f0a808279 100644 --- a/src/app/services/dashboard/dashboardSrv.js +++ b/src/app/services/dashboard/dashboardSrv.js @@ -35,6 +35,7 @@ function (angular, $, kbn, _, moment) { this.annotations = this._ensureListExist(data.annotations); this.refresh = data.refresh; this.version = data.version || 0; + this.hideAllLegends = data.hideAllLegends || false; if (this.nav.length === 0) { this.nav.push({ type: 'timepicker' }); @@ -91,6 +92,26 @@ function (angular, $, kbn, _, moment) { row.panels.push(panel); }; + p.getPanelInfoById = function(panelId) { + var result = {}; + _.each(this.rows, function(row) { + _.each(row.panels, function(panel, index) { + if (panel.id === panelId) { + result.panel = panel; + result.row = row; + result.index = index; + return; + } + }); + }); + + if (!result.panel) { + return null; + } + + return result; + }; + p.duplicatePanel = function(panel, row) { var rowIndex = _.indexOf(this.rows, row); var newPanel = angular.copy(panel); diff --git a/src/app/services/dashboard/dashboardViewStateSrv.js b/src/app/services/dashboard/dashboardViewStateSrv.js index 2f85da5d27a..c08d3cdba6b 100644 --- a/src/app/services/dashboard/dashboardViewStateSrv.js +++ b/src/app/services/dashboard/dashboardViewStateSrv.js @@ -32,33 +32,34 @@ function (angular, _, $) { }); this.update(this.getQueryStringState(), true); + this.expandRowForPanel(); } + DashboardViewState.prototype.expandRowForPanel = function() { + if (!this.state.panelId) { return; } + + var panelInfo = this.$scope.dashboard.getPanelInfoById(this.state.panelId); + if (panelInfo) { + panelInfo.row.collapse = false; + } + }; + DashboardViewState.prototype.needsSync = function(urlState) { return _.isEqual(this.state, urlState) === false; }; DashboardViewState.prototype.getQueryStringState = function() { - var queryParams = $location.search(); - var urlState = { - panelId: parseInt(queryParams.panelId) || null, - fullscreen: queryParams.fullscreen ? true : false, - edit: queryParams.edit ? true : false, - }; - - _.each(queryParams, function(value, key) { - if (key.indexOf('var-') !== 0) { return; } - urlState[key] = value; - }); - - return urlState; + var state = $location.search(); + state.panelId = parseInt(state.panelId) || null; + state.fullscreen = state.fullscreen ? true : null; + state.edit = (state.edit === "true" || state.edit === true) || null; + return state; }; DashboardViewState.prototype.serializeToUrl = function() { var urlState = _.clone(this.state); urlState.fullscreen = this.state.fullscreen ? true : null, urlState.edit = this.state.edit ? true : null; - return urlState; }; @@ -68,7 +69,8 @@ function (angular, _, $) { if (!this.state.fullscreen) { this.state.panelId = null; - this.state.edit = false; + this.state.fullscreen = null; + this.state.edit = null; } if (!skipUrlSync) { diff --git a/src/app/services/elasticsearch/es-datasource.js b/src/app/services/elasticsearch/es-datasource.js index f9ec01dbd4c..843bf8addf3 100644 --- a/src/app/services/elasticsearch/es-datasource.js +++ b/src/app/services/elasticsearch/es-datasource.js @@ -224,7 +224,7 @@ function (angular, _, config, kbn, moment) { var endsInOpen = function(string, opener, closer) { var character; var count = 0; - for (var i=0; i 0 && query[0] !== '/') { + query = '/' + query + '/'; + } + + return this._seriesQuery('list series ' + query).then(function(data) { if (!data || data.length === 0) { return []; } @@ -141,7 +146,6 @@ function (angular, _, kbn, InfluxSeries, InfluxQueryBuilder) { InfluxDatasource.prototype._seriesQuery = function(query) { return this._influxRequest('GET', '/series', { q: query, - time_precision: 's', }); }; diff --git a/src/app/services/keyboardManager.js b/src/app/services/keyboardManager.js index 9875d74964f..10d5db03f45 100644 --- a/src/app/services/keyboardManager.js +++ b/src/app/services/keyboardManager.js @@ -61,6 +61,7 @@ function (angular) { else if (e.which) { code = e.which; } + var character = String.fromCharCode(code).toLowerCase(); if (code === 188) { @@ -93,6 +94,9 @@ function (angular) { ",": "<", ".": ">", "/": "?", + "»": "?", + "«": "?", + "¿": "?", "\\": "|" }; // Special Keys - and their codes @@ -277,4 +281,4 @@ function (angular) { return keyboardManagerService; }]); -}); \ No newline at end of file +}); diff --git a/src/app/services/opentsdb/opentsdbDatasource.js b/src/app/services/opentsdb/opentsdbDatasource.js index f6a36c6f035..96784cbc992 100644 --- a/src/app/services/opentsdb/opentsdbDatasource.js +++ b/src/app/services/opentsdb/opentsdbDatasource.js @@ -1,14 +1,15 @@ define([ 'angular', 'lodash', - 'kbn' + 'kbn', + 'moment' ], function (angular, _, kbn) { 'use strict'; var module = angular.module('grafana.services'); - module.factory('OpenTSDBDatasource', function($q, $http) { + module.factory('OpenTSDBDatasource', function($q, $http, templateSrv) { function OpenTSDBDatasource(datasource) { this.type = 'opentsdb'; @@ -99,7 +100,7 @@ function (angular, _, kbn) { // TSDB returns datapoints has a hash of ts => value. // Can't use _.pairs(invert()) because it stringifies keys/values _.each(md.dps, function (v, k) { - dps.push([v, k]); + dps.push([v, k * 1000]); }); return { target: metricLabel, datapoints: dps }; @@ -123,12 +124,12 @@ function (angular, _, kbn) { } var query = { - metric: target.metric, + metric: templateSrv.replace(target.metric), aggregator: "avg" }; if (target.aggregator) { - query.aggregator = target.aggregator; + query.aggregator = templateSrv.replace(target.aggregator); } if (target.shouldComputeRate) { @@ -136,6 +137,14 @@ function (angular, _, kbn) { query.rateOptions = { counter: !!target.isCounter }; + + if (target.counterMax && target.counterMax.length) { + query.rateOptions.counterMax = parseInt(target.counterMax); + } + + if (target.counterResetValue && target.counterResetValue.length) { + query.rateOptions.resetValue = parseInt(target.counterResetValue); + } } if (target.shouldDownsample) { @@ -143,6 +152,11 @@ function (angular, _, kbn) { } query.tags = angular.copy(target.tags); + if(query.tags){ + for(var key in query.tags){ + query.tags[key] = templateSrv.replace(query.tags[key]); + } + } return query; } diff --git a/src/app/services/panelMove.js b/src/app/services/panelMove.js deleted file mode 100644 index aa32400e907..00000000000 --- a/src/app/services/panelMove.js +++ /dev/null @@ -1,85 +0,0 @@ -define([ - 'angular', - 'lodash' -], -function (angular, _) { - 'use strict'; - - var module = angular.module('grafana.services'); - - module.service('panelMoveSrv', function($rootScope) { - - function PanelMoveSrv(dashboard) { - this.dashboard = dashboard; - _.bindAll(this, 'onStart', 'onOver', 'onOut', 'onDrop', 'onStop', 'cleanup'); - } - - var p = PanelMoveSrv.prototype; - - /* each of these can take event,ui,data parameters */ - p.onStart = function() { - this.dashboard.$$panelDragging = true; - $rootScope.$apply(); - }; - - p.onOver = function() { - $rootScope.$apply(); - }; - - p.onOut = function() { - $rootScope.$apply(); - }; - - /* - Use our own drop logic. the $parent.$parent this is ugly. - */ - p.onDrop = function(event,ui,data) { - var - dragRow = data.draggableScope.$parent.$parent.row.panels, - dropRow = data.droppableScope.$parent.$parent.row.panels, - dragIndex = data.dragSettings.index, - dropIndex = data.dropSettings.index; - - // Remove panel from source row - dragRow.splice(dragIndex,1); - - // Add to destination row - if (!_.isUndefined(dropRow)) { - dropRow.splice(dropIndex,0,data.dragItem); - } - - this.dashboard.$$panelDragging = false; - // Cleanup nulls/undefined left behind - this.cleanup(); - $rootScope.$apply(); - $rootScope.$broadcast('render'); - }; - - p.onStop = function() { - this.dashboard.$$panelDragging = false; - this.cleanup(); - $rootScope.$apply(); - }; - - p.cleanup = function () { - _.each(this.dashboard.rows, function(row) { - row.panels = _.without(row.panels,{}); - row.panels = _.compact(row.panels); - }); - }; - - return { - init: function(dashboard, scope) { - var panelMove = new PanelMoveSrv(dashboard); - - scope.panelMoveDrop = panelMove.onDrop; - scope.panelMoveStart = panelMove.onStart; - scope.panelMoveStop = panelMove.onStop; - scope.panelMoveOver = panelMove.onOver; - scope.panelMoveOut = panelMove.onOut; - } - }; - - }); - -}); diff --git a/src/app/services/panelSrv.js b/src/app/services/panelSrv.js index 4563f3ad6c8..3cc945624b3 100644 --- a/src/app/services/panelSrv.js +++ b/src/app/services/panelSrv.js @@ -11,44 +11,10 @@ function (angular, _) { this.init = function($scope) { if (!$scope.panel.span) { $scope.panel.span = 12; } - var menu = [ - { - text: "view", - icon: "icon-eye-open", - click: 'toggleFullscreen(false)', - condition: $scope.panelMeta.fullscreenView - }, - { - text: 'edit', - icon: 'icon-cogs', - click: 'editPanel()', - condition: true, - }, - { - text: 'duplicate', - icon: 'icon-copy', - click: 'duplicatePanel(panel)', - condition: true - }, - { - text: 'json', - icon: 'icon-code', - click: 'editPanelJson()', - condition: true - }, - { - text: 'share', - icon: 'icon-share', - click: 'sharePanel()', - condition: true - }, - ]; - $scope.inspector = {}; - $scope.panelMeta.menu = _.where(menu, { condition: true }); $scope.editPanel = function() { - if ($scope.panelMeta.fullscreenEdit) { + if ($scope.panelMeta.fullscreen) { $scope.toggleFullscreen(true); } else { @@ -67,6 +33,10 @@ function (angular, _) { $scope.appEvent('show-json-editor', { object: $scope.panel, updateHandler: $scope.replacePanel }); }; + $scope.duplicatePanel = function() { + $scope.dashboard.duplicatePanel($scope.panel, $scope.row); + }; + $scope.updateColumnSpan = function(span) { $scope.panel.span = Math.min(Math.max($scope.panel.span + span, 1), 12); @@ -99,6 +69,14 @@ function (angular, _) { $scope.get_data(); }; + $scope.toggleEditorHelp = function(index) { + if ($scope.editorHelpIndex === index) { + $scope.editorHelpIndex = null; + return; + } + $scope.editorHelpIndex = index; + }; + $scope.toggleFullscreen = function(edit) { $scope.dashboardViewState.update({ fullscreen: true, edit: edit, panelId: $scope.panel.id }); }; @@ -110,9 +88,6 @@ function (angular, _) { // Post init phase $scope.fullscreen = false; $scope.editor = { index: 1 }; - if ($scope.panelMeta.fullEditorTabs) { - $scope.editorTabs = _.pluck($scope.panelMeta.fullEditorTabs, 'title'); - } $scope.datasources = datasourceSrv.getMetricSources(); $scope.setDatasource($scope.panel.datasource); diff --git a/src/app/services/popoverSrv.js b/src/app/services/popoverSrv.js new file mode 100644 index 00000000000..a1e60525e2d --- /dev/null +++ b/src/app/services/popoverSrv.js @@ -0,0 +1,46 @@ +define([ + 'angular', + 'lodash', +], +function (angular, _) { + 'use strict'; + + var module = angular.module('grafana.services'); + + module.service('popoverSrv', function($templateCache, $timeout, $q, $http, $compile) { + + this.getTemplate = function(url) { + return $q.when($templateCache.get(url) || $http.get(url, {cache: true})); + }; + + this.show = function(options) { + var popover = options.element.data('popover'); + if (popover) { + popover.scope.$destroy(); + popover.destroy(); + return; + } + + this.getTemplate(options.templateUrl).then(function(result) { + var template = _.isString(result) ? result : result.data; + + options.element.popover({ + content: template, + placement: 'bottom', + html: true + }); + + popover = options.element.data('popover'); + popover.hasContent = function () { + return template; + }; + + popover.toggle(); + popover.scope = options.scope; + $compile(popover.$tip)(popover.scope); + }); + }; + + }); + +}); diff --git a/src/app/services/templateValuesSrv.js b/src/app/services/templateValuesSrv.js index 9b80c336f4b..51b11dfb3fa 100644 --- a/src/app/services/templateValuesSrv.js +++ b/src/app/services/templateValuesSrv.js @@ -30,6 +30,7 @@ function (angular, _, kbn) { var option = _.findWhere(variable.options, { text: urlValue }); option = option || { text: urlValue, value: urlValue }; this.setVariableValue(variable, option, true); + this.updateAutoInterval(variable); } else if (variable.refresh) { this.updateOptions(variable); diff --git a/src/app/services/timeSrv.js b/src/app/services/timeSrv.js index 5a28532eb5a..564f30de7fd 100644 --- a/src/app/services/timeSrv.js +++ b/src/app/services/timeSrv.js @@ -95,6 +95,18 @@ define([ $timeout(this.refreshDashboard, 0); }; + this.timeRangeForUrl = function() { + var range = this.timeRange(false); + if (_.isString(range.to) && range.to.indexOf('now')) { + range = this.timeRange(); + } + + if (_.isDate(range.from)) { range.from = range.from.getTime(); } + if (_.isDate(range.to)) { range.to = range.to.getTime(); } + + return range; + }; + this.timeRange = function(parse) { var _t = this.time; if(_.isUndefined(_t) || _.isUndefined(_t.from)) { diff --git a/src/config.sample.js b/src/config.sample.js index 07f29d7be28..b1c901c5790 100644 --- a/src/config.sample.js +++ b/src/config.sample.js @@ -73,7 +73,7 @@ define(['settings'], function(Settings) { // specify the limit for dashboard search results search: { - max_results: 20 + max_results: 100 }, // default home dashboard diff --git a/src/css/less/grafana.less b/src/css/less/grafana.less index 4df24c22658..af44071147e 100644 --- a/src/css/less/grafana.less +++ b/src/css/less/grafana.less @@ -7,10 +7,17 @@ @import "search.less"; @import "panel.less"; @import "forms.less"; +@import "singlestat.less"; + +.row-control-inner { + padding:0px; + margin:0px; + position:relative; +} .hide-controls { padding: 0; - .row-control-inner { + .row-tab { display: none; } .submenu-controls { @@ -110,7 +117,7 @@ position: fixed; left: 0px; right: 0px; - top: 54px; + top: 51px; height: 100%; padding: 0 10px; background: @grafanaPanelBackground; @@ -128,8 +135,11 @@ .dashboard-fullscreen { .main-view-container { - height: 0; overflow: hidden; + height: 0; + .row-control-inner { + display: none; + } } } @@ -546,3 +556,13 @@ select.grafana-target-segment-input { .grafana-tip { padding-left: 5px; } + +.shortcut-table { + td { padding: 3px; } + th:last-child { text-align: left; } + td:first-child { text-align: right; } +} + +.confirm-modal { + max-width: 500px; +} diff --git a/src/css/less/graph.less b/src/css/less/graph.less index 01a697ddb2e..cb489aeb8b3 100644 --- a/src/css/less/graph.less +++ b/src/css/less/graph.less @@ -18,12 +18,12 @@ top: 1px; } -.graph-legend-series, .graph-legend-icon, .graph-legend-alias, .graph-legend-value { float: left; white-space: nowrap; + font-size: 85%; text-align: left; &.current:before { content: "Current: " @@ -43,6 +43,8 @@ } .graph-legend-series { + float: left; + white-space: nowrap; padding-left: 10px; padding-top: 6px; } @@ -53,6 +55,8 @@ .graph-legend-table { display: table; + width: 100%; + margin: 0; .graph-legend-series { display: table-row; @@ -60,35 +64,60 @@ padding-left: 0; &.pull-right { float: none; - .graph-legend-alias::after { - content: 'y\00B2'; + } + } + + td, .graph-legend-alias, .graph-legend-icon, .graph-legend-value { + float: none; + display: table-cell; + white-space: nowrap; + padding: 2px 10px; + text-align: right; + border-bottom: 1px solid @grafanaListBorderBottom; + } + + .graph-legend-icon { + width: 5px; + padding: 0; + top: 0; + .icon-minus { + position: relative; + top: 2px; + } + } + + .graph-legend-value { + padding-left: 15px; + } + + .graph-legend-alias { + padding-left: 7px; + text-align: left; + width: 95%; + } + + .graph-legend-series:nth-child(odd) { + background-color: @grafanaListAccent; + } + + .graph-legend-value { + &.current, &.max, &.min, &.total, &.avg { + &:before { + content: ''; } } } - .graph-legend-alias { - float: none; - display: table-cell; + th { + text-align: right; + padding: 5px 10px; + font-weight: bold; + color: @blue; + font-size: 85%; white-space: nowrap; } - - .graph-legend-icon { - display: table-cell; - float: none; - white-space: nowrap; - padding: 0 4px; - top: 2px; - } - - .graph-legend-value { - float: none; - display: table-cell; - white-space: nowrap; - padding-left: 15px; - } } - .graph-legend-rightside { &.graph-wrapper { @@ -106,7 +135,8 @@ display: table-cell; vertical-align: top; position: relative; - left: -4px; + left: 4px; + top: -20px; } .graph-legend { @@ -169,6 +199,8 @@ } .graph-tooltip { + white-space: nowrap; + .graph-tooltip-time { text-align: center; font-weight: bold; @@ -176,9 +208,18 @@ top: -3px; } + .graph-tooltip-list-item { + display: table-row; + } + + .graph-tooltip-series-name { + display: table-cell; + } + .graph-tooltip-value { + display: table-cell; font-weight: bold; - float: right; padding-left: 10px; + text-align: right; } } diff --git a/src/css/less/overrides.less b/src/css/less/overrides.less index 6c8f6c0224f..110f03a7773 100644 --- a/src/css/less/overrides.less +++ b/src/css/less/overrides.less @@ -231,10 +231,6 @@ form input.ng-invalid { z-index: 9999; } -.dragInProgress .panel-container { - border: 3px solid rgba(100,100,100,0.50); -} - .link { color: @linkColor; cursor: pointer; diff --git a/src/css/less/panel.less b/src/css/less/panel.less index 7d24d1752c4..5c3dd93c6cf 100644 --- a/src/css/less/panel.less +++ b/src/css/less/panel.less @@ -2,6 +2,7 @@ display: inline-block; float: left; vertical-align: top; + position: relative; } .panel-container { @@ -17,6 +18,7 @@ .panel-title-container { min-height: 5px; + padding-top: 4px; cursor: context-menu; } @@ -24,8 +26,16 @@ border: 0px; font-weight: bold; position: relative; - font-size: 0.9em; cursor: context-menu; + + &.has-panel-links { + .panel-title-text:after { + content: "\f0c1"; + font-family:'FontAwesome'; + font-size: 80%; + padding-left: 10px; + } + } } .panel-loading { @@ -39,7 +49,6 @@ text-align: center; } - .panel-error { color: @white; position: absolute; @@ -93,8 +102,25 @@ border: none; } } + + .dropdown-menu { + text-align: left; + } } .panel-highlight { .box-shadow(~"inset 0 1px 1px rgba(0,0,0,.075), 0 0 5px rgba(82,168,236, 0.8)"); } + +.on-drag-hover { + .panel-container { + .box-shadow(~"inset 0 1px 1px rgba(0,0,0,.075), 0 0 5px rgba(82,168,236, 0.8)"); + } +} + +.panel-drop-zone { + display: none; + .panel-container { + border: 1px solid @grayDark; + } +} diff --git a/src/css/less/singlestat.less b/src/css/less/singlestat.less new file mode 100644 index 00000000000..368be2b3538 --- /dev/null +++ b/src/css/less/singlestat.less @@ -0,0 +1,51 @@ +.singlestat-panel { + position: relative; + display: table; + width: 100%; +} + +.singlestat-panel-value-container { + padding: 20px; + display: table-cell; + vertical-align: middle; + text-align: center; + position: relative; + z-index: 1; + font-size: 3em; + font-weight: bold; +} + +.singlestat-panel-prefix { + padding-right: 20px; +} + +.singlestat-panel-table { + width: 100%; + td { + padding: 5px 10px; + white-space: nowrap; + text-align: right; + border-bottom: 1px solid @grafanaListBorderBottom; + } + + th { + text-align: right; + padding: 5px 10px; + font-weight: bold; + color: @blue + } + + td:first-child { + text-align: left; + } + + tr:nth-child(odd) td { + background-color: @grafanaListAccent; + } + + tr:last-child td { + border: none; + } +} + + diff --git a/src/css/less/submenu.less b/src/css/less/submenu.less index b762d7acfbd..52bd1c81e92 100644 --- a/src/css/less/submenu.less +++ b/src/css/less/submenu.less @@ -1,6 +1,6 @@ .submenu-controls-visible:not(.hide-controls) { .panel-fullscreen { - top: 91px; + top: 88px; } } diff --git a/src/plugins/custom.panel.example/editor.html b/src/plugins/custom.panel.example/editor.html new file mode 100644 index 00000000000..b121e866850 --- /dev/null +++ b/src/plugins/custom.panel.example/editor.html @@ -0,0 +1,10 @@ +
    +
    +
    + +
    +
    + +
    +
    +
    diff --git a/src/plugins/custom.panel.example/module.html b/src/plugins/custom.panel.example/module.html new file mode 100644 index 00000000000..08ecbe30daa --- /dev/null +++ b/src/plugins/custom.panel.example/module.html @@ -0,0 +1,3 @@ +
    +

    Custom panel

    +
    diff --git a/src/plugins/custom.panel.example/module.js b/src/plugins/custom.panel.example/module.js new file mode 100644 index 00000000000..d04d8fca959 --- /dev/null +++ b/src/plugins/custom.panel.example/module.js @@ -0,0 +1,31 @@ +define([ + 'angular', + 'app', + 'lodash', + 'require', +], +function (angular, app, _) { + 'use strict'; + + var module = angular.module('grafana.panels.custom', []); + app.useModule(module); + + module.controller('CustomPanelCtrl', function($scope, panelSrv) { + + $scope.panelMeta = { + description : "Example plugin panel", + }; + + // set and populate defaults + var _d = { + }; + + _.defaults($scope.panel, _d); + + $scope.init = function() { + panelSrv.init($scope); + }; + + $scope.init(); + }); +}); diff --git a/src/plugins/datasource.example.js b/src/plugins/datasource.example.js index d85409cf2a5..fcc160ec569 100644 --- a/src/plugins/datasource.example.js +++ b/src/plugins/datasource.example.js @@ -19,7 +19,7 @@ function (angular, _, kbn) { this.url = datasource.url; } - CustomDatasource.prototype.query = function(filterSrv, options) { + CustomDatasource.prototype.query = function(options) { // get from & to in seconds var from = kbn.parseDate(options.range.from).getTime() / 1000; var to = kbn.parseDate(options.range.to).getTime() / 1000; diff --git a/src/test/specs/dashboardViewStateSrv-specs.js b/src/test/specs/dashboardViewStateSrv-specs.js index 82ccfe51363..1ade349610d 100644 --- a/src/test/specs/dashboardViewStateSrv-specs.js +++ b/src/test/specs/dashboardViewStateSrv-specs.js @@ -30,7 +30,7 @@ define([ viewState.update({fullscreen: false}); expect(location.search()).to.eql({}); expect(viewState.fullscreen).to.be(false); - expect(viewState.state.fullscreen).to.be(false); + expect(viewState.state.fullscreen).to.be(null); }); }); diff --git a/src/test/specs/gfunc-specs.js b/src/test/specs/gfunc-specs.js index e0714d8376f..f0650c16077 100644 --- a/src/test/specs/gfunc-specs.js +++ b/src/test/specs/gfunc-specs.js @@ -79,7 +79,7 @@ define([ var func = gfunc.createFuncInstance('summarize', { withDefaultParams: true }); func.updateParam('1h', 0); expect(func.params[0]).to.be('1h'); - expect(func.text).to.be('summarize(1h, sum)'); + expect(func.text).to.be('summarize(1h, sum, false)'); }); it('should parse numbers as float', function() { diff --git a/src/test/specs/graph-ctrl-specs.js b/src/test/specs/graph-ctrl-specs.js index e1ddcbc571e..fded5b93ee2 100644 --- a/src/test/specs/graph-ctrl-specs.js +++ b/src/test/specs/graph-ctrl-specs.js @@ -27,15 +27,20 @@ define([ ctx.scope.$digest(); }); - it('should build legend model', function() { - expect(ctx.scope.legend[0].alias).to.be('test.cpu1'); - expect(ctx.scope.legend[1].alias).to.be('test.cpu2'); - }); - it('should send time series to render', function() { var data = ctx.scope.render.getCall(0).args[0]; expect(data.length).to.be(2); }); + + describe('get_data failure following success', function() { + beforeEach(function() { + ctx.datasource.query = sinon.stub().returns(ctx.$q.reject('Datasource Error')); + ctx.scope.get_data(); + ctx.scope.$digest(); + }); + + }); + }); }); diff --git a/src/test/specs/grafanaGraph-specs.js b/src/test/specs/graph-specs.js similarity index 87% rename from src/test/specs/grafanaGraph-specs.js rename to src/test/specs/graph-specs.js index c5659ba8825..71c04084e91 100644 --- a/src/test/specs/grafanaGraph-specs.js +++ b/src/test/specs/graph-specs.js @@ -3,7 +3,7 @@ define([ 'angular', 'jquery', 'components/timeSeries', - 'directives/grafanaGraph' + 'panels/graph/graph' ], function(helpers, angular, $, TimeSeries) { 'use strict'; @@ -47,11 +47,11 @@ define([ ctx.data = []; ctx.data.push(new TimeSeries({ datapoints: [[1,1],[2,2]], - info: { alias: 'series1', enable: true } + alias: 'series1' })); ctx.data.push(new TimeSeries({ datapoints: [[1,1],[2,2]], - info: { alias: 'series2', enable: true } + alias: 'series2' })); setupFunc(scope, ctx.data); @@ -126,6 +126,20 @@ define([ }); }); + graphScenario('should use timeStep for barWidth', function(ctx) { + ctx.setup(function(scope, data) { + scope.panel.bars = true; + data[0] = new TimeSeries({ + datapoints: [[1,10],[2,20]], + alias: 'series1', + }); + }); + + it('should set barWidth', function() { + expect(ctx.plotOptions.series.bars.barWidth).to.be(10/1.5); + }); + }); + graphScenario('series option overrides, fill & points', function(ctx) { ctx.setup(function(scope, data) { scope.panel.lines = true; @@ -134,7 +148,7 @@ define([ { alias: 'test', fill: 0, points: true } ]; - data[1].info.alias = 'test'; + data[1].alias = 'test'; }); it('should match second series and fill zero, and enable points', function() { @@ -150,8 +164,8 @@ define([ }); it('should move zindex 2 last', function() { - expect(ctx.plotData[0].info.alias).to.be('series2'); - expect(ctx.plotData[1].info.alias).to.be('series1'); + expect(ctx.plotData[0].alias).to.be('series2'); + expect(ctx.plotData[1].alias).to.be('series1'); }); }); @@ -161,7 +175,7 @@ define([ }); it('should remove datapoints and disable stack', function() { - expect(ctx.plotData[0].info.alias).to.be('series1'); + expect(ctx.plotData[0].alias).to.be('series1'); expect(ctx.plotData[1].data.length).to.be(0); expect(ctx.plotData[1].stack).to.be(false); }); diff --git a/src/test/specs/graph-tooltip-specs.js b/src/test/specs/graph-tooltip-specs.js index 7f20ad8dbc9..6d1b9a40a58 100644 --- a/src/test/specs/graph-tooltip-specs.js +++ b/src/test/specs/graph-tooltip-specs.js @@ -1,55 +1,93 @@ define([ 'jquery', - 'directives/grafanaGraph.tooltip' -], function($, tooltip) { + 'panels/graph/graph.tooltip' +], function($, GraphTooltip) { 'use strict'; - describe('graph tooltip', function() { - var elem = $('
    '); - var dashboard = { - formatDate: sinon.stub().returns('date'), - }; - var scope = { - appEvent: sinon.spy(), - onAppEvent: sinon.spy(), - panel: { - tooltip: { - shared: true - }, - y_formats: ['ms', 'none'], - stack: true - } - }; + var scope = { + appEvent: sinon.spy(), + onAppEvent: sinon.spy(), + }; - var data = [ - { - data: [[10,10], [12,20]], - info: { yaxis: 1 }, - yaxis: { tickDecimals: 2 }, + var elem = $('
    '); + var dashboard = { }; + + function describeSharedTooltip(desc, fn) { + var ctx = {}; + ctx.scope = scope; + ctx.scope.panel = { + tooltip: { + shared: true }, - { - data: [[10,10], [12,20]], - info: { yaxis: 1 }, - yaxis: { tickDecimals: 2 }, - } - ]; - - var plot = { - getData: sinon.stub().returns(data), - highlight: sinon.stub(), - unhighlight: sinon.stub() + stack: false }; - elem.data('plot', plot); + ctx.setup = function(setupFn) { + ctx.setupFn = setupFn; + }; - beforeEach(function() { - tooltip.register(elem, dashboard, scope); - elem.trigger('plothover', [{}, {x: 13}, {}]); + describe(desc, function() { + beforeEach(function() { + ctx.setupFn(); + var tooltip = new GraphTooltip(elem, dashboard, scope); + ctx.results = tooltip.getMultiSeriesPlotHoverInfo(ctx.data, ctx.pos); + }); + + fn(ctx); + }); + } + + describeSharedTooltip("steppedLine false, stack false", function(ctx) { + ctx.setup(function() { + ctx.data = [ + { data: [[10, 15], [12, 20]], }, + { data: [[10, 2], [12, 3]], } + ]; + ctx.pos = { x: 11 }; }); - it('should add tooltip', function() { - var tooltipHtml = $(".graph-tooltip").text(); - expect(tooltipHtml).to.be('date : 40.00 ms : 20.00 ms'); + it('should return 2 series', function() { + expect(ctx.results.length).to.be(2); + }); + it('should add time to results array', function() { + expect(ctx.results.time).to.be(10); + }); + it('should set value and hoverIndex', function() { + expect(ctx.results[0].value).to.be(15); + expect(ctx.results[1].value).to.be(2); + expect(ctx.results[0].hoverIndex).to.be(0); + }); + }); + + describeSharedTooltip("steppedLine false, stack true, individual false", function(ctx) { + ctx.setup(function() { + ctx.data = [ + { data: [[10, 15], [12, 20]], }, + { data: [[10, 2], [12, 3]], } + ]; + ctx.scope.panel.stack = true; + ctx.pos = { x: 11 }; + }); + + it('should show stacked value', function() { + expect(ctx.results[1].value).to.be(17); + }); + + }); + + describeSharedTooltip("steppedLine false, stack true, individual true", function(ctx) { + ctx.setup(function() { + ctx.data = [ + { data: [[10, 15], [12, 20]], }, + { data: [[10, 2], [12, 3]], } + ]; + ctx.scope.panel.stack = true; + ctx.scope.panel.tooltip.value_type = 'individual'; + ctx.pos = { x: 11 }; + }); + + it('should not show stacked value', function() { + expect(ctx.results[1].value).to.be(2); }); }); diff --git a/src/test/specs/graphiteDatasource-specs.js b/src/test/specs/graphiteDatasource-specs.js index 99270867266..f112492aebc 100644 --- a/src/test/specs/graphiteDatasource-specs.js +++ b/src/test/specs/graphiteDatasource-specs.js @@ -21,7 +21,7 @@ define([ maxDataPoints: 500, }; - var response = [{ target: 'prod1.count', points: [[10, 1], [12,1]], }]; + var response = [{ target: 'prod1.count', datapoints: [[10, 1], [12,1]], }]; var results; var request; diff --git a/src/test/specs/influxQueryBuilder-specs.js b/src/test/specs/influxQueryBuilder-specs.js index 4f18bf2905e..b4e052a23af 100644 --- a/src/test/specs/influxQueryBuilder-specs.js +++ b/src/test/specs/influxQueryBuilder-specs.js @@ -44,6 +44,35 @@ define([ }); + describe('merge function detection', function() { + it('should not quote wrap regex merged series', function() { + var builder = new InfluxQueryBuilder({ + series: 'merge(/^google.test/)', + column: 'value', + function: 'mean' + }); + + var query = builder.build(); + + expect(query).to.be('select mean(value) from merge(/^google.test/) where $timeFilter ' + + 'group by time($interval) order asc'); + }); + + it('should quote wrap series names that start with "merge"', function() { + var builder = new InfluxQueryBuilder({ + series: 'merge.google.test', + column: 'value', + function: 'mean' + }); + + var query = builder.build(); + + expect(query).to.be('select mean(value) from "merge.google.test" where $timeFilter ' + + 'group by time($interval) order asc'); + }); + + }); + }); }); diff --git a/src/test/specs/influxdb-datasource-specs.js b/src/test/specs/influxdb-datasource-specs.js index 3851b0cd898..c0d25e1e81f 100644 --- a/src/test/specs/influxdb-datasource-specs.js +++ b/src/test/specs/influxdb-datasource-specs.js @@ -17,7 +17,7 @@ define([ describe('When querying influxdb with one target using query editor target spec', function() { var results; var urlExpected = "/series?p=mupp&q=select+mean(value)+from+%22test%22"+ - "+where+time+%3E+now()+-+1h+group+by+time(1s)+order+asc&time_precision=s"; + "+where+time+%3E+now()+-+1h+group+by+time(1s)+order+asc"; var query = { range: { from: 'now-1h', to: 'now' }, targets: [{ series: 'test', column: 'value', function: 'mean' }], @@ -50,7 +50,7 @@ define([ describe('When querying influxdb with one raw query', function() { var results; var urlExpected = "/series?p=mupp&q=select+value+from+series"+ - "+where+time+%3E+now()+-+1h&time_precision=s"; + "+where+time+%3E+now()+-+1h"; var query = { range: { from: 'now-1h', to: 'now' }, targets: [{ query: "select value from series where $timeFilter", rawQuery: true }] @@ -73,7 +73,7 @@ define([ describe('When issuing annotation query', function() { var results; var urlExpected = "/series?p=mupp&q=select+title+from+events.backend_01"+ - "+where+time+%3E+now()+-+1h&time_precision=s"; + "+where+time+%3E+now()+-+1h"; var range = { from: 'now-1h', to: 'now' }; var annotation = { query: 'select title from events.$server where $timeFilter' }; diff --git a/src/test/specs/seriesOverridesCtrl-specs.js b/src/test/specs/seriesOverridesCtrl-specs.js index e211b6dab35..6a96c77416d 100644 --- a/src/test/specs/seriesOverridesCtrl-specs.js +++ b/src/test/specs/seriesOverridesCtrl-specs.js @@ -18,7 +18,7 @@ define([ describe('Controller should init overrideMenu', function() { it('click should include option and value index', function() { - expect(ctx.scope.overrideMenu[1].submenu[1].click).to.be('setOverride(1,1)'); + expect(ctx.scope.overrideMenu[1].submenu[1].click).to.be('menuItemSelected(1,1)'); }); }); diff --git a/src/test/specs/sharePanelCtrl-specs.js b/src/test/specs/sharePanelCtrl-specs.js index 8c4eb2a2443..8c83f008ed5 100644 --- a/src/test/specs/sharePanelCtrl-specs.js +++ b/src/test/specs/sharePanelCtrl-specs.js @@ -7,6 +7,12 @@ define([ describe('SharePanelCtrl', function() { var ctx = new helpers.ControllerTestContext(); + function setTime(range) { + ctx.timeSrv.timeRangeForUrl = sinon.stub().returns(range); + } + + setTime({ from: 'now-1h', to: 'now' }); + beforeEach(module('grafana.controllers')); beforeEach(ctx.providePhase()); @@ -14,10 +20,12 @@ define([ describe('shareUrl with current time range and panel', function() { + it('should generate share url relative time', function() { ctx.$location.path('/test'); ctx.scope.panel = { id: 22 }; - ctx.timeSrv.time = { from: 'now-1h', to: 'now' }; + + setTime({ from: 'now-1h', to: 'now' }); ctx.scope.buildUrl(); expect(ctx.scope.shareUrl).to.be('http://server/#/test?from=now-1h&to=now&panelId=22&fullscreen'); @@ -26,26 +34,17 @@ define([ it('should generate share url absolute time', function() { ctx.$location.path('/test'); ctx.scope.panel = { id: 22 }; - ctx.timeSrv.time = { from: new Date(1362178800000), to: new Date(1396648800000) }; + setTime({ from: 1362178800000, to: 1396648800000 }); ctx.scope.buildUrl(); expect(ctx.scope.shareUrl).to.be('http://server/#/test?from=1362178800000&to=1396648800000&panelId=22&fullscreen'); }); - it('should generate share url with time as JSON strings', function() { - ctx.$location.path('/test'); - ctx.scope.panel = { id: 22 }; - ctx.timeSrv.time = { from: "2012-01-31T23:00:00.000Z", to: "2014-04-04T22:00:00.000Z" }; - - ctx.scope.buildUrl(); - expect(ctx.scope.shareUrl).to.be('http://server/#/test?from=1328050800000&to=1396648800000&panelId=22&fullscreen'); - }); - it('should remove panel id when toPanel is false', function() { ctx.$location.path('/test'); ctx.scope.panel = { id: 22 }; ctx.scope.toPanel = false; - ctx.timeSrv.time = { from: 'now-1h', to: 'now' }; + setTime({ from: 'now-1h', to: 'now' }); ctx.scope.buildUrl(); expect(ctx.scope.shareUrl).to.be('http://server/#/test?from=now-1h&to=now'); @@ -57,7 +56,7 @@ define([ ctx.scope.includeTemplateVars = true; ctx.scope.toPanel = false; ctx.templateSrv.variables = [{ name: 'app', current: {text: 'mupp' }}, {name: 'server', current: {text: 'srv-01'}}]; - ctx.timeSrv.time = { from: 'now-1h', to: 'now' }; + setTime({ from: 'now-1h', to: 'now' }); ctx.scope.buildUrl(); expect(ctx.scope.shareUrl).to.be('http://server/#/test?from=now-1h&to=now&var-app=mupp&var-server=srv-01'); diff --git a/src/test/specs/timeSeries-specs.js b/src/test/specs/timeSeries-specs.js index cf28c1aa450..e68ce16d54e 100644 --- a/src/test/specs/timeSeries-specs.js +++ b/src/test/specs/timeSeries-specs.js @@ -7,7 +7,7 @@ define([ var points, series; var yAxisFormats = ['short', 'ms']; var testData = { - info: { alias: 'test' }, + alias: 'test', datapoints: [ [1,2],[null,3],[10,4],[8,5] ] @@ -26,6 +26,15 @@ define([ expect(points.length).to.be(4); expect(points[1][1]).to.be(0); }); + + it('if last is null current should pick next to last', function() { + series = new TimeSeries({ + datapoints: [[10,1], [null, 2]] + }); + series.getFlotPairs('null', yAxisFormats); + expect(series.stats.current).to.be(10); + }); + }); describe('series overrides', function() { @@ -36,7 +45,7 @@ define([ describe('fill & points', function() { beforeEach(function() { - series.info.alias = 'test'; + series.alias = 'test'; series.applySeriesOverrides([{ alias: 'test', fill: 0, points: true }]); }); @@ -48,7 +57,7 @@ define([ describe('series option overrides, bars, true & lines false', function() { beforeEach(function() { - series.info.alias = 'test'; + series.alias = 'test'; series.applySeriesOverrides([{ alias: 'test', bars: true, lines: false }]); }); @@ -60,7 +69,7 @@ define([ describe('series option overrides, linewidth, stack', function() { beforeEach(function() { - series.info.alias = 'test'; + series.alias = 'test'; series.applySeriesOverrides([{ alias: 'test', linewidth: 5, stack: false }]); }); @@ -70,9 +79,20 @@ define([ }); }); + describe('series option overrides, fill below to', function() { + beforeEach(function() { + series.alias = 'test'; + series.applySeriesOverrides([{ alias: 'test', fillBelowTo: 'min' }]); + }); + + it('should disable line fill and add fillBelowTo', function() { + expect(series.fillBelowTo).to.be('min'); + }); + }); + describe('series option overrides, pointradius, steppedLine', function() { beforeEach(function() { - series.info.alias = 'test'; + series.alias = 'test'; series.applySeriesOverrides([{ alias: 'test', pointradius: 5, steppedLine: true }]); }); @@ -84,7 +104,7 @@ define([ describe('override match on regex', function() { beforeEach(function() { - series.info.alias = 'test_01'; + series.alias = 'test_01'; series.applySeriesOverrides([{ alias: '/.*01/', lines: false }]); }); @@ -95,12 +115,12 @@ define([ describe('override series y-axis, and z-index', function() { beforeEach(function() { - series.info.alias = 'test'; + series.alias = 'test'; series.applySeriesOverrides([{ alias: 'test', yaxis: 2, zindex: 2 }]); }); it('should set yaxis', function() { - expect(series.info.yaxis).to.be(2); + expect(series.yaxis).to.be(2); }); it('should set zindex', function() { diff --git a/src/test/test-main.js b/src/test/test-main.js index b642063e598..f0fc9bb4d51 100644 --- a/src/test/test-main.js +++ b/src/test/test-main.js @@ -32,8 +32,6 @@ require.config({ bootstrap: '../vendor/bootstrap/bootstrap', 'bootstrap-tagsinput': '../vendor/tagsinput/bootstrap-tagsinput', - 'jquery-ui': '../vendor/jquery/jquery-ui-1.10.3', - 'extend-jquery': 'components/extend-jquery', 'jquery.flot': '../vendor/jquery/jquery.flot', @@ -44,6 +42,7 @@ require.config({ 'jquery.flot.stackpercent':'../vendor/jquery/jquery.flot.stackpercent', 'jquery.flot.time': '../vendor/jquery/jquery.flot.time', 'jquery.flot.crosshair': '../vendor/jquery/jquery.flot.crosshair', + 'jquery.flot.fillbelow': '../vendor/jquery/jquery.flot.fillbelow', modernizr: '../vendor/modernizr-2.6.1', }, @@ -70,7 +69,6 @@ require.config({ exports: 'Crypto' }, - 'jquery-ui': ['jquery'], 'jquery.flot': ['jquery'], 'jquery.flot.pie': ['jquery', 'jquery.flot'], 'jquery.flot.events': ['jquery', 'jquery.flot'], @@ -79,10 +77,11 @@ require.config({ 'jquery.flot.stackpercent':['jquery', 'jquery.flot'], 'jquery.flot.time': ['jquery', 'jquery.flot'], 'jquery.flot.crosshair':['jquery', 'jquery.flot'], + 'jquery.flot.fillbelow':['jquery', 'jquery.flot'], 'angular-route': ['angular'], 'angular-cookies': ['angular'], - 'angular-dragdrop': ['jquery','jquery-ui','angular'], + 'angular-dragdrop': ['jquery', 'angular'], 'angular-loader': ['angular'], 'angular-mocks': ['angular'], 'angular-resource': ['angular'], @@ -130,7 +129,7 @@ require([ 'specs/influxQueryBuilder-specs', 'specs/influxdb-datasource-specs', 'specs/graph-ctrl-specs', - 'specs/grafanaGraph-specs', + 'specs/graph-specs', 'specs/graph-tooltip-specs', 'specs/seriesOverridesCtrl-specs', 'specs/sharePanelCtrl-specs', diff --git a/src/vendor/angular/angular-dragdrop.js b/src/vendor/angular/angular-dragdrop.js index bc0ddf4fee1..eb982ca0c8e 100644 --- a/src/vendor/angular/angular-dragdrop.js +++ b/src/vendor/angular/angular-dragdrop.js @@ -1,338 +1,341 @@ /** - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. + * Created with IntelliJ IDEA. + * User: Ganaraj.Pr + * Date: 11/10/13 + * Time: 11:27 + * To change this template use File | Settings | File Templates. */ -/** - * Implementing Drag and Drop functionality in AngularJS is easier than ever. - * Demo: http://codef0rmer.github.com/angular-dragdrop/ - * - * @version 1.0.4 - * - * (c) 2013 Amit Gharat a.k.a codef0rmer - amitgharat.wordpress.com - */ +(function(){ -/** - * This has been modified from the default angular-draganddrop to provide the original - * model and some other stuff as the 'data' arguement to callEventCallback - */ +function isDnDsSupported(){ + return 'draggable' in document.createElement("span"); +} -(function (window, angular, undefined) { -'use strict'; +if(!isDnDsSupported()){ + return; +} -var jqyoui = angular.module('ngDragDrop', []).service('ngDragDropService', ['$timeout', '$parse', function($timeout, $parse) { - this.callEventCallback = function (scope, callbackName, event, ui, data) { - if (!callbackName) { - return; - } - var args = [event, ui, data]; - var match = callbackName.match(/^(.+)\((.+)\)$/); - if (match !== null) { - callbackName = match[1]; - var values = eval('[' + match[0].replace(/^(.+)\(/, '').replace(/\)/, '') + ']'); - args.push.apply(args, values); - } - if(scope[callbackName]) { - scope[callbackName].apply(scope, args); - } - }; +if (window.jQuery && (-1 == window.jQuery.event.props.indexOf("dataTransfer"))) { + window.jQuery.event.props.push("dataTransfer"); +} - this.invokeDrop = function ($draggable, $droppable, event, ui) { - var dragModel = '', - dropModel = '', - dragSettings = {}, - dropSettings = {}, - jqyoui_pos = null, - dragItem = {}, - dropItem = {}, - dragModelValue, - dropModelValue, - $droppableDraggable = null, - droppableScope = $droppable.scope(), - draggableScope = $draggable.scope(), - data = {}; +var currentData; - dragModel = $draggable.ngattr('ng-model'); - dropModel = $droppable.ngattr('ng-model'); - dragModelValue = draggableScope.$eval(dragModel); - dropModelValue = droppableScope.$eval(dropModel); +angular.module("ngDragDrop",[]) + .directive("uiDraggable", [ + '$parse', + '$rootScope', + '$dragImage', + function ($parse, $rootScope, $dragImage) { + return function (scope, element, attrs) { + var dragData = "", + isDragHandleUsed = false, + dragHandleClass, + draggingClass = attrs.draggingClass || "on-dragging", + dragTarget; - $droppableDraggable = $droppable.find('[jqyoui-draggable]:last'); - dropSettings = droppableScope.$eval($droppable.attr('jqyoui-droppable')) || []; - dragSettings = draggableScope.$eval($draggable.attr('jqyoui-draggable')) || []; + element.attr("draggable", false); - // Helps pick up the right item - dragSettings.index = this.fixIndex(draggableScope, dragSettings, dragModelValue); - dropSettings.index = this.fixIndex(droppableScope, dropSettings, dropModelValue); + attrs.$observe("uiDraggable", function (newValue) { + if(newValue){ + element.attr("draggable", newValue); + } + else{ + element.removeAttr("draggable"); + } - jqyoui_pos = angular.isArray(dragModelValue) ? dragSettings.index : null; - dragItem = angular.isArray(dragModelValue) ? dragModelValue[jqyoui_pos] : dragModelValue; + }); - if (angular.isArray(dropModelValue) && dropSettings && dropSettings.index !== undefined) { - dropItem = dropModelValue[dropSettings.index]; - } else if (!angular.isArray(dropModelValue)) { - dropItem = dropModelValue; - } else { - dropItem = {}; - } - - data = { - dragModel: dragModel, - dropModel: dropModel, - dragSettings: dragSettings, - dropSettings: dropSettings, - jqyoui_pos: jqyoui_pos, - dragItem: dragItem, - dropItem: dropItem, - dragModelValue: dragModelValue, - dropModelValue: dropModelValue, - droppableScope: $droppable.scope(), - draggableScope: $draggable.scope() - }; - - - if (dragSettings.animate === true) { - - this.move($draggable, $droppableDraggable.length > 0 ? $droppableDraggable : $droppable, null, 'fast', dropSettings, null); - this.move($droppableDraggable.length > 0 && !dropSettings.multiple ? $droppableDraggable : [], $draggable.parent('[jqyoui-droppable]'), jqyoui.startXY, 'fast', dropSettings, function() { - $timeout(function() { - // Do not move this into move() to avoid flickering issue - $draggable.css({'position': 'relative', 'left': '', 'top': ''}); - $droppableDraggable.css({'position': 'relative', 'left': '', 'top': ''}); - - if(dragSettings.mutate !== false) { - this.mutateDraggable(draggableScope, dropSettings, dragSettings, dragModel, dropModel, dropItem, $draggable); - } - - if(dropSettings.mutate !== false) { - this.mutateDroppable(droppableScope, dropSettings, dragSettings, dropModel, dragItem, jqyoui_pos); - } - - this.callEventCallback(droppableScope, dropSettings.onDrop, event, ui, data); - }.bind(this)); - }.bind(this)); - } else { - $timeout(function() { - - if(dragSettings.mutate !== false) { - this.mutateDraggable(draggableScope, dropSettings, dragSettings, dragModel, dropModel, dropItem, $draggable); - } - - if(dropSettings.mutate !== false) { - this.mutateDroppable(droppableScope, dropSettings, dragSettings, dropModel, dragItem, jqyoui_pos); - } - - this.callEventCallback(droppableScope, dropSettings.onDrop, event, ui, data); - }.bind(this)); - } - }; - - this.move = function($fromEl, $toEl, toPos, duration, dropSettings, callback) { - if ($fromEl.length === 0) { - if (callback) { - window.setTimeout(function() { - callback(); - }, 300); - } - return false; - } - - var zIndex = 9999, - fromPos = $fromEl.offset(), - wasVisible = $toEl && $toEl.is(':visible'); - - if (toPos === null && $toEl.length > 0) { - if ($toEl.attr('jqyoui-draggable') !== undefined && $toEl.ngattr('ng-model') !== undefined && $toEl.is(':visible') && dropSettings && dropSettings.multiple) { - toPos = $toEl.offset(); - if (dropSettings.stack === false) { - toPos.left+= $toEl.outerWidth(true); - } else { - toPos.top+= $toEl.outerHeight(true); - } - } else { - toPos = $toEl.css({'visibility': 'hidden', 'display': 'block'}).offset(); - $toEl.css({'visibility': '','display': wasVisible ? '' : 'none'}); - } - } - - $fromEl.css({'position': 'absolute', 'z-index': zIndex}) - .css(fromPos) - .animate(toPos, duration, function() { - if (callback) callback(); - }); - }; - - this.mutateDroppable = function(scope, dropSettings, dragSettings, dropModel, dragItem, jqyoui_pos) { - var dropModelValue = scope.$eval(dropModel); - - scope.__dragItem = dragItem; - - if (angular.isArray(dropModelValue)) { - if (dropSettings && dropSettings.index >= 0) { - dropModelValue[dropSettings.index] = dragItem; - } else { - dropModelValue.push(dragItem); - } - if (dragSettings && dragSettings.placeholder === true) { - dropModelValue[dropModelValue.length - 1]['jqyoui_pos'] = jqyoui_pos; - } - } else { - $parse(dropModel + ' = __dragItem')(scope); - if (dragSettings && dragSettings.placeholder === true) { - dropModelValue['jqyoui_pos'] = jqyoui_pos; - } - } - }; - - this.mutateDraggable = function(scope, dropSettings, dragSettings, dragModel, dropModel, dropItem, $draggable) { - var isEmpty = angular.equals(angular.copy(dropItem), {}), - dragModelValue = scope.$eval(dragModel); - - scope.__dropItem = dropItem; - - if (dragSettings && dragSettings.placeholder) { - if (dragSettings.placeholder != 'keep'){ - if (angular.isArray(dragModelValue) && dragSettings.index !== undefined) { - dragModelValue[dragSettings.index] = dropItem; - } else { - $parse(dragModel + ' = __dropItem')(scope); - } - } - } else { - if (angular.isArray(dragModelValue)) { - if (isEmpty) { - if (dragSettings && ( dragSettings.placeholder !== true && dragSettings.placeholder !== 'keep' )) { - dragModelValue.splice(dragSettings.index, 1); - } - } else { - dragModelValue[dragSettings.index] = dropItem; - } - } else { - // Fix: LIST(object) to LIST(array) - model does not get updated using just scope[dragModel] = {...} - // P.S.: Could not figure out why it happened - $parse(dragModel + ' = __dropItem')(scope); - if (scope.$parent) { - $parse(dragModel + ' = __dropItem')(scope.$parent); - } - } - } - - $draggable.css({'z-index': '', 'left': '', 'top': ''}); - }; - - this.fixIndex = function(scope, settings, modelValue) { - if (settings.applyFilter && angular.isArray(modelValue) && modelValue.length > 0) { - var dragModelValueFiltered = scope[settings.applyFilter](), - lookup = dragModelValueFiltered[settings.index], - actualIndex = undefined; - - modelValue.forEach(function(item, i) { - if (angular.equals(item, lookup)) { - actualIndex = i; - } - }); - - return actualIndex; - } - - return settings.index; - }; - }]).directive('jqyouiDraggable', ['ngDragDropService', function(ngDragDropService) { - return { - require: '?jqyouiDroppable', - restrict: 'A', - link: function(scope, element, attrs) { - // grafana change, remove watcher after first evaluation - var dragSettings, zIndex, removeWatcher; - var updateDraggable = function(newValue, oldValue) { - if (newValue) { - removeWatcher(); - dragSettings = scope.$eval(element.attr('jqyoui-draggable')) || []; - element - .draggable({disabled: false}) - .draggable(scope.$eval(attrs.jqyouiOptions) || {}) - .draggable({ - start: function(event, ui) { - zIndex = angular.element(this).css('z-index'); - angular.element(this).css('z-index', 99999); - jqyoui.startXY = angular.element(this).offset(); - ngDragDropService.callEventCallback(scope, dragSettings.onStart, event, ui); - }, - stop: function(event, ui) { - angular.element(this).css('z-index', zIndex); - ngDragDropService.callEventCallback(scope, dragSettings.onStop, event, ui); - }, - drag: function(event, ui) { - ngDragDropService.callEventCallback(scope, dragSettings.onDrag, event, ui); + if (attrs.drag) { + scope.$watch(attrs.drag, function (newValue) { + dragData = newValue || ""; + }); } - }); - } else { - element.draggable({disabled: true}); - } - }; - removeWatcher = scope.$watch(function() { return scope.$eval(attrs.drag); }, updateDraggable); - updateDraggable(); - } - }; - }]).directive('jqyouiDroppable', ['ngDragDropService', function(ngDragDropService) { - return { - restrict: 'A', - priority: 1, - link: function(scope, element, attrs) { - // grafana change, remove watcher after first evaluation - var removeWatcher; - var updateDroppable = function(newValue, oldValue) { - if (newValue) { - removeWatcher(); - element - .droppable({disabled: false}) - .droppable(scope.$eval(attrs.jqyouiOptions) || {}) - .droppable({ - over: function(event, ui) { - var dropSettings = scope.$eval(angular.element(this).attr('jqyoui-droppable')) || []; - ngDragDropService.callEventCallback(scope, dropSettings.onOver, event, ui); - }, - out: function(event, ui) { - var dropSettings = scope.$eval(angular.element(this).attr('jqyoui-droppable')) || []; - ngDragDropService.callEventCallback(scope, dropSettings.onOut, event, ui); - }, - drop: function(event, ui) { - if (angular.element(ui.draggable).ngattr('ng-model') && attrs.ngModel) { - ngDragDropService.invokeDrop(angular.element(ui.draggable), angular.element(this), event, ui); - } else { - ngDragDropService.callEventCallback(scope, (scope.$eval(angular.element(this).attr('jqyoui-droppable')) || []).onDrop, event, ui); + + if (angular.isString(attrs.dragHandleClass)) { + isDragHandleUsed = true; + dragHandleClass = attrs.dragHandleClass.trim() || "drag-handle"; + + element.bind("mousedown", function (e) { + dragTarget = e.target; + }); + } + + function dragendHandler(e) { + setTimeout(function() { + element.unbind('$destroy', dragendHandler); + }, 0); + var sendChannel = attrs.dragChannel || "defaultchannel"; + $rootScope.$broadcast("ANGULAR_DRAG_END", sendChannel); + if (e.dataTransfer && e.dataTransfer.dropEffect !== "none") { + if (attrs.onDropSuccess) { + var fn = $parse(attrs.onDropSuccess); + scope.$apply(function () { + fn(scope, {$event: e}); + }); + } else { + if (attrs.onDropFailure) { + var fn = $parse(attrs.onDropFailure); + scope.$apply(function () { + fn(scope, {$event: e}); + }); + } + } + } + element.removeClass(draggingClass); + } + + element.bind("dragend", dragendHandler); + + element.bind("dragstart", function (e) { + var isDragAllowed = !isDragHandleUsed || dragTarget.classList.contains(dragHandleClass); + + if (isDragAllowed) { + var sendChannel = attrs.dragChannel || "defaultchannel"; + var sendData = angular.toJson({ data: dragData, channel: sendChannel }); + var dragImage = attrs.dragImage || null; + + element.addClass(draggingClass); + element.bind('$destroy', dragendHandler); + + if (dragImage) { + var dragImageFn = $parse(attrs.dragImage); + scope.$apply(function() { + var dragImageParameters = dragImageFn(scope, {$event: e}); + if (dragImageParameters) { + if (angular.isString(dragImageParameters)) { + dragImageParameters = $dragImage.generate(dragImageParameters); + } + if (dragImageParameters.image) { + var xOffset = dragImageParameters.xOffset || 0, + yOffset = dragImageParameters.yOffset || 0; + e.dataTransfer.setDragImage(dragImageParameters.image, xOffset, yOffset); + } + } + }); + } + + e.dataTransfer.setData("Text", sendData); + currentData = angular.fromJson(sendData); + e.dataTransfer.effectAllowed = "copyMove"; + $rootScope.$broadcast("ANGULAR_DRAG_START", sendChannel); + } + else { + e.preventDefault(); + } + }); + }; + } + ]) + .directive("uiOnDrop", [ + '$parse', + '$rootScope', + function ($parse, $rootScope) { + return function (scope, element, attr) { + var dragging = 0; //Ref. http://stackoverflow.com/a/10906204 + var dropChannel = attr.dropChannel || "defaultchannel" ; + var dragChannel = ""; + var dragEnterClass = attr.dragEnterClass || "on-drag-enter"; + var dragHoverClass = attr.dragHoverClass || "on-drag-hover"; + + function onDragOver(e) { + if (e.preventDefault) { + e.preventDefault(); // Necessary. Allows us to drop. + } + + if (e.stopPropagation) { + e.stopPropagation(); + } + + e.dataTransfer.dropEffect = e.shiftKey ? 'copy' : 'move'; + return false; + } + + function onDragLeave(e) { + dragging--; + if (dragging == 0) { + element.removeClass(dragHoverClass); } } - }); - } else { - element.droppable({disabled: true}); - } - }; - removeWatcher = scope.$watch(function() { return scope.$eval(attrs.drop); }, updateDroppable); - updateDroppable(); - } - }; - }]); + function onDragEnter(e) { + dragging++; + $rootScope.$broadcast("ANGULAR_HOVER", dragChannel); + element.addClass(dragHoverClass); + } - $.fn.ngattr = function(name, value) { - var element = angular.element(this).get(0); + function onDrop(e) { + if (e.preventDefault) { + e.preventDefault(); // Necessary. Allows us to drop. + } + if (e.stopPropagation) { + e.stopPropagation(); // Necessary. Allows us to drop. + } - return element.getAttribute(name) || element.getAttribute('data-' + name); - }; -})(window, window.angular); + var sendData = e.dataTransfer.getData("Text"); + sendData = angular.fromJson(sendData); + + var fn = $parse(attr.uiOnDrop); + scope.$apply(function () { + fn(scope, {$data: sendData.data, $event: e, $channel: sendData.channel}); + }); + element.removeClass(dragEnterClass); + dragging = 0; + } + + function isDragChannelAccepted(dragChannel, dropChannel) { + if (dropChannel === "*") { + return true; + } + + var channelMatchPattern = new RegExp("(\\s|[,])+(" + dragChannel + ")(\\s|[,])+", "i"); + + return channelMatchPattern.test("," + dropChannel + ","); + } + + function preventNativeDnD(e) { + if (e.preventDefault) { + e.preventDefault(); + } + if (e.stopPropagation) { + e.stopPropagation(); + } + e.dataTransfer.dropEffect = "none"; + return false; + } + + var deregisterDragStart = $rootScope.$on("ANGULAR_DRAG_START", function (event, channel) { + dragChannel = channel; + if (isDragChannelAccepted(channel, dropChannel)) { + if (attr.dropValidate) { + var validateFn = $parse(attr.dropValidate); + var valid = validateFn(scope, {$data: currentData.data, $channel: currentData.channel}); + if (!valid) { + element.bind("dragover", preventNativeDnD); + element.bind("dragenter", preventNativeDnD); + element.bind("dragleave", preventNativeDnD); + element.bind("drop", preventNativeDnD); + return; + } + } + + element.bind("dragover", onDragOver); + element.bind("dragenter", onDragEnter); + element.bind("dragleave", onDragLeave); + + element.bind("drop", onDrop); + element.addClass(dragEnterClass); + } + else { + element.bind("dragover", preventNativeDnD); + element.bind("dragenter", preventNativeDnD); + element.bind("dragleave", preventNativeDnD); + element.bind("drop", preventNativeDnD); + } + + }); + + + + var deregisterDragEnd = $rootScope.$on("ANGULAR_DRAG_END", function (e, channel) { + dragChannel = ""; + if (isDragChannelAccepted(channel, dropChannel)) { + + element.unbind("dragover", onDragOver); + element.unbind("dragenter", onDragEnter); + element.unbind("dragleave", onDragLeave); + + element.unbind("drop", onDrop); + element.removeClass(dragHoverClass); + element.removeClass(dragEnterClass); + } + + element.unbind("dragover", preventNativeDnD); + element.unbind("dragenter", preventNativeDnD); + element.unbind("dragleave", preventNativeDnD); + element.unbind("drop", preventNativeDnD); + }); + + + var deregisterDragHover = $rootScope.$on("ANGULAR_HOVER", function (e, channel) { + if (isDragChannelAccepted(channel, dropChannel)) { + element.removeClass(dragHoverClass); + } + }); + + + scope.$on('$destroy', function () { + deregisterDragStart(); + deregisterDragEnd(); + deregisterDragHover(); + }); + + + attr.$observe('dropChannel', function (value) { + if (value) { + dropChannel = value; + } + }); + + + }; + } + ]) + .constant("$dragImageConfig", { + height: 20, + width: 200, + padding: 10, + font: 'bold 11px Arial', + fontColor: '#eee8d5', + backgroundColor: '#93a1a1', + xOffset: 0, + yOffset: 0 + }) + .service("$dragImage", [ + '$dragImageConfig', + function (defaultConfig) { + var ELLIPSIS = '…'; + + function fitString(canvas, text, config) { + var width = canvas.measureText(text).width; + if (width < config.width) { + return text; + } + while (width + config.padding > config.width) { + text = text.substring(0, text.length - 1); + width = canvas.measureText(text + ELLIPSIS).width; + } + return text + ELLIPSIS; + }; + + this.generate = function (text, options) { + var config = angular.extend({}, defaultConfig, options || {}); + var el = document.createElement('canvas'); + + el.height = config.height; + el.width = config.width; + + var canvas = el.getContext('2d'); + + canvas.fillStyle = config.backgroundColor; + canvas.fillRect(0, 0, config.width, config.height); + canvas.font = config.font; + canvas.fillStyle = config.fontColor; + + var title = fitString(canvas, text, config); + canvas.fillText(title, 4, config.padding + 4); + + var image = new Image(); + image.src = el.toDataURL(); + + return { + image: image, + xOffset: config.xOffset, + yOffset: config.yOffset + }; + } + } + ]); + +}()); diff --git a/src/vendor/angular/angular-strap.js b/src/vendor/angular/angular-strap.js index 22d89a6a3e0..772b5162943 100644 --- a/src/vendor/angular/angular-strap.js +++ b/src/vendor/angular/angular-strap.js @@ -14,235 +14,6 @@ angular.module('$strap', [ '$strap.config' ]); 'use strict'; -angular.module('$strap.directives').directive('bsAlert', [ - '$parse', - '$timeout', - '$compile', - function ($parse, $timeout, $compile) { - return { - restrict: 'A', - link: function postLink(scope, element, attrs) { - var getter = $parse(attrs.bsAlert), setter = getter.assign, value = getter(scope); - var closeAlert = function closeAlertFn(delay) { - $timeout(function () { - element.alert('close'); - }, delay * 1); - }; - if (!attrs.bsAlert) { - if (angular.isUndefined(attrs.closeButton) || attrs.closeButton !== '0' && attrs.closeButton !== 'false') { - element.prepend(''); - } - if (attrs.closeAfter) - closeAlert(attrs.closeAfter); - } else { - scope.$watch(attrs.bsAlert, function (newValue, oldValue) { - value = newValue; - element.html((newValue.title ? '' + newValue.title + ' ' : '') + newValue.content || ''); - if (!!newValue.closed) { - element.hide(); - } - $compile(element.contents())(scope); - if (newValue.type || oldValue.type) { - oldValue.type && element.removeClass('alert-' + oldValue.type); - newValue.type && element.addClass('alert-' + newValue.type); - } - if (angular.isDefined(newValue.closeAfter)) - closeAlert(newValue.closeAfter); - else if (attrs.closeAfter) - closeAlert(attrs.closeAfter); - if (angular.isUndefined(attrs.closeButton) || attrs.closeButton !== '0' && attrs.closeButton !== 'false') { - element.prepend(''); - } - }, true); - } - element.addClass('alert').alert(); - if (element.hasClass('fade')) { - element.removeClass('in'); - setTimeout(function () { - element.addClass('in'); - }); - } - var parentArray = attrs.ngRepeat && attrs.ngRepeat.split(' in ').pop(); - element.on('close', function (ev) { - var removeElement; - if (parentArray) { - ev.preventDefault(); - element.removeClass('in'); - removeElement = function () { - element.trigger('closed'); - if (scope.$parent) { - scope.$parent.$apply(function () { - var path = parentArray.split('.'); - var curr = scope.$parent; - for (var i = 0; i < path.length; ++i) { - if (curr) { - curr = curr[path[i]]; - } - } - if (curr) { - curr.splice(scope.$index, 1); - } - }); - } - }; - $.support.transition && element.hasClass('fade') ? element.on($.support.transition.end, removeElement) : removeElement(); - } else if (value) { - ev.preventDefault(); - element.removeClass('in'); - removeElement = function () { - element.trigger('closed'); - scope.$apply(function () { - value.closed = true; - }); - }; - $.support.transition && element.hasClass('fade') ? element.on($.support.transition.end, removeElement) : removeElement(); - } else { - } - }); - } - }; - } -]); -'use strict'; -angular.module('$strap.directives').directive('bsButton', [ - '$parse', - '$timeout', - function ($parse, $timeout) { - return { - restrict: 'A', - require: '?ngModel', - link: function postLink(scope, element, attrs, controller) { - if (controller) { - if (!element.parent('[data-toggle="buttons-checkbox"], [data-toggle="buttons-radio"]').length) { - element.attr('data-toggle', 'button'); - } - var startValue = !!scope.$eval(attrs.ngModel); - if (startValue) { - element.addClass('active'); - } - scope.$watch(attrs.ngModel, function (newValue, oldValue) { - var bNew = !!newValue, bOld = !!oldValue; - if (bNew !== bOld) { - $.fn.button.Constructor.prototype.toggle.call(button); - } else if (bNew && !startValue) { - element.addClass('active'); - } - }); - } - if (!element.hasClass('btn')) { - element.on('click.button.data-api', function (ev) { - element.button('toggle'); - }); - } - element.button(); - var button = element.data('button'); - button.toggle = function () { - if (!controller) { - return $.fn.button.Constructor.prototype.toggle.call(this); - } - var $parent = element.parent('[data-toggle="buttons-radio"]'); - if ($parent.length) { - element.siblings('[ng-model]').each(function (k, v) { - $parse($(v).attr('ng-model')).assign(scope, false); - }); - scope.$digest(); - if (!controller.$modelValue) { - controller.$setViewValue(!controller.$modelValue); - scope.$digest(); - } - } else { - scope.$apply(function () { - controller.$setViewValue(!controller.$modelValue); - }); - } - }; - } - }; - } -]).directive('bsButtonsCheckbox', [ - '$parse', - function ($parse) { - return { - restrict: 'A', - require: '?ngModel', - compile: function compile(tElement, tAttrs, transclude) { - tElement.attr('data-toggle', 'buttons-checkbox').find('a, button').each(function (k, v) { - $(v).attr('bs-button', ''); - }); - } - }; - } -]).directive('bsButtonsRadio', [ - '$timeout', - function ($timeout) { - return { - restrict: 'A', - require: '?ngModel', - compile: function compile(tElement, tAttrs, transclude) { - tElement.attr('data-toggle', 'buttons-radio'); - if (!tAttrs.ngModel) { - tElement.find('a, button').each(function (k, v) { - $(v).attr('bs-button', ''); - }); - } - return function postLink(scope, iElement, iAttrs, controller) { - if (controller) { - $timeout(function () { - iElement.find('[value]').button().filter('[value="' + controller.$viewValue + '"]').addClass('active'); - }); - iElement.on('click.button.data-api', function (ev) { - scope.$apply(function () { - controller.$setViewValue($(ev.target).closest('button').attr('value')); - }); - }); - scope.$watch(iAttrs.ngModel, function (newValue, oldValue) { - if (newValue !== oldValue) { - var $btn = iElement.find('[value="' + scope.$eval(iAttrs.ngModel) + '"]'); - if ($btn.length) { - $btn.button('toggle'); - } - } - }); - } - }; - } - }; - } -]); -'use strict'; -angular.module('$strap.directives').directive('bsButtonSelect', [ - '$parse', - '$timeout', - function ($parse, $timeout) { - return { - restrict: 'A', - require: '?ngModel', - link: function postLink(scope, element, attrs, ctrl) { - var getter = $parse(attrs.bsButtonSelect), setter = getter.assign; - if (ctrl) { - element.text(scope.$eval(attrs.ngModel)); - scope.$watch(attrs.ngModel, function (newValue, oldValue) { - element.text(newValue); - }); - } - var values, value, index, newValue; - element.bind('click', function (ev) { - values = getter(scope); - value = ctrl ? scope.$eval(attrs.ngModel) : element.text(); - index = values.indexOf(value); - newValue = index > values.length - 2 ? values[0] : values[index + 1]; - scope.$apply(function () { - element.text(newValue); - if (ctrl) { - ctrl.$setViewValue(newValue); - } - }); - }); - } - }; - } -]); -'use strict'; angular.module('$strap.directives').directive('bsDatepicker', [ '$timeout', '$strapConfig', @@ -379,46 +150,6 @@ angular.module('$strap.directives').directive('bsDatepicker', [ } ]); 'use strict'; -angular.module('$strap.directives').directive('bsDropdown', [ - '$parse', - '$compile', - '$timeout', - function ($parse, $compile, $timeout) { - var buildTemplate = function (items, ul) { - if (!ul) - ul = [ - '' - ]; - angular.forEach(items, function (item, index) { - if (item.divider) - return ul.splice(index + 1, 0, '
  • '); - var li = '' + '' + (item.text || '') + ''; - if (item.submenu && item.submenu.length) - li += buildTemplate(item.submenu).join('\n'); - li += ''; - ul.splice(index + 1, 0, li); - }); - return ul; - }; - return { - restrict: 'EA', - scope: true, - link: function postLink(scope, iElement, iAttrs) { - var getter = $parse(iAttrs.bsDropdown), items = getter(scope); - $timeout(function () { - if (!angular.isArray(items)) { - } - var dropdown = angular.element(buildTemplate(items).join('')); - dropdown.insertAfter(iElement); - $compile(iElement.next('ul.dropdown-menu'))(scope); - }); - iElement.addClass('dropdown-toggle').attr('data-toggle', 'dropdown'); - } - }; - } -]); -'use strict'; angular.module('$strap.directives').factory('$modal', [ '$rootScope', '$compile', @@ -483,183 +214,7 @@ angular.module('$strap.directives').factory('$modal', [ }; return ModalFactory; } -]).directive('bsModal', [ - '$q', - '$modal', - function ($q, $modal) { - return { - restrict: 'A', - scope: true, - link: function postLink(scope, iElement, iAttrs, controller) { - var options = { - template: scope.$eval(iAttrs.bsModal), - persist: true, - show: false, - scope: scope - }; - angular.forEach([ - 'modalClass', - 'backdrop', - 'keyboard' - ], function (key) { - if (angular.isDefined(iAttrs[key])) - options[key] = iAttrs[key]; - }); - $q.when($modal(options)).then(function onSuccess(modal) { - iElement.attr('data-target', '#' + modal.attr('id')).attr('data-toggle', 'modal'); - }); - } - }; - } -]); -'use strict'; -angular.module('$strap.directives').directive('bsNavbar', [ - '$location', - function ($location) { - return { - restrict: 'A', - link: function postLink(scope, element, attrs, controller) { - scope.$watch(function () { - return $location.path(); - }, function (newValue, oldValue) { - $('li[data-match-route]', element).each(function (k, li) { - var $li = angular.element(li), pattern = $li.attr('data-match-route'), regexp = new RegExp('^' + pattern + '$', ['i']); - if (regexp.test(newValue)) { - $li.addClass('active').find('.collapse.in').collapse('hide'); - } else { - $li.removeClass('active'); - } - }); - }); - } - }; - } -]); -'use strict'; -angular.module('$strap.directives').directive('bsPopover', [ - '$parse', - '$compile', - '$http', - '$timeout', - '$q', - '$templateCache', - function ($parse, $compile, $http, $timeout, $q, $templateCache) { - $('body').on('keyup', function (ev) { - if (ev.keyCode === 27) { - $('.popover.in').each(function () { - $(this).popover('hide'); - }); - } - }); - return { - restrict: 'A', - scope: true, - link: function postLink(scope, element, attr, ctrl) { - var getter = $parse(attr.bsPopover), setter = getter.assign, value = getter(scope), options = {}; - if (angular.isObject(value)) { - options = value; - } - $q.when(options.content || $templateCache.get(value) || $http.get(value, { cache: true })).then(function onSuccess(template) { - if (angular.isObject(template)) { - template = template.data; - } - if (!!attr.unique) { - element.on('show', function (ev) { - $('.popover.in').each(function () { - var $this = $(this), popover = $this.data('popover'); - if (popover && !popover.$element.is(element)) { - $this.popover('hide'); - } - }); - }); - } - if (!!attr.hide) { - scope.$watch(attr.hide, function (newValue, oldValue) { - if (!!newValue) { - popover.hide(); - } else if (newValue !== oldValue) { - popover.show(); - } - }); - } - if (!!attr.show) { - scope.$watch(attr.show, function (newValue, oldValue) { - if (!!newValue) { - $timeout(function () { - popover.show(); - }); - } else if (newValue !== oldValue) { - popover.hide(); - } - }); - } - element.popover(angular.extend({}, options, { - content: template, - html: true - })); - var popover = element.data('popover'); - popover.hasContent = function () { - return this.getTitle() || template; - }; - popover.getPosition = function () { - var r = $.fn.popover.Constructor.prototype.getPosition.apply(this, arguments); - $compile(this.$tip)(scope); - scope.$digest(); - this.$tip.data('popover', this); - return r; - }; - scope.$popover = function (name) { - popover(name); - }; - angular.forEach([ - 'show', - 'hide' - ], function (name) { - scope[name] = function () { - popover[name](); - }; - }); - scope.dismiss = scope.hide; - angular.forEach([ - 'show', - 'shown', - 'hide', - 'hidden' - ], function (name) { - element.on(name, function (ev) { - scope.$emit('popover-' + name, ev); - }); - }); - }); - } - }; - } -]); -'use strict'; -angular.module('$strap.directives').directive('bsSelect', [ - '$timeout', - function ($timeout) { - var NG_OPTIONS_REGEXP = /^\s*(.*?)(?:\s+as\s+(.*?))?(?:\s+group\s+by\s+(.*))?\s+for\s+(?:([\$\w][\$\w\d]*)|(?:\(\s*([\$\w][\$\w\d]*)\s*,\s*([\$\w][\$\w\d]*)\s*\)))\s+in\s+(.*)$/; - return { - restrict: 'A', - require: '?ngModel', - link: function postLink(scope, element, attrs, controller) { - var options = scope.$eval(attrs.bsSelect) || {}; - $timeout(function () { - element.selectpicker(options); - element.next().removeClass('ng-scope'); - }); - if (controller) { - scope.$watch(attrs.ngModel, function (newValue, oldValue) { - if (!angular.equals(newValue, oldValue)) { - element.selectpicker('refresh'); - } - }); - } - } - }; - } -]); +]) 'use strict'; angular.module('$strap.directives').directive('bsTabs', [ '$parse', @@ -731,45 +286,6 @@ angular.module('$strap.directives').directive('bsTabs', [ } ]); 'use strict'; -angular.module('$strap.directives').directive('bsTimepicker', [ - '$timeout', - '$strapConfig', - function ($timeout, $strapConfig) { - var TIME_REGEXP = '((?:(?:[0-1][0-9])|(?:[2][0-3])|(?:[0-9])):(?:[0-5][0-9])(?::[0-5][0-9])?(?:\\s?(?:am|AM|pm|PM))?)'; - return { - restrict: 'A', - require: '?ngModel', - link: function postLink(scope, element, attrs, controller) { - if (controller) { - element.on('changeTime.timepicker', function (ev) { - $timeout(function () { - controller.$setViewValue(element.val()); - }); - }); - var timeRegExp = new RegExp('^' + TIME_REGEXP + '$', ['i']); - controller.$parsers.unshift(function (viewValue) { - if (!viewValue || timeRegExp.test(viewValue)) { - controller.$setValidity('time', true); - return viewValue; - } else { - controller.$setValidity('time', false); - return; - } - }); - } - element.attr('data-toggle', 'timepicker'); - element.parent().addClass('bootstrap-timepicker'); - element.timepicker($strapConfig.timepicker || {}); - var timepicker = element.data('timepicker'); - var component = element.siblings('[data-toggle="timepicker"]'); - if (component.length) { - component.on('click', $.proxy(timepicker.showWidget, timepicker)); - } - } - }; - } -]); -'use strict'; angular.module('$strap.directives').directive('bsTooltip', [ '$parse', '$compile', diff --git a/src/vendor/jquery/jquery-ui-1.10.3.js b/src/vendor/jquery/jquery-ui-1.10.3.js deleted file mode 100644 index e10513c8609..00000000000 --- a/src/vendor/jquery/jquery-ui-1.10.3.js +++ /dev/null @@ -1,5253 +0,0 @@ -/*! jQuery UI - v1.10.3 - 2013-09-23 -* http://jqueryui.com -* Includes: jquery.ui.core.js, jquery.ui.widget.js, jquery.ui.mouse.js, jquery.ui.position.js, jquery.ui.draggable.js, jquery.ui.droppable.js, jquery.ui.resizable.js, jquery.ui.selectable.js, jquery.ui.sortable.js -* Copyright 2013 jQuery Foundation and other contributors; Licensed MIT */ - -(function( $, undefined ) { - -var uuid = 0, - runiqueId = /^ui-id-\d+$/; - -// $.ui might exist from components with no dependencies, e.g., $.ui.position -$.ui = $.ui || {}; - -$.extend( $.ui, { - version: "1.10.3", - - keyCode: { - BACKSPACE: 8, - COMMA: 188, - DELETE: 46, - DOWN: 40, - END: 35, - ENTER: 13, - ESCAPE: 27, - HOME: 36, - LEFT: 37, - NUMPAD_ADD: 107, - NUMPAD_DECIMAL: 110, - NUMPAD_DIVIDE: 111, - NUMPAD_ENTER: 108, - NUMPAD_MULTIPLY: 106, - NUMPAD_SUBTRACT: 109, - PAGE_DOWN: 34, - PAGE_UP: 33, - PERIOD: 190, - RIGHT: 39, - SPACE: 32, - TAB: 9, - UP: 38 - } -}); - -// plugins -$.fn.extend({ - focus: (function( orig ) { - return function( delay, fn ) { - return typeof delay === "number" ? - this.each(function() { - var elem = this; - setTimeout(function() { - $( elem ).focus(); - if ( fn ) { - fn.call( elem ); - } - }, delay ); - }) : - orig.apply( this, arguments ); - }; - })( $.fn.focus ), - - scrollParent: function() { - var scrollParent; - if (($.ui.ie && (/(static|relative)/).test(this.css("position"))) || (/absolute/).test(this.css("position"))) { - scrollParent = this.parents().filter(function() { - return (/(relative|absolute|fixed)/).test($.css(this,"position")) && (/(auto|scroll)/).test($.css(this,"overflow")+$.css(this,"overflow-y")+$.css(this,"overflow-x")); - }).eq(0); - } else { - scrollParent = this.parents().filter(function() { - return (/(auto|scroll)/).test($.css(this,"overflow")+$.css(this,"overflow-y")+$.css(this,"overflow-x")); - }).eq(0); - } - - return (/fixed/).test(this.css("position")) || !scrollParent.length ? $(document) : scrollParent; - }, - - zIndex: function( zIndex ) { - if ( zIndex !== undefined ) { - return this.css( "zIndex", zIndex ); - } - - if ( this.length ) { - var elem = $( this[ 0 ] ), position, value; - while ( elem.length && elem[ 0 ] !== document ) { - // Ignore z-index if position is set to a value where z-index is ignored by the browser - // This makes behavior of this function consistent across browsers - // WebKit always returns auto if the element is positioned - position = elem.css( "position" ); - if ( position === "absolute" || position === "relative" || position === "fixed" ) { - // IE returns 0 when zIndex is not specified - // other browsers return a string - // we ignore the case of nested elements with an explicit value of 0 - //
    - value = parseInt( elem.css( "zIndex" ), 10 ); - if ( !isNaN( value ) && value !== 0 ) { - return value; - } - } - elem = elem.parent(); - } - } - - return 0; - }, - - uniqueId: function() { - return this.each(function() { - if ( !this.id ) { - this.id = "ui-id-" + (++uuid); - } - }); - }, - - removeUniqueId: function() { - return this.each(function() { - if ( runiqueId.test( this.id ) ) { - $( this ).removeAttr( "id" ); - } - }); - } -}); - -// selectors -function focusable( element, isTabIndexNotNaN ) { - var map, mapName, img, - nodeName = element.nodeName.toLowerCase(); - if ( "area" === nodeName ) { - map = element.parentNode; - mapName = map.name; - if ( !element.href || !mapName || map.nodeName.toLowerCase() !== "map" ) { - return false; - } - img = $( "img[usemap=#" + mapName + "]" )[0]; - return !!img && visible( img ); - } - return ( /input|select|textarea|button|object/.test( nodeName ) ? - !element.disabled : - "a" === nodeName ? - element.href || isTabIndexNotNaN : - isTabIndexNotNaN) && - // the element and all of its ancestors must be visible - visible( element ); -} - -function visible( element ) { - return $.expr.filters.visible( element ) && - !$( element ).parents().addBack().filter(function() { - return $.css( this, "visibility" ) === "hidden"; - }).length; -} - -$.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 ] ); - }, - - focusable: function( element ) { - return focusable( element, !isNaN( $.attr( element, "tabindex" ) ) ); - }, - - tabbable: function( element ) { - var tabIndex = $.attr( element, "tabindex" ), - isTabIndexNaN = isNaN( tabIndex ); - return ( isTabIndexNaN || tabIndex >= 0 ) && focusable( element, !isTabIndexNaN ); - } -}); - -// support: jQuery <1.8 -if ( !$( "" ).outerWidth( 1 ).jquery ) { - $.each( [ "Width", "Height" ], function( i, name ) { - var side = name === "Width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ], - type = name.toLowerCase(), - orig = { - innerWidth: $.fn.innerWidth, - innerHeight: $.fn.innerHeight, - outerWidth: $.fn.outerWidth, - outerHeight: $.fn.outerHeight - }; - - function reduce( elem, size, border, margin ) { - $.each( side, function() { - size -= parseFloat( $.css( elem, "padding" + this ) ) || 0; - if ( border ) { - size -= parseFloat( $.css( elem, "border" + this + "Width" ) ) || 0; - } - if ( margin ) { - size -= parseFloat( $.css( elem, "margin" + this ) ) || 0; - } - }); - return size; - } - - $.fn[ "inner" + name ] = function( size ) { - if ( size === undefined ) { - return orig[ "inner" + name ].call( this ); - } - - return this.each(function() { - $( this ).css( type, reduce( this, size ) + "px" ); - }); - }; - - $.fn[ "outer" + name] = function( size, margin ) { - if ( typeof size !== "number" ) { - return orig[ "outer" + name ].call( this, size ); - } - - return this.each(function() { - $( this).css( type, reduce( this, size, true, margin ) + "px" ); - }); - }; - }); -} - -// support: jQuery <1.8 -if ( !$.fn.addBack ) { - $.fn.addBack = function( selector ) { - return this.add( selector == null ? - this.prevObject : this.prevObject.filter( selector ) - ); - }; -} - -// support: jQuery 1.6.1, 1.6.2 (http://bugs.jquery.com/ticket/9413) -if ( $( "" ).data( "a-b", "a" ).removeData( "a-b" ).data( "a-b" ) ) { - $.fn.removeData = (function( removeData ) { - return function( key ) { - if ( arguments.length ) { - return removeData.call( this, $.camelCase( key ) ); - } else { - return removeData.call( this ); - } - }; - })( $.fn.removeData ); -} - - - - - -// deprecated -$.ui.ie = !!/msie [\w.]+/.exec( navigator.userAgent.toLowerCase() ); - -$.support.selectstart = "onselectstart" in document.createElement( "div" ); -$.fn.extend({ - disableSelection: function() { - return this.bind( ( $.support.selectstart ? "selectstart" : "mousedown" ) + - ".ui-disableSelection", function( event ) { - event.preventDefault(); - }); - }, - - enableSelection: function() { - return this.unbind( ".ui-disableSelection" ); - } -}); - -$.extend( $.ui, { - // $.ui.plugin is deprecated. Use $.widget() extensions instead. - 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 ) { - var i, - set = instance.plugins[ name ]; - if ( !set || !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 ); - } - } - } - }, - - // only used by resizable - hasScroll: function( el, a ) { - - //If overflow is hidden, the element might have extra content, but the user wants to hide it - if ( $( el ).css( "overflow" ) === "hidden") { - return false; - } - - var scroll = ( a && a === "left" ) ? "scrollLeft" : "scrollTop", - has = false; - - if ( el[ scroll ] > 0 ) { - return true; - } - - // TODO: determine which cases actually cause this to happen - // if the element doesn't have the scroll set, see if it's possible to - // set the scroll - el[ scroll ] = 1; - has = ( el[ scroll ] > 0 ); - el[ scroll ] = 0; - return has; - } -}); - -})( jQuery ); -(function( $, undefined ) { - -var uuid = 0, - slice = Array.prototype.slice, - _cleanData = $.cleanData; -$.cleanData = function( elems ) { - for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { - try { - $( elem ).triggerHandler( "remove" ); - // http://bugs.jquery.com/ticket/8235 - } catch( e ) {} - } - _cleanData( elems ); -}; - -$.widget = function( name, base, prototype ) { - var fullName, existingConstructor, constructor, basePrototype, - // proxiedPrototype allows the provided prototype to remain unmodified - // so that it can be used as a mixin for multiple widgets (#8876) - proxiedPrototype = {}, - namespace = name.split( "." )[ 0 ]; - - name = name.split( "." )[ 1 ]; - fullName = namespace + "-" + name; - - if ( !prototype ) { - prototype = base; - base = $.Widget; - } - - // 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() { - var _super = function() { - return base.prototype[ prop ].apply( this, arguments ); - }, - _superApply = function( args ) { - return base.prototype[ prop ].apply( this, args ); - }; - return function() { - var __super = this._super, - __superApply = this._superApply, - 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 - }, 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 ); -}; - -$.widget.extend = function( target ) { - var input = slice.call( arguments, 1 ), - inputIndex = 0, - inputLength = input.length, - key, - 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", - args = slice.call( arguments, 1 ), - returnValue = this; - - // allow multiple hashes to be passed on init - options = !isMethodCall && args.length ? - $.widget.extend.apply( null, [ options ].concat(args) ) : - options; - - if ( isMethodCall ) { - this.each(function() { - var methodValue, - instance = $.data( this, fullName ); - 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 { - this.each(function() { - var instance = $.data( this, fullName ); - if ( instance ) { - instance.option( options || {} )._init(); - } else { - $.data( this, fullName, new object( options, this ) ); - } - }); - } - - return returnValue; - }; -}; - -$.Widget = function( /* options, element */ ) {}; -$.Widget._childConstructors = []; - -$.Widget.prototype = { - widgetName: "widget", - widgetEventPrefix: "", - defaultElement: "
    ", - options: { - disabled: false, - - // callbacks - create: null - }, - _createWidget: function( options, element ) { - element = $( element || this.defaultElement || this )[ 0 ]; - this.element = $( element ); - this.uuid = uuid++; - this.eventNamespace = "." + this.widgetName + this.uuid; - this.options = $.widget.extend( {}, - this.options, - this._getCreateOptions(), - options ); - - this.bindings = $(); - this.hoverable = $(); - this.focusable = $(); - - 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._create(); - this._trigger( "create", null, this._getCreateEventData() ); - this._init(); - }, - _getCreateOptions: $.noop, - _getCreateEventData: $.noop, - _create: $.noop, - _init: $.noop, - - destroy: function() { - this._destroy(); - // we can probably remove the unbind calls in 2.0 - // all event bindings should go through this._on() - this.element - .unbind( this.eventNamespace ) - // 1.9 BC for #7810 - // TODO remove dual storage - .removeData( this.widgetName ) - .removeData( this.widgetFullName ) - // support: jquery <1.6.3 - // http://bugs.jquery.com/ticket/9413 - .removeData( $.camelCase( this.widgetFullName ) ); - this.widget() - .unbind( this.eventNamespace ) - .removeAttr( "aria-disabled" ) - .removeClass( - this.widgetFullName + "-disabled " + - "ui-state-disabled" ); - - // clean up events and states - this.bindings.unbind( this.eventNamespace ); - this.hoverable.removeClass( "ui-state-hover" ); - this.focusable.removeClass( "ui-state-focus" ); - }, - _destroy: $.noop, - - widget: function() { - return this.element; - }, - - option: function( key, value ) { - var options = key, - parts, - curOption, - 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 ( value === undefined ) { - return curOption[ key ] === undefined ? null : curOption[ key ]; - } - curOption[ key ] = value; - } else { - if ( value === undefined ) { - 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 ) { - this.options[ key ] = value; - - if ( key === "disabled" ) { - this.widget() - .toggleClass( this.widgetFullName + "-disabled ui-state-disabled", !!value ) - .attr( "aria-disabled", value ); - this.hoverable.removeClass( "ui-state-hover" ); - this.focusable.removeClass( "ui-state-focus" ); - } - - return this; - }, - - enable: function() { - return this._setOption( "disabled", false ); - }, - disable: function() { - return this._setOption( "disabled", true ); - }, - - _on: function( suppressDisabledCheck, element, handlers ) { - var delegateElement, - 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 { - // accept selectors, DOM elements - 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*(.*)$/ ), - eventName = match[1] + instance.eventNamespace, - selector = match[2]; - if ( selector ) { - delegateElement.delegate( selector, eventName, handlerProxy ); - } else { - element.bind( eventName, handlerProxy ); - } - }); - }, - - _off: function( element, eventName ) { - eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) + this.eventNamespace; - element.unbind( eventName ).undelegate( eventName ); - }, - - _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 ) { - $( event.currentTarget ).addClass( "ui-state-hover" ); - }, - mouseleave: function( event ) { - $( event.currentTarget ).removeClass( "ui-state-hover" ); - } - }); - }, - - _focusable: function( element ) { - this.focusable = this.focusable.add( element ); - this._on( element, { - focusin: function( event ) { - $( event.currentTarget ).addClass( "ui-state-focus" ); - }, - focusout: function( event ) { - $( event.currentTarget ).removeClass( "ui-state-focus" ); - } - }); - }, - - _trigger: function( type, event, data ) { - var prop, orig, - 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, - 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(); - }); - } - }; -}); - -})( jQuery ); -(function( $, undefined ) { - -var mouseHandled = false; -$( document ).mouseup( function() { - mouseHandled = false; -}); - -$.widget("ui.mouse", { - version: "1.10.3", - options: { - cancel: "input,textarea,button,select,option", - distance: 1, - delay: 0 - }, - _mouseInit: function() { - var that = this; - - this.element - .bind("mousedown."+this.widgetName, function(event) { - return that._mouseDown(event); - }) - .bind("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.unbind("."+this.widgetName); - if ( this._mouseMoveDelegate ) { - $(document) - .unbind("mousemove."+this.widgetName, this._mouseMoveDelegate) - .unbind("mouseup."+this.widgetName, this._mouseUpDelegate); - } - }, - - _mouseDown: function(event) { - // don't let more than one widget handle mouseStart - if( mouseHandled ) { return; } - - // 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); - }; - $(document) - .bind("mousemove."+this.widgetName, this._mouseMoveDelegate) - .bind("mouseup."+this.widgetName, this._mouseUpDelegate); - - event.preventDefault(); - - mouseHandled = true; - return true; - }, - - _mouseMove: function(event) { - // 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); - } - - 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) { - $(document) - .unbind("mousemove."+this.widgetName, this._mouseMoveDelegate) - .unbind("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); - } - - return false; - }, - - _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; } -}); - -})(jQuery); -(function( $, undefined ) { - -$.ui = $.ui || {}; - -var cachedScrollbarWidth, - max = Math.max, - abs = Math.abs, - round = Math.round, - rhorizontal = /left|center|right/, - rvertical = /top|center|bottom/, - roffset = /[\+\-]\d+(\.[\d]+)?%?/, - rposition = /^\w+/, - rpercent = /%$/, - _position = $.fn.position; - -function getOffsets( offsets, width, height ) { - return [ - parseFloat( offsets[ 0 ] ) * ( rpercent.test( offsets[ 0 ] ) ? width / 100 : 1 ), - parseFloat( offsets[ 1 ] ) * ( rpercent.test( offsets[ 1 ] ) ? height / 100 : 1 ) - ]; -} - -function parseCss( element, property ) { - return parseInt( $.css( element, property ), 10 ) || 0; -} - -function getDimensions( elem ) { - var raw = elem[0]; - if ( raw.nodeType === 9 ) { - return { - width: elem.width(), - height: elem.height(), - offset: { top: 0, left: 0 } - }; - } - if ( $.isWindow( raw ) ) { - return { - width: elem.width(), - height: elem.height(), - offset: { top: elem.scrollTop(), left: elem.scrollLeft() } - }; - } - if ( raw.preventDefault ) { - return { - width: 0, - height: 0, - offset: { top: raw.pageY, left: raw.pageX } - }; - } - return { - width: elem.outerWidth(), - height: elem.outerHeight(), - offset: elem.offset() - }; -} - -$.position = { - scrollbarWidth: function() { - if ( cachedScrollbarWidth !== undefined ) { - return cachedScrollbarWidth; - } - var w1, w2, - div = $( "
    " ), - innerDiv = div.children()[0]; - - $( "body" ).append( div ); - w1 = innerDiv.offsetWidth; - div.css( "overflow", "scroll" ); - - w2 = innerDiv.offsetWidth; - - if ( w1 === w2 ) { - w2 = div[0].clientWidth; - } - - div.remove(); - - return (cachedScrollbarWidth = w1 - w2); - }, - getScrollInfo: function( within ) { - var overflowX = within.isWindow ? "" : within.element.css( "overflow-x" ), - overflowY = within.isWindow ? "" : within.element.css( "overflow-y" ), - hasOverflowX = overflowX === "scroll" || - ( overflowX === "auto" && within.width < within.element[0].scrollWidth ), - hasOverflowY = overflowY === "scroll" || - ( overflowY === "auto" && within.height < within.element[0].scrollHeight ); - return { - width: hasOverflowY ? $.position.scrollbarWidth() : 0, - height: hasOverflowX ? $.position.scrollbarWidth() : 0 - }; - }, - getWithinInfo: function( element ) { - var withinElement = $( element || window ), - isWindow = $.isWindow( withinElement[0] ); - return { - element: withinElement, - isWindow: isWindow, - offset: withinElement.offset() || { left: 0, top: 0 }, - scrollLeft: withinElement.scrollLeft(), - scrollTop: withinElement.scrollTop(), - width: isWindow ? withinElement.width() : withinElement.outerWidth(), - height: isWindow ? withinElement.height() : withinElement.outerHeight() - }; - } -}; - -$.fn.position = function( options ) { - if ( !options || !options.of ) { - return _position.apply( this, arguments ); - } - - // make a copy, we don't want to modify arguments - options = $.extend( {}, options ); - - var atOffset, targetWidth, targetHeight, targetOffset, basePosition, dimensions, - target = $( options.of ), - within = $.position.getWithinInfo( options.within ), - scrollInfo = $.position.getScrollInfo( within ), - collision = ( options.collision || "flip" ).split( " " ), - offsets = {}; - - dimensions = getDimensions( target ); - if ( target[0].preventDefault ) { - // force left top to allow flipping - options.at = "left top"; - } - targetWidth = dimensions.width; - targetHeight = dimensions.height; - targetOffset = dimensions.offset; - // clone to reuse original targetOffset later - basePosition = $.extend( {}, targetOffset ); - - // force my and at to have valid horizontal and vertical positions - // if a value is missing or invalid, it will be converted to center - $.each( [ "my", "at" ], function() { - var pos = ( options[ this ] || "" ).split( " " ), - horizontalOffset, - verticalOffset; - - if ( pos.length === 1) { - pos = rhorizontal.test( pos[ 0 ] ) ? - pos.concat( [ "center" ] ) : - rvertical.test( pos[ 0 ] ) ? - [ "center" ].concat( pos ) : - [ "center", "center" ]; - } - pos[ 0 ] = rhorizontal.test( pos[ 0 ] ) ? pos[ 0 ] : "center"; - pos[ 1 ] = rvertical.test( pos[ 1 ] ) ? pos[ 1 ] : "center"; - - // calculate offsets - horizontalOffset = roffset.exec( pos[ 0 ] ); - verticalOffset = roffset.exec( pos[ 1 ] ); - offsets[ this ] = [ - horizontalOffset ? horizontalOffset[ 0 ] : 0, - verticalOffset ? verticalOffset[ 0 ] : 0 - ]; - - // reduce to just the positions without the offsets - options[ this ] = [ - rposition.exec( pos[ 0 ] )[ 0 ], - rposition.exec( pos[ 1 ] )[ 0 ] - ]; - }); - - // normalize collision option - if ( collision.length === 1 ) { - collision[ 1 ] = collision[ 0 ]; - } - - if ( options.at[ 0 ] === "right" ) { - basePosition.left += targetWidth; - } else if ( options.at[ 0 ] === "center" ) { - basePosition.left += targetWidth / 2; - } - - if ( options.at[ 1 ] === "bottom" ) { - basePosition.top += targetHeight; - } else if ( options.at[ 1 ] === "center" ) { - basePosition.top += targetHeight / 2; - } - - atOffset = getOffsets( offsets.at, targetWidth, targetHeight ); - basePosition.left += atOffset[ 0 ]; - basePosition.top += atOffset[ 1 ]; - - return this.each(function() { - var collisionPosition, using, - elem = $( this ), - elemWidth = elem.outerWidth(), - elemHeight = elem.outerHeight(), - marginLeft = parseCss( this, "marginLeft" ), - marginTop = parseCss( this, "marginTop" ), - collisionWidth = elemWidth + marginLeft + parseCss( this, "marginRight" ) + scrollInfo.width, - collisionHeight = elemHeight + marginTop + parseCss( this, "marginBottom" ) + scrollInfo.height, - position = $.extend( {}, basePosition ), - myOffset = getOffsets( offsets.my, elem.outerWidth(), elem.outerHeight() ); - - if ( options.my[ 0 ] === "right" ) { - position.left -= elemWidth; - } else if ( options.my[ 0 ] === "center" ) { - position.left -= elemWidth / 2; - } - - if ( options.my[ 1 ] === "bottom" ) { - position.top -= elemHeight; - } else if ( options.my[ 1 ] === "center" ) { - position.top -= elemHeight / 2; - } - - position.left += myOffset[ 0 ]; - position.top += myOffset[ 1 ]; - - // if the browser doesn't support fractions, then round for consistent results - if ( !$.support.offsetFractions ) { - position.left = round( position.left ); - position.top = round( position.top ); - } - - collisionPosition = { - marginLeft: marginLeft, - marginTop: marginTop - }; - - $.each( [ "left", "top" ], function( i, dir ) { - if ( $.ui.position[ collision[ i ] ] ) { - $.ui.position[ collision[ i ] ][ dir ]( position, { - targetWidth: targetWidth, - targetHeight: targetHeight, - elemWidth: elemWidth, - elemHeight: elemHeight, - collisionPosition: collisionPosition, - collisionWidth: collisionWidth, - collisionHeight: collisionHeight, - offset: [ atOffset[ 0 ] + myOffset[ 0 ], atOffset [ 1 ] + myOffset[ 1 ] ], - my: options.my, - at: options.at, - within: within, - elem : elem - }); - } - }); - - if ( options.using ) { - // adds feedback as second argument to using callback, if present - using = function( props ) { - var left = targetOffset.left - position.left, - right = left + targetWidth - elemWidth, - top = targetOffset.top - position.top, - bottom = top + targetHeight - elemHeight, - feedback = { - target: { - element: target, - left: targetOffset.left, - top: targetOffset.top, - width: targetWidth, - height: targetHeight - }, - element: { - element: elem, - left: position.left, - top: position.top, - width: elemWidth, - height: elemHeight - }, - horizontal: right < 0 ? "left" : left > 0 ? "right" : "center", - vertical: bottom < 0 ? "top" : top > 0 ? "bottom" : "middle" - }; - if ( targetWidth < elemWidth && abs( left + right ) < targetWidth ) { - feedback.horizontal = "center"; - } - if ( targetHeight < elemHeight && abs( top + bottom ) < targetHeight ) { - feedback.vertical = "middle"; - } - if ( max( abs( left ), abs( right ) ) > max( abs( top ), abs( bottom ) ) ) { - feedback.important = "horizontal"; - } else { - feedback.important = "vertical"; - } - options.using.call( this, props, feedback ); - }; - } - - elem.offset( $.extend( position, { using: using } ) ); - }); -}; - -$.ui.position = { - fit: { - left: function( position, data ) { - var within = data.within, - withinOffset = within.isWindow ? within.scrollLeft : within.offset.left, - outerWidth = within.width, - collisionPosLeft = position.left - data.collisionPosition.marginLeft, - overLeft = withinOffset - collisionPosLeft, - overRight = collisionPosLeft + data.collisionWidth - outerWidth - withinOffset, - newOverRight; - - // element is wider than within - if ( data.collisionWidth > outerWidth ) { - // element is initially over the left side of within - if ( overLeft > 0 && overRight <= 0 ) { - newOverRight = position.left + overLeft + data.collisionWidth - outerWidth - withinOffset; - position.left += overLeft - newOverRight; - // element is initially over right side of within - } else if ( overRight > 0 && overLeft <= 0 ) { - position.left = withinOffset; - // element is initially over both left and right sides of within - } else { - if ( overLeft > overRight ) { - position.left = withinOffset + outerWidth - data.collisionWidth; - } else { - position.left = withinOffset; - } - } - // too far left -> align with left edge - } else if ( overLeft > 0 ) { - position.left += overLeft; - // too far right -> align with right edge - } else if ( overRight > 0 ) { - position.left -= overRight; - // adjust based on position and margin - } else { - position.left = max( position.left - collisionPosLeft, position.left ); - } - }, - top: function( position, data ) { - var within = data.within, - withinOffset = within.isWindow ? within.scrollTop : within.offset.top, - outerHeight = data.within.height, - collisionPosTop = position.top - data.collisionPosition.marginTop, - overTop = withinOffset - collisionPosTop, - overBottom = collisionPosTop + data.collisionHeight - outerHeight - withinOffset, - newOverBottom; - - // element is taller than within - if ( data.collisionHeight > outerHeight ) { - // element is initially over the top of within - if ( overTop > 0 && overBottom <= 0 ) { - newOverBottom = position.top + overTop + data.collisionHeight - outerHeight - withinOffset; - position.top += overTop - newOverBottom; - // element is initially over bottom of within - } else if ( overBottom > 0 && overTop <= 0 ) { - position.top = withinOffset; - // element is initially over both top and bottom of within - } else { - if ( overTop > overBottom ) { - position.top = withinOffset + outerHeight - data.collisionHeight; - } else { - position.top = withinOffset; - } - } - // too far up -> align with top - } else if ( overTop > 0 ) { - position.top += overTop; - // too far down -> align with bottom edge - } else if ( overBottom > 0 ) { - position.top -= overBottom; - // adjust based on position and margin - } else { - position.top = max( position.top - collisionPosTop, position.top ); - } - } - }, - flip: { - left: function( position, data ) { - var within = data.within, - withinOffset = within.offset.left + within.scrollLeft, - outerWidth = within.width, - offsetLeft = within.isWindow ? within.scrollLeft : within.offset.left, - collisionPosLeft = position.left - data.collisionPosition.marginLeft, - overLeft = collisionPosLeft - offsetLeft, - overRight = collisionPosLeft + data.collisionWidth - outerWidth - offsetLeft, - myOffset = data.my[ 0 ] === "left" ? - -data.elemWidth : - data.my[ 0 ] === "right" ? - data.elemWidth : - 0, - atOffset = data.at[ 0 ] === "left" ? - data.targetWidth : - data.at[ 0 ] === "right" ? - -data.targetWidth : - 0, - offset = -2 * data.offset[ 0 ], - newOverRight, - newOverLeft; - - if ( overLeft < 0 ) { - newOverRight = position.left + myOffset + atOffset + offset + data.collisionWidth - outerWidth - withinOffset; - if ( newOverRight < 0 || newOverRight < abs( overLeft ) ) { - position.left += myOffset + atOffset + offset; - } - } - else if ( overRight > 0 ) { - newOverLeft = position.left - data.collisionPosition.marginLeft + myOffset + atOffset + offset - offsetLeft; - if ( newOverLeft > 0 || abs( newOverLeft ) < overRight ) { - position.left += myOffset + atOffset + offset; - } - } - }, - top: function( position, data ) { - var within = data.within, - withinOffset = within.offset.top + within.scrollTop, - outerHeight = within.height, - offsetTop = within.isWindow ? within.scrollTop : within.offset.top, - collisionPosTop = position.top - data.collisionPosition.marginTop, - overTop = collisionPosTop - offsetTop, - overBottom = collisionPosTop + data.collisionHeight - outerHeight - offsetTop, - top = data.my[ 1 ] === "top", - myOffset = top ? - -data.elemHeight : - data.my[ 1 ] === "bottom" ? - data.elemHeight : - 0, - atOffset = data.at[ 1 ] === "top" ? - data.targetHeight : - data.at[ 1 ] === "bottom" ? - -data.targetHeight : - 0, - offset = -2 * data.offset[ 1 ], - newOverTop, - newOverBottom; - if ( overTop < 0 ) { - newOverBottom = position.top + myOffset + atOffset + offset + data.collisionHeight - outerHeight - withinOffset; - if ( ( position.top + myOffset + atOffset + offset) > overTop && ( newOverBottom < 0 || newOverBottom < abs( overTop ) ) ) { - position.top += myOffset + atOffset + offset; - } - } - else if ( overBottom > 0 ) { - newOverTop = position.top - data.collisionPosition.marginTop + myOffset + atOffset + offset - offsetTop; - if ( ( position.top + myOffset + atOffset + offset) > overBottom && ( newOverTop > 0 || abs( newOverTop ) < overBottom ) ) { - position.top += myOffset + atOffset + offset; - } - } - } - }, - flipfit: { - left: function() { - $.ui.position.flip.left.apply( this, arguments ); - $.ui.position.fit.left.apply( this, arguments ); - }, - top: function() { - $.ui.position.flip.top.apply( this, arguments ); - $.ui.position.fit.top.apply( this, arguments ); - } - } -}; - -// fraction support test -(function () { - var testElement, testElementParent, testElementStyle, offsetLeft, i, - body = document.getElementsByTagName( "body" )[ 0 ], - div = document.createElement( "div" ); - - //Create a "fake body" for testing based on method used in jQuery.support - testElement = document.createElement( body ? "div" : "body" ); - testElementStyle = { - visibility: "hidden", - width: 0, - height: 0, - border: 0, - margin: 0, - background: "none" - }; - if ( body ) { - $.extend( testElementStyle, { - position: "absolute", - left: "-1000px", - top: "-1000px" - }); - } - for ( i in testElementStyle ) { - testElement.style[ i ] = testElementStyle[ i ]; - } - testElement.appendChild( div ); - testElementParent = body || document.documentElement; - testElementParent.insertBefore( testElement, testElementParent.firstChild ); - - div.style.cssText = "position: absolute; left: 10.7432222px;"; - - offsetLeft = $( div ).offset().left; - $.support.offsetFractions = offsetLeft > 10 && offsetLeft < 11; - - testElement.innerHTML = ""; - testElementParent.removeChild( testElement ); -})(); - -}( jQuery ) ); -(function( $, undefined ) { - -$.widget("ui.draggable", $.ui.mouse, { - version: "1.10.3", - widgetEventPrefix: "drag", - options: { - addClasses: true, - appendTo: "parent", - axis: false, - connectToSortable: false, - containment: false, - cursor: "auto", - cursorAt: false, - grid: false, - handle: false, - helper: "original", - iframeFix: false, - opacity: false, - refreshPositions: false, - revert: false, - revertDuration: 500, - scope: "default", - scroll: true, - scrollSensitivity: 20, - scrollSpeed: 20, - snap: false, - snapMode: "both", - snapTolerance: 20, - stack: false, - zIndex: false, - - // callbacks - drag: null, - start: null, - stop: null - }, - _create: function() { - - if (this.options.helper === "original" && !(/^(?:r|a|f)/).test(this.element.css("position"))) { - this.element[0].style.position = "relative"; - } - if (this.options.addClasses){ - this.element.addClass("ui-draggable"); - } - if (this.options.disabled){ - this.element.addClass("ui-draggable-disabled"); - } - - this._mouseInit(); - - }, - - _destroy: function() { - this.element.removeClass( "ui-draggable ui-draggable-dragging ui-draggable-disabled" ); - this._mouseDestroy(); - }, - - _mouseCapture: function(event) { - - var o = this.options; - - // among others, prevent a drag on a resizable-handle - if (this.helper || o.disabled || $(event.target).closest(".ui-resizable-handle").length > 0) { - return false; - } - - //Quit if we're not on a valid handle - this.handle = this._getHandle(event); - if (!this.handle) { - return false; - } - - $(o.iframeFix === true ? "iframe" : o.iframeFix).each(function() { - $("
    ") - .css({ - width: this.offsetWidth+"px", height: this.offsetHeight+"px", - position: "absolute", opacity: "0.001", zIndex: 1000 - }) - .css($(this).offset()) - .appendTo("body"); - }); - - return true; - - }, - - _mouseStart: function(event) { - - var o = this.options; - - //Create and append the visible helper - this.helper = this._createHelper(event); - - this.helper.addClass("ui-draggable-dragging"); - - //Cache the helper size - this._cacheHelperProportions(); - - //If ddmanager is used for droppables, set the global draggable - if($.ui.ddmanager) { - $.ui.ddmanager.current = this; - } - - /* - * - Position generation - - * This block generates everything position related - it's the core of draggables. - */ - - //Cache the margins of the original element - this._cacheMargins(); - - //Store the helper's css position - this.cssPosition = this.helper.css( "position" ); - this.scrollParent = this.helper.scrollParent(); - this.offsetParent = this.helper.offsetParent(); - this.offsetParentCssPosition = this.offsetParent.css( "position" ); - - //The element's absolute position on the page minus margins - this.offset = this.positionAbs = this.element.offset(); - this.offset = { - top: this.offset.top - this.margins.top, - left: this.offset.left - this.margins.left - }; - - //Reset scroll cache - this.offset.scroll = false; - - $.extend(this.offset, { - click: { //Where the click happened, relative to the element - left: event.pageX - this.offset.left, - top: event.pageY - this.offset.top - }, - parent: this._getParentOffset(), - relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper - }); - - //Generate the original position - this.originalPosition = this.position = this._generatePosition(event); - this.originalPageX = event.pageX; - this.originalPageY = event.pageY; - - //Adjust the mouse offset relative to the helper if "cursorAt" is supplied - (o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt)); - - //Set a containment if given in the options - this._setContainment(); - - //Trigger event + callbacks - if(this._trigger("start", event) === false) { - this._clear(); - return false; - } - - //Recache the helper size - this._cacheHelperProportions(); - - //Prepare the droppable offsets - if ($.ui.ddmanager && !o.dropBehaviour) { - $.ui.ddmanager.prepareOffsets(this, event); - } - - - this._mouseDrag(event, true); //Execute the drag once - this causes the helper not to be visible before getting its correct position - - //If the ddmanager is used for droppables, inform the manager that dragging has started (see #5003) - if ( $.ui.ddmanager ) { - $.ui.ddmanager.dragStart(this, event); - } - - return true; - }, - - _mouseDrag: function(event, noPropagation) { - // reset any necessary cached properties (see #5009) - if ( this.offsetParentCssPosition === "fixed" ) { - this.offset.parent = this._getParentOffset(); - } - - //Compute the helpers position - this.position = this._generatePosition(event); - this.positionAbs = this._convertPositionTo("absolute"); - - //Call plugins and callbacks and use the resulting position if something is returned - if (!noPropagation) { - var ui = this._uiHash(); - if(this._trigger("drag", event, ui) === false) { - this._mouseUp({}); - return false; - } - this.position = ui.position; - } - - if(!this.options.axis || this.options.axis !== "y") { - this.helper[0].style.left = this.position.left+"px"; - } - if(!this.options.axis || this.options.axis !== "x") { - this.helper[0].style.top = this.position.top+"px"; - } - if($.ui.ddmanager) { - $.ui.ddmanager.drag(this, event); - } - - return false; - }, - - _mouseStop: function(event) { - - //If we are using droppables, inform the manager about the drop - var that = this, - dropped = false; - if ($.ui.ddmanager && !this.options.dropBehaviour) { - dropped = $.ui.ddmanager.drop(this, event); - } - - //if a drop comes from outside (a sortable) - if(this.dropped) { - dropped = this.dropped; - this.dropped = false; - } - - //if the original element is no longer in the DOM don't bother to continue (see #8269) - if ( this.options.helper === "original" && !$.contains( this.element[ 0 ].ownerDocument, this.element[ 0 ] ) ) { - return false; - } - - if((this.options.revert === "invalid" && !dropped) || (this.options.revert === "valid" && dropped) || this.options.revert === true || ($.isFunction(this.options.revert) && this.options.revert.call(this.element, dropped))) { - $(this.helper).animate(this.originalPosition, parseInt(this.options.revertDuration, 10), function() { - if(that._trigger("stop", event) !== false) { - that._clear(); - } - }); - } else { - if(this._trigger("stop", event) !== false) { - this._clear(); - } - } - - return false; - }, - - _mouseUp: function(event) { - //Remove frame helpers - $("div.ui-draggable-iframeFix").each(function() { - this.parentNode.removeChild(this); - }); - - //If the ddmanager is used for droppables, inform the manager that dragging has stopped (see #5003) - if( $.ui.ddmanager ) { - $.ui.ddmanager.dragStop(this, event); - } - - return $.ui.mouse.prototype._mouseUp.call(this, event); - }, - - cancel: function() { - - if(this.helper.is(".ui-draggable-dragging")) { - this._mouseUp({}); - } else { - this._clear(); - } - - return this; - - }, - - _getHandle: function(event) { - return this.options.handle ? - !!$( event.target ).closest( this.element.find( this.options.handle ) ).length : - true; - }, - - _createHelper: function(event) { - - var o = this.options, - helper = $.isFunction(o.helper) ? $(o.helper.apply(this.element[0], [event])) : (o.helper === "clone" ? this.element.clone().removeAttr("id") : this.element); - - if(!helper.parents("body").length) { - helper.appendTo((o.appendTo === "parent" ? this.element[0].parentNode : o.appendTo)); - } - - if(helper[0] !== this.element[0] && !(/(fixed|absolute)/).test(helper.css("position"))) { - helper.css("position", "absolute"); - } - - return helper; - - }, - - _adjustOffsetFromHelper: function(obj) { - if (typeof obj === "string") { - obj = obj.split(" "); - } - if ($.isArray(obj)) { - obj = {left: +obj[0], top: +obj[1] || 0}; - } - if ("left" in obj) { - this.offset.click.left = obj.left + this.margins.left; - } - if ("right" in obj) { - this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left; - } - if ("top" in obj) { - this.offset.click.top = obj.top + this.margins.top; - } - if ("bottom" in obj) { - this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top; - } - }, - - _getParentOffset: function() { - - //Get the offsetParent and cache its position - var po = this.offsetParent.offset(); - - // This is a special case where we need to modify a offset calculated on start, since the following happened: - // 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent - // 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that - // the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag - if(this.cssPosition === "absolute" && this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) { - po.left += this.scrollParent.scrollLeft(); - po.top += this.scrollParent.scrollTop(); - } - - //This needs to be actually done for all browsers, since pageX/pageY includes this information - //Ugly IE fix - if((this.offsetParent[0] === document.body) || - (this.offsetParent[0].tagName && this.offsetParent[0].tagName.toLowerCase() === "html" && $.ui.ie)) { - po = { top: 0, left: 0 }; - } - - return { - top: po.top + (parseInt(this.offsetParent.css("borderTopWidth"),10) || 0), - left: po.left + (parseInt(this.offsetParent.css("borderLeftWidth"),10) || 0) - }; - - }, - - _getRelativeOffset: function() { - - if(this.cssPosition === "relative") { - var p = this.element.position(); - return { - top: p.top - (parseInt(this.helper.css("top"),10) || 0) + this.scrollParent.scrollTop(), - left: p.left - (parseInt(this.helper.css("left"),10) || 0) + this.scrollParent.scrollLeft() - }; - } else { - return { top: 0, left: 0 }; - } - - }, - - _cacheMargins: function() { - this.margins = { - left: (parseInt(this.element.css("marginLeft"),10) || 0), - top: (parseInt(this.element.css("marginTop"),10) || 0), - right: (parseInt(this.element.css("marginRight"),10) || 0), - bottom: (parseInt(this.element.css("marginBottom"),10) || 0) - }; - }, - - _cacheHelperProportions: function() { - this.helperProportions = { - width: this.helper.outerWidth(), - height: this.helper.outerHeight() - }; - }, - - _setContainment: function() { - - var over, c, ce, - o = this.options; - - if ( !o.containment ) { - this.containment = null; - return; - } - - if ( o.containment === "window" ) { - this.containment = [ - $( window ).scrollLeft() - this.offset.relative.left - this.offset.parent.left, - $( window ).scrollTop() - this.offset.relative.top - this.offset.parent.top, - $( window ).scrollLeft() + $( window ).width() - this.helperProportions.width - this.margins.left, - $( window ).scrollTop() + ( $( window ).height() || document.body.parentNode.scrollHeight ) - this.helperProportions.height - this.margins.top - ]; - return; - } - - if ( o.containment === "document") { - this.containment = [ - 0, - 0, - $( document ).width() - this.helperProportions.width - this.margins.left, - ( $( document ).height() || document.body.parentNode.scrollHeight ) - this.helperProportions.height - this.margins.top - ]; - return; - } - - if ( o.containment.constructor === Array ) { - this.containment = o.containment; - return; - } - - if ( o.containment === "parent" ) { - o.containment = this.helper[ 0 ].parentNode; - } - - c = $( o.containment ); - ce = c[ 0 ]; - - if( !ce ) { - return; - } - - over = c.css( "overflow" ) !== "hidden"; - - this.containment = [ - ( parseInt( c.css( "borderLeftWidth" ), 10 ) || 0 ) + ( parseInt( c.css( "paddingLeft" ), 10 ) || 0 ), - ( parseInt( c.css( "borderTopWidth" ), 10 ) || 0 ) + ( parseInt( c.css( "paddingTop" ), 10 ) || 0 ) , - ( over ? Math.max( ce.scrollWidth, ce.offsetWidth ) : ce.offsetWidth ) - ( parseInt( c.css( "borderRightWidth" ), 10 ) || 0 ) - ( parseInt( c.css( "paddingRight" ), 10 ) || 0 ) - this.helperProportions.width - this.margins.left - this.margins.right, - ( over ? Math.max( ce.scrollHeight, ce.offsetHeight ) : ce.offsetHeight ) - ( parseInt( c.css( "borderBottomWidth" ), 10 ) || 0 ) - ( parseInt( c.css( "paddingBottom" ), 10 ) || 0 ) - this.helperProportions.height - this.margins.top - this.margins.bottom - ]; - this.relative_container = c; - }, - - _convertPositionTo: function(d, pos) { - - if(!pos) { - pos = this.position; - } - - var mod = d === "absolute" ? 1 : -1, - scroll = this.cssPosition === "absolute" && !( this.scrollParent[ 0 ] !== document && $.contains( this.scrollParent[ 0 ], this.offsetParent[ 0 ] ) ) ? this.offsetParent : this.scrollParent; - - //Cache the scroll - if (!this.offset.scroll) { - this.offset.scroll = {top : scroll.scrollTop(), left : scroll.scrollLeft()}; - } - - return { - top: ( - pos.top + // The absolute mouse position - this.offset.relative.top * mod + // Only for relative positioned nodes: Relative offset from element to offset parent - this.offset.parent.top * mod - // The offsetParent's offset without borders (offset + border) - ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollTop() : this.offset.scroll.top ) * mod ) - ), - left: ( - pos.left + // The absolute mouse position - this.offset.relative.left * mod + // Only for relative positioned nodes: Relative offset from element to offset parent - this.offset.parent.left * mod - // The offsetParent's offset without borders (offset + border) - ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollLeft() : this.offset.scroll.left ) * mod ) - ) - }; - - }, - - _generatePosition: function(event) { - - var containment, co, top, left, - o = this.options, - scroll = this.cssPosition === "absolute" && !( this.scrollParent[ 0 ] !== document && $.contains( this.scrollParent[ 0 ], this.offsetParent[ 0 ] ) ) ? this.offsetParent : this.scrollParent, - pageX = event.pageX, - pageY = event.pageY; - - //Cache the scroll - if (!this.offset.scroll) { - this.offset.scroll = {top : scroll.scrollTop(), left : scroll.scrollLeft()}; - } - - /* - * - Position constraining - - * Constrain the position to a mix of grid, containment. - */ - - // If we are not dragging yet, we won't check for options - if ( this.originalPosition ) { - if ( this.containment ) { - if ( this.relative_container ){ - co = this.relative_container.offset(); - containment = [ - this.containment[ 0 ] + co.left, - this.containment[ 1 ] + co.top, - this.containment[ 2 ] + co.left, - this.containment[ 3 ] + co.top - ]; - } - else { - containment = this.containment; - } - - if(event.pageX - this.offset.click.left < containment[0]) { - pageX = containment[0] + this.offset.click.left; - } - if(event.pageY - this.offset.click.top < containment[1]) { - pageY = containment[1] + this.offset.click.top; - } - if(event.pageX - this.offset.click.left > containment[2]) { - pageX = containment[2] + this.offset.click.left; - } - if(event.pageY - this.offset.click.top > containment[3]) { - pageY = containment[3] + this.offset.click.top; - } - } - - if(o.grid) { - //Check for grid elements set to 0 to prevent divide by 0 error causing invalid argument errors in IE (see ticket #6950) - top = o.grid[1] ? this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1] : this.originalPageY; - pageY = containment ? ((top - this.offset.click.top >= containment[1] || top - this.offset.click.top > containment[3]) ? top : ((top - this.offset.click.top >= containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top; - - left = o.grid[0] ? this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0] : this.originalPageX; - pageX = containment ? ((left - this.offset.click.left >= containment[0] || left - this.offset.click.left > containment[2]) ? left : ((left - this.offset.click.left >= containment[0]) ? left - o.grid[0] : left + o.grid[0])) : left; - } - - } - - return { - top: ( - pageY - // The absolute mouse position - this.offset.click.top - // Click offset (relative to the element) - this.offset.relative.top - // Only for relative positioned nodes: Relative offset from element to offset parent - this.offset.parent.top + // The offsetParent's offset without borders (offset + border) - ( this.cssPosition === "fixed" ? -this.scrollParent.scrollTop() : this.offset.scroll.top ) - ), - left: ( - pageX - // The absolute mouse position - this.offset.click.left - // Click offset (relative to the element) - this.offset.relative.left - // Only for relative positioned nodes: Relative offset from element to offset parent - this.offset.parent.left + // The offsetParent's offset without borders (offset + border) - ( this.cssPosition === "fixed" ? -this.scrollParent.scrollLeft() : this.offset.scroll.left ) - ) - }; - - }, - - _clear: function() { - this.helper.removeClass("ui-draggable-dragging"); - if(this.helper[0] !== this.element[0] && !this.cancelHelperRemoval) { - this.helper.remove(); - } - this.helper = null; - this.cancelHelperRemoval = false; - }, - - // From now on bulk stuff - mainly helpers - - _trigger: function(type, event, ui) { - ui = ui || this._uiHash(); - $.ui.plugin.call(this, type, [event, ui]); - //The absolute position has to be recalculated after plugins - if(type === "drag") { - this.positionAbs = this._convertPositionTo("absolute"); - } - return $.Widget.prototype._trigger.call(this, type, event, ui); - }, - - plugins: {}, - - _uiHash: function() { - return { - helper: this.helper, - position: this.position, - originalPosition: this.originalPosition, - offset: this.positionAbs - }; - } - -}); - -$.ui.plugin.add("draggable", "connectToSortable", { - start: function(event, ui) { - - var inst = $(this).data("ui-draggable"), o = inst.options, - uiSortable = $.extend({}, ui, { item: inst.element }); - inst.sortables = []; - $(o.connectToSortable).each(function() { - var sortable = $.data(this, "ui-sortable"); - if (sortable && !sortable.options.disabled) { - inst.sortables.push({ - instance: sortable, - shouldRevert: sortable.options.revert - }); - sortable.refreshPositions(); // Call the sortable's refreshPositions at drag start to refresh the containerCache since the sortable container cache is used in drag and needs to be up to date (this will ensure it's initialised as well as being kept in step with any changes that might have happened on the page). - sortable._trigger("activate", event, uiSortable); - } - }); - - }, - stop: function(event, ui) { - - //If we are still over the sortable, we fake the stop event of the sortable, but also remove helper - var inst = $(this).data("ui-draggable"), - uiSortable = $.extend({}, ui, { item: inst.element }); - - $.each(inst.sortables, function() { - if(this.instance.isOver) { - - this.instance.isOver = 0; - - inst.cancelHelperRemoval = true; //Don't remove the helper in the draggable instance - this.instance.cancelHelperRemoval = false; //Remove it in the sortable instance (so sortable plugins like revert still work) - - //The sortable revert is supported, and we have to set a temporary dropped variable on the draggable to support revert: "valid/invalid" - if(this.shouldRevert) { - this.instance.options.revert = this.shouldRevert; - } - - //Trigger the stop of the sortable - this.instance._mouseStop(event); - - this.instance.options.helper = this.instance.options._helper; - - //If the helper has been the original item, restore properties in the sortable - if(inst.options.helper === "original") { - this.instance.currentItem.css({ top: "auto", left: "auto" }); - } - - } else { - this.instance.cancelHelperRemoval = false; //Remove the helper in the sortable instance - this.instance._trigger("deactivate", event, uiSortable); - } - - }); - - }, - drag: function(event, ui) { - - var inst = $(this).data("ui-draggable"), that = this; - - $.each(inst.sortables, function() { - - var innermostIntersecting = false, - thisSortable = this; - - //Copy over some variables to allow calling the sortable's native _intersectsWith - this.instance.positionAbs = inst.positionAbs; - this.instance.helperProportions = inst.helperProportions; - this.instance.offset.click = inst.offset.click; - - if(this.instance._intersectsWith(this.instance.containerCache)) { - innermostIntersecting = true; - $.each(inst.sortables, function () { - this.instance.positionAbs = inst.positionAbs; - this.instance.helperProportions = inst.helperProportions; - this.instance.offset.click = inst.offset.click; - if (this !== thisSortable && - this.instance._intersectsWith(this.instance.containerCache) && - $.contains(thisSortable.instance.element[0], this.instance.element[0]) - ) { - innermostIntersecting = false; - } - return innermostIntersecting; - }); - } - - - if(innermostIntersecting) { - //If it intersects, we use a little isOver variable and set it once, so our move-in stuff gets fired only once - if(!this.instance.isOver) { - - this.instance.isOver = 1; - //Now we fake the start of dragging for the sortable instance, - //by cloning the list group item, appending it to the sortable and using it as inst.currentItem - //We can then fire the start event of the sortable with our passed browser event, and our own helper (so it doesn't create a new one) - this.instance.currentItem = $(that).clone().removeAttr("id").appendTo(this.instance.element).data("ui-sortable-item", true); - this.instance.options._helper = this.instance.options.helper; //Store helper option to later restore it - this.instance.options.helper = function() { return ui.helper[0]; }; - - event.target = this.instance.currentItem[0]; - this.instance._mouseCapture(event, true); - this.instance._mouseStart(event, true, true); - - //Because the browser event is way off the new appended portlet, we modify a couple of variables to reflect the changes - this.instance.offset.click.top = inst.offset.click.top; - this.instance.offset.click.left = inst.offset.click.left; - this.instance.offset.parent.left -= inst.offset.parent.left - this.instance.offset.parent.left; - this.instance.offset.parent.top -= inst.offset.parent.top - this.instance.offset.parent.top; - - inst._trigger("toSortable", event); - inst.dropped = this.instance.element; //draggable revert needs that - //hack so receive/update callbacks work (mostly) - inst.currentItem = inst.element; - this.instance.fromOutside = inst; - - } - - //Provided we did all the previous steps, we can fire the drag event of the sortable on every draggable drag, when it intersects with the sortable - if(this.instance.currentItem) { - this.instance._mouseDrag(event); - } - - } else { - - //If it doesn't intersect with the sortable, and it intersected before, - //we fake the drag stop of the sortable, but make sure it doesn't remove the helper by using cancelHelperRemoval - if(this.instance.isOver) { - - this.instance.isOver = 0; - this.instance.cancelHelperRemoval = true; - - //Prevent reverting on this forced stop - this.instance.options.revert = false; - - // The out event needs to be triggered independently - this.instance._trigger("out", event, this.instance._uiHash(this.instance)); - - this.instance._mouseStop(event, true); - this.instance.options.helper = this.instance.options._helper; - - //Now we remove our currentItem, the list group clone again, and the placeholder, and animate the helper back to it's original size - this.instance.currentItem.remove(); - if(this.instance.placeholder) { - this.instance.placeholder.remove(); - } - - inst._trigger("fromSortable", event); - inst.dropped = false; //draggable revert needs that - } - - } - - }); - - } -}); - -$.ui.plugin.add("draggable", "cursor", { - start: function() { - var t = $("body"), o = $(this).data("ui-draggable").options; - if (t.css("cursor")) { - o._cursor = t.css("cursor"); - } - t.css("cursor", o.cursor); - }, - stop: function() { - var o = $(this).data("ui-draggable").options; - if (o._cursor) { - $("body").css("cursor", o._cursor); - } - } -}); - -$.ui.plugin.add("draggable", "opacity", { - start: function(event, ui) { - var t = $(ui.helper), o = $(this).data("ui-draggable").options; - if(t.css("opacity")) { - o._opacity = t.css("opacity"); - } - t.css("opacity", o.opacity); - }, - stop: function(event, ui) { - var o = $(this).data("ui-draggable").options; - if(o._opacity) { - $(ui.helper).css("opacity", o._opacity); - } - } -}); - -$.ui.plugin.add("draggable", "scroll", { - start: function() { - var i = $(this).data("ui-draggable"); - if(i.scrollParent[0] !== document && i.scrollParent[0].tagName !== "HTML") { - i.overflowOffset = i.scrollParent.offset(); - } - }, - drag: function( event ) { - - var i = $(this).data("ui-draggable"), o = i.options, scrolled = false; - - if(i.scrollParent[0] !== document && i.scrollParent[0].tagName !== "HTML") { - - if(!o.axis || o.axis !== "x") { - if((i.overflowOffset.top + i.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity) { - i.scrollParent[0].scrollTop = scrolled = i.scrollParent[0].scrollTop + o.scrollSpeed; - } else if(event.pageY - i.overflowOffset.top < o.scrollSensitivity) { - i.scrollParent[0].scrollTop = scrolled = i.scrollParent[0].scrollTop - o.scrollSpeed; - } - } - - if(!o.axis || o.axis !== "y") { - if((i.overflowOffset.left + i.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity) { - i.scrollParent[0].scrollLeft = scrolled = i.scrollParent[0].scrollLeft + o.scrollSpeed; - } else if(event.pageX - i.overflowOffset.left < o.scrollSensitivity) { - i.scrollParent[0].scrollLeft = scrolled = i.scrollParent[0].scrollLeft - o.scrollSpeed; - } - } - - } else { - - if(!o.axis || o.axis !== "x") { - if(event.pageY - $(document).scrollTop() < o.scrollSensitivity) { - scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed); - } else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity) { - scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed); - } - } - - if(!o.axis || o.axis !== "y") { - if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity) { - scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed); - } else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity) { - scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed); - } - } - - } - - if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour) { - $.ui.ddmanager.prepareOffsets(i, event); - } - - } -}); - -$.ui.plugin.add("draggable", "snap", { - start: function() { - - var i = $(this).data("ui-draggable"), - o = i.options; - - i.snapElements = []; - - $(o.snap.constructor !== String ? ( o.snap.items || ":data(ui-draggable)" ) : o.snap).each(function() { - var $t = $(this), - $o = $t.offset(); - if(this !== i.element[0]) { - i.snapElements.push({ - item: this, - width: $t.outerWidth(), height: $t.outerHeight(), - top: $o.top, left: $o.left - }); - } - }); - - }, - drag: function(event, ui) { - - var ts, bs, ls, rs, l, r, t, b, i, first, - inst = $(this).data("ui-draggable"), - o = inst.options, - d = o.snapTolerance, - x1 = ui.offset.left, x2 = x1 + inst.helperProportions.width, - y1 = ui.offset.top, y2 = y1 + inst.helperProportions.height; - - for (i = inst.snapElements.length - 1; i >= 0; i--){ - - l = inst.snapElements[i].left; - r = l + inst.snapElements[i].width; - t = inst.snapElements[i].top; - b = t + inst.snapElements[i].height; - - if ( x2 < l - d || x1 > r + d || y2 < t - d || y1 > b + d || !$.contains( inst.snapElements[ i ].item.ownerDocument, inst.snapElements[ i ].item ) ) { - if(inst.snapElements[i].snapping) { - (inst.options.snap.release && inst.options.snap.release.call(inst.element, event, $.extend(inst._uiHash(), { snapItem: inst.snapElements[i].item }))); - } - inst.snapElements[i].snapping = false; - continue; - } - - if(o.snapMode !== "inner") { - ts = Math.abs(t - y2) <= d; - bs = Math.abs(b - y1) <= d; - ls = Math.abs(l - x2) <= d; - rs = Math.abs(r - x1) <= d; - if(ts) { - ui.position.top = inst._convertPositionTo("relative", { top: t - inst.helperProportions.height, left: 0 }).top - inst.margins.top; - } - if(bs) { - ui.position.top = inst._convertPositionTo("relative", { top: b, left: 0 }).top - inst.margins.top; - } - if(ls) { - ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l - inst.helperProportions.width }).left - inst.margins.left; - } - if(rs) { - ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r }).left - inst.margins.left; - } - } - - first = (ts || bs || ls || rs); - - if(o.snapMode !== "outer") { - ts = Math.abs(t - y1) <= d; - bs = Math.abs(b - y2) <= d; - ls = Math.abs(l - x1) <= d; - rs = Math.abs(r - x2) <= d; - if(ts) { - ui.position.top = inst._convertPositionTo("relative", { top: t, left: 0 }).top - inst.margins.top; - } - if(bs) { - ui.position.top = inst._convertPositionTo("relative", { top: b - inst.helperProportions.height, left: 0 }).top - inst.margins.top; - } - if(ls) { - ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l }).left - inst.margins.left; - } - if(rs) { - ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r - inst.helperProportions.width }).left - inst.margins.left; - } - } - - if(!inst.snapElements[i].snapping && (ts || bs || ls || rs || first)) { - (inst.options.snap.snap && inst.options.snap.snap.call(inst.element, event, $.extend(inst._uiHash(), { snapItem: inst.snapElements[i].item }))); - } - inst.snapElements[i].snapping = (ts || bs || ls || rs || first); - - } - - } -}); - -$.ui.plugin.add("draggable", "stack", { - start: function() { - var min, - o = this.data("ui-draggable").options, - group = $.makeArray($(o.stack)).sort(function(a,b) { - return (parseInt($(a).css("zIndex"),10) || 0) - (parseInt($(b).css("zIndex"),10) || 0); - }); - - if (!group.length) { return; } - - min = parseInt($(group[0]).css("zIndex"), 10) || 0; - $(group).each(function(i) { - $(this).css("zIndex", min + i); - }); - this.css("zIndex", (min + group.length)); - } -}); - -$.ui.plugin.add("draggable", "zIndex", { - start: function(event, ui) { - var t = $(ui.helper), o = $(this).data("ui-draggable").options; - if(t.css("zIndex")) { - o._zIndex = t.css("zIndex"); - } - t.css("zIndex", o.zIndex); - }, - stop: function(event, ui) { - var o = $(this).data("ui-draggable").options; - if(o._zIndex) { - $(ui.helper).css("zIndex", o._zIndex); - } - } -}); - -})(jQuery); -(function( $, undefined ) { - -function isOverAxis( x, reference, size ) { - return ( x > reference ) && ( x < ( reference + size ) ); -} - -$.widget("ui.droppable", { - version: "1.10.3", - widgetEventPrefix: "drop", - options: { - accept: "*", - activeClass: false, - addClasses: true, - greedy: false, - hoverClass: false, - scope: "default", - tolerance: "intersect", - - // callbacks - activate: null, - deactivate: null, - drop: null, - out: null, - over: null - }, - _create: function() { - - var o = this.options, - accept = o.accept; - - this.isover = false; - this.isout = true; - - this.accept = $.isFunction(accept) ? accept : function(d) { - return d.is(accept); - }; - - //Store the droppable's proportions - this.proportions = { width: this.element[0].offsetWidth, height: this.element[0].offsetHeight }; - - // Add the reference and positions to the manager - $.ui.ddmanager.droppables[o.scope] = $.ui.ddmanager.droppables[o.scope] || []; - $.ui.ddmanager.droppables[o.scope].push(this); - - (o.addClasses && this.element.addClass("ui-droppable")); - - }, - - _destroy: function() { - var i = 0, - drop = $.ui.ddmanager.droppables[this.options.scope]; - - for ( ; i < drop.length; i++ ) { - if ( drop[i] === this ) { - drop.splice(i, 1); - } - } - - this.element.removeClass("ui-droppable ui-droppable-disabled"); - }, - - _setOption: function(key, value) { - - if(key === "accept") { - this.accept = $.isFunction(value) ? value : function(d) { - return d.is(value); - }; - } - $.Widget.prototype._setOption.apply(this, arguments); - }, - - _activate: function(event) { - var draggable = $.ui.ddmanager.current; - if(this.options.activeClass) { - this.element.addClass(this.options.activeClass); - } - if(draggable){ - this._trigger("activate", event, this.ui(draggable)); - } - }, - - _deactivate: function(event) { - var draggable = $.ui.ddmanager.current; - if(this.options.activeClass) { - this.element.removeClass(this.options.activeClass); - } - if(draggable){ - this._trigger("deactivate", event, this.ui(draggable)); - } - }, - - _over: function(event) { - - var draggable = $.ui.ddmanager.current; - - // Bail if draggable and droppable are same element - if (!draggable || (draggable.currentItem || draggable.element)[0] === this.element[0]) { - return; - } - - if (this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) { - if(this.options.hoverClass) { - this.element.addClass(this.options.hoverClass); - } - this._trigger("over", event, this.ui(draggable)); - } - - }, - - _out: function(event) { - - var draggable = $.ui.ddmanager.current; - - // Bail if draggable and droppable are same element - if (!draggable || (draggable.currentItem || draggable.element)[0] === this.element[0]) { - return; - } - - if (this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) { - if(this.options.hoverClass) { - this.element.removeClass(this.options.hoverClass); - } - this._trigger("out", event, this.ui(draggable)); - } - - }, - - _drop: function(event,custom) { - - var draggable = custom || $.ui.ddmanager.current, - childrenIntersection = false; - - // Bail if draggable and droppable are same element - if (!draggable || (draggable.currentItem || draggable.element)[0] === this.element[0]) { - return false; - } - - this.element.find(":data(ui-droppable)").not(".ui-draggable-dragging").each(function() { - var inst = $.data(this, "ui-droppable"); - if( - inst.options.greedy && - !inst.options.disabled && - inst.options.scope === draggable.options.scope && - inst.accept.call(inst.element[0], (draggable.currentItem || draggable.element)) && - $.ui.intersect(draggable, $.extend(inst, { offset: inst.element.offset() }), inst.options.tolerance) - ) { childrenIntersection = true; return false; } - }); - if(childrenIntersection) { - return false; - } - - if(this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) { - if(this.options.activeClass) { - this.element.removeClass(this.options.activeClass); - } - if(this.options.hoverClass) { - this.element.removeClass(this.options.hoverClass); - } - this._trigger("drop", event, this.ui(draggable)); - return this.element; - } - - return false; - - }, - - ui: function(c) { - return { - draggable: (c.currentItem || c.element), - helper: c.helper, - position: c.position, - offset: c.positionAbs - }; - } - -}); - -$.ui.intersect = function(draggable, droppable, toleranceMode) { - - if (!droppable.offset) { - return false; - } - - var draggableLeft, draggableTop, - x1 = (draggable.positionAbs || draggable.position.absolute).left, x2 = x1 + draggable.helperProportions.width, - y1 = (draggable.positionAbs || draggable.position.absolute).top, y2 = y1 + draggable.helperProportions.height, - l = droppable.offset.left, r = l + droppable.proportions.width, - t = droppable.offset.top, b = t + droppable.proportions.height; - - switch (toleranceMode) { - case "fit": - return (l <= x1 && x2 <= r && t <= y1 && y2 <= b); - case "intersect": - return (l < x1 + (draggable.helperProportions.width / 2) && // Right Half - x2 - (draggable.helperProportions.width / 2) < r && // Left Half - t < y1 + (draggable.helperProportions.height / 2) && // Bottom Half - y2 - (draggable.helperProportions.height / 2) < b ); // Top Half - case "pointer": - draggableLeft = ((draggable.positionAbs || draggable.position.absolute).left + (draggable.clickOffset || draggable.offset.click).left); - draggableTop = ((draggable.positionAbs || draggable.position.absolute).top + (draggable.clickOffset || draggable.offset.click).top); - return isOverAxis( draggableTop, t, droppable.proportions.height ) && isOverAxis( draggableLeft, l, droppable.proportions.width ); - case "touch": - return ( - (y1 >= t && y1 <= b) || // Top edge touching - (y2 >= t && y2 <= b) || // Bottom edge touching - (y1 < t && y2 > b) // Surrounded vertically - ) && ( - (x1 >= l && x1 <= r) || // Left edge touching - (x2 >= l && x2 <= r) || // Right edge touching - (x1 < l && x2 > r) // Surrounded horizontally - ); - default: - return false; - } - -}; - -/* - This manager tracks offsets of draggables and droppables -*/ -$.ui.ddmanager = { - current: null, - droppables: { "default": [] }, - prepareOffsets: function(t, event) { - - var i, j, - m = $.ui.ddmanager.droppables[t.options.scope] || [], - type = event ? event.type : null, // workaround for #2317 - list = (t.currentItem || t.element).find(":data(ui-droppable)").addBack(); - - droppablesLoop: for (i = 0; i < m.length; i++) { - - //No disabled and non-accepted - if(m[i].options.disabled || (t && !m[i].accept.call(m[i].element[0],(t.currentItem || t.element)))) { - continue; - } - - // Filter out elements in the current dragged item - for (j=0; j < list.length; j++) { - if(list[j] === m[i].element[0]) { - m[i].proportions.height = 0; - continue droppablesLoop; - } - } - - m[i].visible = m[i].element.css("display") !== "none"; - if(!m[i].visible) { - continue; - } - - //Activate the droppable if used directly from draggables - if(type === "mousedown") { - m[i]._activate.call(m[i], event); - } - - m[i].offset = m[i].element.offset(); - m[i].proportions = { width: m[i].element[0].offsetWidth, height: m[i].element[0].offsetHeight }; - - } - - }, - drop: function(draggable, event) { - - var dropped = false; - // Create a copy of the droppables in case the list changes during the drop (#9116) - $.each(($.ui.ddmanager.droppables[draggable.options.scope] || []).slice(), function() { - - if(!this.options) { - return; - } - if (!this.options.disabled && this.visible && $.ui.intersect(draggable, this, this.options.tolerance)) { - dropped = this._drop.call(this, event) || dropped; - } - - if (!this.options.disabled && this.visible && this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) { - this.isout = true; - this.isover = false; - this._deactivate.call(this, event); - } - - }); - return dropped; - - }, - dragStart: function( draggable, event ) { - //Listen for scrolling so that if the dragging causes scrolling the position of the droppables can be recalculated (see #5003) - draggable.element.parentsUntil( "body" ).bind( "scroll.droppable", function() { - if( !draggable.options.refreshPositions ) { - $.ui.ddmanager.prepareOffsets( draggable, event ); - } - }); - }, - drag: function(draggable, event) { - - //If you have a highly dynamic page, you might try this option. It renders positions every time you move the mouse. - if(draggable.options.refreshPositions) { - $.ui.ddmanager.prepareOffsets(draggable, event); - } - - //Run through all droppables and check their positions based on specific tolerance options - $.each($.ui.ddmanager.droppables[draggable.options.scope] || [], function() { - - if(this.options.disabled || this.greedyChild || !this.visible) { - return; - } - - var parentInstance, scope, parent, - intersects = $.ui.intersect(draggable, this, this.options.tolerance), - c = !intersects && this.isover ? "isout" : (intersects && !this.isover ? "isover" : null); - if(!c) { - return; - } - - if (this.options.greedy) { - // find droppable parents with same scope - scope = this.options.scope; - parent = this.element.parents(":data(ui-droppable)").filter(function () { - return $.data(this, "ui-droppable").options.scope === scope; - }); - - if (parent.length) { - parentInstance = $.data(parent[0], "ui-droppable"); - parentInstance.greedyChild = (c === "isover"); - } - } - - // we just moved into a greedy child - if (parentInstance && c === "isover") { - parentInstance.isover = false; - parentInstance.isout = true; - parentInstance._out.call(parentInstance, event); - } - - this[c] = true; - this[c === "isout" ? "isover" : "isout"] = false; - this[c === "isover" ? "_over" : "_out"].call(this, event); - - // we just moved out of a greedy child - if (parentInstance && c === "isout") { - parentInstance.isout = false; - parentInstance.isover = true; - parentInstance._over.call(parentInstance, event); - } - }); - - }, - dragStop: function( draggable, event ) { - draggable.element.parentsUntil( "body" ).unbind( "scroll.droppable" ); - //Call prepareOffsets one final time since IE does not fire return scroll events when overflow was caused by drag (see #5003) - if( !draggable.options.refreshPositions ) { - $.ui.ddmanager.prepareOffsets( draggable, event ); - } - } -}; - -})(jQuery); -(function( $, undefined ) { - -function num(v) { - return parseInt(v, 10) || 0; -} - -function isNumber(value) { - return !isNaN(parseInt(value, 10)); -} - -$.widget("ui.resizable", $.ui.mouse, { - version: "1.10.3", - widgetEventPrefix: "resize", - options: { - alsoResize: false, - animate: false, - animateDuration: "slow", - animateEasing: "swing", - aspectRatio: false, - autoHide: false, - containment: false, - ghost: false, - grid: false, - handles: "e,s,se", - helper: false, - maxHeight: null, - maxWidth: null, - minHeight: 10, - minWidth: 10, - // See #7960 - zIndex: 90, - - // callbacks - resize: null, - start: null, - stop: null - }, - _create: function() { - - var n, i, handle, axis, hname, - that = this, - o = this.options; - this.element.addClass("ui-resizable"); - - $.extend(this, { - _aspectRatio: !!(o.aspectRatio), - aspectRatio: o.aspectRatio, - originalElement: this.element, - _proportionallyResizeElements: [], - _helper: o.helper || o.ghost || o.animate ? o.helper || "ui-resizable-helper" : null - }); - - //Wrap the element if it cannot hold child nodes - if(this.element[0].nodeName.match(/canvas|textarea|input|select|button|img/i)) { - - //Create a wrapper element and set the wrapper to the new current internal element - this.element.wrap( - $("
    ").css({ - position: this.element.css("position"), - width: this.element.outerWidth(), - height: this.element.outerHeight(), - top: this.element.css("top"), - left: this.element.css("left") - }) - ); - - //Overwrite the original this.element - this.element = this.element.parent().data( - "ui-resizable", this.element.data("ui-resizable") - ); - - this.elementIsWrapper = true; - - //Move margins to the wrapper - this.element.css({ marginLeft: this.originalElement.css("marginLeft"), marginTop: this.originalElement.css("marginTop"), marginRight: this.originalElement.css("marginRight"), marginBottom: this.originalElement.css("marginBottom") }); - this.originalElement.css({ marginLeft: 0, marginTop: 0, marginRight: 0, marginBottom: 0}); - - //Prevent Safari textarea resize - this.originalResizeStyle = this.originalElement.css("resize"); - this.originalElement.css("resize", "none"); - - //Push the actual element to our proportionallyResize internal array - this._proportionallyResizeElements.push(this.originalElement.css({ position: "static", zoom: 1, display: "block" })); - - // avoid IE jump (hard set the margin) - this.originalElement.css({ margin: this.originalElement.css("margin") }); - - // fix handlers offset - this._proportionallyResize(); - - } - - this.handles = o.handles || (!$(".ui-resizable-handle", this.element).length ? "e,s,se" : { n: ".ui-resizable-n", e: ".ui-resizable-e", s: ".ui-resizable-s", w: ".ui-resizable-w", se: ".ui-resizable-se", sw: ".ui-resizable-sw", ne: ".ui-resizable-ne", nw: ".ui-resizable-nw" }); - if(this.handles.constructor === String) { - - if ( this.handles === "all") { - this.handles = "n,e,s,w,se,sw,ne,nw"; - } - - n = this.handles.split(","); - this.handles = {}; - - for(i = 0; i < n.length; i++) { - - handle = $.trim(n[i]); - hname = "ui-resizable-"+handle; - axis = $("
    "); - - // Apply zIndex to all handles - see #7960 - axis.css({ zIndex: o.zIndex }); - - //TODO : What's going on here? - if ("se" === handle) { - axis.addClass("ui-icon ui-icon-gripsmall-diagonal-se"); - } - - //Insert into internal handles object and append to element - this.handles[handle] = ".ui-resizable-"+handle; - this.element.append(axis); - } - - } - - this._renderAxis = function(target) { - - var i, axis, padPos, padWrapper; - - target = target || this.element; - - for(i in this.handles) { - - if(this.handles[i].constructor === String) { - this.handles[i] = $(this.handles[i], this.element).show(); - } - - //Apply pad to wrapper element, needed to fix axis position (textarea, inputs, scrolls) - if (this.elementIsWrapper && this.originalElement[0].nodeName.match(/textarea|input|select|button/i)) { - - axis = $(this.handles[i], this.element); - - //Checking the correct pad and border - padWrapper = /sw|ne|nw|se|n|s/.test(i) ? axis.outerHeight() : axis.outerWidth(); - - //The padding type i have to apply... - padPos = [ "padding", - /ne|nw|n/.test(i) ? "Top" : - /se|sw|s/.test(i) ? "Bottom" : - /^e$/.test(i) ? "Right" : "Left" ].join(""); - - target.css(padPos, padWrapper); - - this._proportionallyResize(); - - } - - //TODO: What's that good for? There's not anything to be executed left - if(!$(this.handles[i]).length) { - continue; - } - } - }; - - //TODO: make renderAxis a prototype function - this._renderAxis(this.element); - - this._handles = $(".ui-resizable-handle", this.element) - .disableSelection(); - - //Matching axis name - this._handles.mouseover(function() { - if (!that.resizing) { - if (this.className) { - axis = this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i); - } - //Axis, default = se - that.axis = axis && axis[1] ? axis[1] : "se"; - } - }); - - //If we want to auto hide the elements - if (o.autoHide) { - this._handles.hide(); - $(this.element) - .addClass("ui-resizable-autohide") - .mouseenter(function() { - if (o.disabled) { - return; - } - $(this).removeClass("ui-resizable-autohide"); - that._handles.show(); - }) - .mouseleave(function(){ - if (o.disabled) { - return; - } - if (!that.resizing) { - $(this).addClass("ui-resizable-autohide"); - that._handles.hide(); - } - }); - } - - //Initialize the mouse interaction - this._mouseInit(); - - }, - - _destroy: function() { - - this._mouseDestroy(); - - var wrapper, - _destroy = function(exp) { - $(exp).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing") - .removeData("resizable").removeData("ui-resizable").unbind(".resizable").find(".ui-resizable-handle").remove(); - }; - - //TODO: Unwrap at same DOM position - if (this.elementIsWrapper) { - _destroy(this.element); - wrapper = this.element; - this.originalElement.css({ - position: wrapper.css("position"), - width: wrapper.outerWidth(), - height: wrapper.outerHeight(), - top: wrapper.css("top"), - left: wrapper.css("left") - }).insertAfter( wrapper ); - wrapper.remove(); - } - - this.originalElement.css("resize", this.originalResizeStyle); - _destroy(this.originalElement); - - return this; - }, - - _mouseCapture: function(event) { - var i, handle, - capture = false; - - for (i in this.handles) { - handle = $(this.handles[i])[0]; - if (handle === event.target || $.contains(handle, event.target)) { - capture = true; - } - } - - return !this.options.disabled && capture; - }, - - _mouseStart: function(event) { - - var curleft, curtop, cursor, - o = this.options, - iniPos = this.element.position(), - el = this.element; - - this.resizing = true; - - // bugfix for http://dev.jquery.com/ticket/1749 - if ( (/absolute/).test( el.css("position") ) ) { - el.css({ position: "absolute", top: el.css("top"), left: el.css("left") }); - } else if (el.is(".ui-draggable")) { - el.css({ position: "absolute", top: iniPos.top, left: iniPos.left }); - } - - this._renderProxy(); - - curleft = num(this.helper.css("left")); - curtop = num(this.helper.css("top")); - - if (o.containment) { - curleft += $(o.containment).scrollLeft() || 0; - curtop += $(o.containment).scrollTop() || 0; - } - - //Store needed variables - this.offset = this.helper.offset(); - this.position = { left: curleft, top: curtop }; - this.size = this._helper ? { width: el.outerWidth(), height: el.outerHeight() } : { width: el.width(), height: el.height() }; - this.originalSize = this._helper ? { width: el.outerWidth(), height: el.outerHeight() } : { width: el.width(), height: el.height() }; - this.originalPosition = { left: curleft, top: curtop }; - this.sizeDiff = { width: el.outerWidth() - el.width(), height: el.outerHeight() - el.height() }; - this.originalMousePosition = { left: event.pageX, top: event.pageY }; - - //Aspect Ratio - this.aspectRatio = (typeof o.aspectRatio === "number") ? o.aspectRatio : ((this.originalSize.width / this.originalSize.height) || 1); - - cursor = $(".ui-resizable-" + this.axis).css("cursor"); - $("body").css("cursor", cursor === "auto" ? this.axis + "-resize" : cursor); - - el.addClass("ui-resizable-resizing"); - this._propagate("start", event); - return true; - }, - - _mouseDrag: function(event) { - - //Increase performance, avoid regex - var data, - el = this.helper, props = {}, - smp = this.originalMousePosition, - a = this.axis, - prevTop = this.position.top, - prevLeft = this.position.left, - prevWidth = this.size.width, - prevHeight = this.size.height, - dx = (event.pageX-smp.left)||0, - dy = (event.pageY-smp.top)||0, - trigger = this._change[a]; - - if (!trigger) { - return false; - } - - // Calculate the attrs that will be change - data = trigger.apply(this, [event, dx, dy]); - - // Put this in the mouseDrag handler since the user can start pressing shift while resizing - this._updateVirtualBoundaries(event.shiftKey); - if (this._aspectRatio || event.shiftKey) { - data = this._updateRatio(data, event); - } - - data = this._respectSize(data, event); - - this._updateCache(data); - - // plugins callbacks need to be called first - this._propagate("resize", event); - - if (this.position.top !== prevTop) { - props.top = this.position.top + "px"; - } - if (this.position.left !== prevLeft) { - props.left = this.position.left + "px"; - } - if (this.size.width !== prevWidth) { - props.width = this.size.width + "px"; - } - if (this.size.height !== prevHeight) { - props.height = this.size.height + "px"; - } - el.css(props); - - if (!this._helper && this._proportionallyResizeElements.length) { - this._proportionallyResize(); - } - - // Call the user callback if the element was resized - if ( ! $.isEmptyObject(props) ) { - this._trigger("resize", event, this.ui()); - } - - return false; - }, - - _mouseStop: function(event) { - - this.resizing = false; - var pr, ista, soffseth, soffsetw, s, left, top, - o = this.options, that = this; - - if(this._helper) { - - pr = this._proportionallyResizeElements; - ista = pr.length && (/textarea/i).test(pr[0].nodeName); - soffseth = ista && $.ui.hasScroll(pr[0], "left") /* TODO - jump height */ ? 0 : that.sizeDiff.height; - soffsetw = ista ? 0 : that.sizeDiff.width; - - s = { width: (that.helper.width() - soffsetw), height: (that.helper.height() - soffseth) }; - left = (parseInt(that.element.css("left"), 10) + (that.position.left - that.originalPosition.left)) || null; - top = (parseInt(that.element.css("top"), 10) + (that.position.top - that.originalPosition.top)) || null; - - if (!o.animate) { - this.element.css($.extend(s, { top: top, left: left })); - } - - that.helper.height(that.size.height); - that.helper.width(that.size.width); - - if (this._helper && !o.animate) { - this._proportionallyResize(); - } - } - - $("body").css("cursor", "auto"); - - this.element.removeClass("ui-resizable-resizing"); - - this._propagate("stop", event); - - if (this._helper) { - this.helper.remove(); - } - - return false; - - }, - - _updateVirtualBoundaries: function(forceAspectRatio) { - var pMinWidth, pMaxWidth, pMinHeight, pMaxHeight, b, - o = this.options; - - b = { - minWidth: isNumber(o.minWidth) ? o.minWidth : 0, - maxWidth: isNumber(o.maxWidth) ? o.maxWidth : Infinity, - minHeight: isNumber(o.minHeight) ? o.minHeight : 0, - maxHeight: isNumber(o.maxHeight) ? o.maxHeight : Infinity - }; - - if(this._aspectRatio || forceAspectRatio) { - // We want to create an enclosing box whose aspect ration is the requested one - // First, compute the "projected" size for each dimension based on the aspect ratio and other dimension - pMinWidth = b.minHeight * this.aspectRatio; - pMinHeight = b.minWidth / this.aspectRatio; - pMaxWidth = b.maxHeight * this.aspectRatio; - pMaxHeight = b.maxWidth / this.aspectRatio; - - if(pMinWidth > b.minWidth) { - b.minWidth = pMinWidth; - } - if(pMinHeight > b.minHeight) { - b.minHeight = pMinHeight; - } - if(pMaxWidth < b.maxWidth) { - b.maxWidth = pMaxWidth; - } - if(pMaxHeight < b.maxHeight) { - b.maxHeight = pMaxHeight; - } - } - this._vBoundaries = b; - }, - - _updateCache: function(data) { - this.offset = this.helper.offset(); - if (isNumber(data.left)) { - this.position.left = data.left; - } - if (isNumber(data.top)) { - this.position.top = data.top; - } - if (isNumber(data.height)) { - this.size.height = data.height; - } - if (isNumber(data.width)) { - this.size.width = data.width; - } - }, - - _updateRatio: function( data ) { - - var cpos = this.position, - csize = this.size, - a = this.axis; - - if (isNumber(data.height)) { - data.width = (data.height * this.aspectRatio); - } else if (isNumber(data.width)) { - data.height = (data.width / this.aspectRatio); - } - - if (a === "sw") { - data.left = cpos.left + (csize.width - data.width); - data.top = null; - } - if (a === "nw") { - data.top = cpos.top + (csize.height - data.height); - data.left = cpos.left + (csize.width - data.width); - } - - return data; - }, - - _respectSize: function( data ) { - - var o = this._vBoundaries, - a = this.axis, - ismaxw = isNumber(data.width) && o.maxWidth && (o.maxWidth < data.width), ismaxh = isNumber(data.height) && o.maxHeight && (o.maxHeight < data.height), - isminw = isNumber(data.width) && o.minWidth && (o.minWidth > data.width), isminh = isNumber(data.height) && o.minHeight && (o.minHeight > data.height), - dw = this.originalPosition.left + this.originalSize.width, - dh = this.position.top + this.size.height, - cw = /sw|nw|w/.test(a), ch = /nw|ne|n/.test(a); - if (isminw) { - data.width = o.minWidth; - } - if (isminh) { - data.height = o.minHeight; - } - if (ismaxw) { - data.width = o.maxWidth; - } - if (ismaxh) { - data.height = o.maxHeight; - } - - if (isminw && cw) { - data.left = dw - o.minWidth; - } - if (ismaxw && cw) { - data.left = dw - o.maxWidth; - } - if (isminh && ch) { - data.top = dh - o.minHeight; - } - if (ismaxh && ch) { - data.top = dh - o.maxHeight; - } - - // fixing jump error on top/left - bug #2330 - if (!data.width && !data.height && !data.left && data.top) { - data.top = null; - } else if (!data.width && !data.height && !data.top && data.left) { - data.left = null; - } - - return data; - }, - - _proportionallyResize: function() { - - if (!this._proportionallyResizeElements.length) { - return; - } - - var i, j, borders, paddings, prel, - element = this.helper || this.element; - - for ( i=0; i < this._proportionallyResizeElements.length; i++) { - - prel = this._proportionallyResizeElements[i]; - - if (!this.borderDif) { - this.borderDif = []; - borders = [prel.css("borderTopWidth"), prel.css("borderRightWidth"), prel.css("borderBottomWidth"), prel.css("borderLeftWidth")]; - paddings = [prel.css("paddingTop"), prel.css("paddingRight"), prel.css("paddingBottom"), prel.css("paddingLeft")]; - - for ( j = 0; j < borders.length; j++ ) { - this.borderDif[ j ] = ( parseInt( borders[ j ], 10 ) || 0 ) + ( parseInt( paddings[ j ], 10 ) || 0 ); - } - } - - prel.css({ - height: (element.height() - this.borderDif[0] - this.borderDif[2]) || 0, - width: (element.width() - this.borderDif[1] - this.borderDif[3]) || 0 - }); - - } - - }, - - _renderProxy: function() { - - var el = this.element, o = this.options; - this.elementOffset = el.offset(); - - if(this._helper) { - - this.helper = this.helper || $("
    "); - - this.helper.addClass(this._helper).css({ - width: this.element.outerWidth() - 1, - height: this.element.outerHeight() - 1, - position: "absolute", - left: this.elementOffset.left +"px", - top: this.elementOffset.top +"px", - zIndex: ++o.zIndex //TODO: Don't modify option - }); - - this.helper - .appendTo("body") - .disableSelection(); - - } else { - this.helper = this.element; - } - - }, - - _change: { - e: function(event, dx) { - return { width: this.originalSize.width + dx }; - }, - w: function(event, dx) { - var cs = this.originalSize, sp = this.originalPosition; - return { left: sp.left + dx, width: cs.width - dx }; - }, - n: function(event, dx, dy) { - var cs = this.originalSize, sp = this.originalPosition; - return { top: sp.top + dy, height: cs.height - dy }; - }, - s: function(event, dx, dy) { - return { height: this.originalSize.height + dy }; - }, - se: function(event, dx, dy) { - return $.extend(this._change.s.apply(this, arguments), this._change.e.apply(this, [event, dx, dy])); - }, - sw: function(event, dx, dy) { - return $.extend(this._change.s.apply(this, arguments), this._change.w.apply(this, [event, dx, dy])); - }, - ne: function(event, dx, dy) { - return $.extend(this._change.n.apply(this, arguments), this._change.e.apply(this, [event, dx, dy])); - }, - nw: function(event, dx, dy) { - return $.extend(this._change.n.apply(this, arguments), this._change.w.apply(this, [event, dx, dy])); - } - }, - - _propagate: function(n, event) { - $.ui.plugin.call(this, n, [event, this.ui()]); - (n !== "resize" && this._trigger(n, event, this.ui())); - }, - - plugins: {}, - - ui: function() { - return { - originalElement: this.originalElement, - element: this.element, - helper: this.helper, - position: this.position, - size: this.size, - originalSize: this.originalSize, - originalPosition: this.originalPosition - }; - } - -}); - -/* - * Resizable Extensions - */ - -$.ui.plugin.add("resizable", "animate", { - - stop: function( event ) { - var that = $(this).data("ui-resizable"), - o = that.options, - pr = that._proportionallyResizeElements, - ista = pr.length && (/textarea/i).test(pr[0].nodeName), - soffseth = ista && $.ui.hasScroll(pr[0], "left") /* TODO - jump height */ ? 0 : that.sizeDiff.height, - soffsetw = ista ? 0 : that.sizeDiff.width, - style = { width: (that.size.width - soffsetw), height: (that.size.height - soffseth) }, - left = (parseInt(that.element.css("left"), 10) + (that.position.left - that.originalPosition.left)) || null, - top = (parseInt(that.element.css("top"), 10) + (that.position.top - that.originalPosition.top)) || null; - - that.element.animate( - $.extend(style, top && left ? { top: top, left: left } : {}), { - duration: o.animateDuration, - easing: o.animateEasing, - step: function() { - - var data = { - width: parseInt(that.element.css("width"), 10), - height: parseInt(that.element.css("height"), 10), - top: parseInt(that.element.css("top"), 10), - left: parseInt(that.element.css("left"), 10) - }; - - if (pr && pr.length) { - $(pr[0]).css({ width: data.width, height: data.height }); - } - - // propagating resize, and updating values for each animation step - that._updateCache(data); - that._propagate("resize", event); - - } - } - ); - } - -}); - -$.ui.plugin.add("resizable", "containment", { - - start: function() { - var element, p, co, ch, cw, width, height, - that = $(this).data("ui-resizable"), - o = that.options, - el = that.element, - oc = o.containment, - ce = (oc instanceof $) ? oc.get(0) : (/parent/.test(oc)) ? el.parent().get(0) : oc; - - if (!ce) { - return; - } - - that.containerElement = $(ce); - - if (/document/.test(oc) || oc === document) { - that.containerOffset = { left: 0, top: 0 }; - that.containerPosition = { left: 0, top: 0 }; - - that.parentData = { - element: $(document), left: 0, top: 0, - width: $(document).width(), height: $(document).height() || document.body.parentNode.scrollHeight - }; - } - - // i'm a node, so compute top, left, right, bottom - else { - element = $(ce); - p = []; - $([ "Top", "Right", "Left", "Bottom" ]).each(function(i, name) { p[i] = num(element.css("padding" + name)); }); - - that.containerOffset = element.offset(); - that.containerPosition = element.position(); - that.containerSize = { height: (element.innerHeight() - p[3]), width: (element.innerWidth() - p[1]) }; - - co = that.containerOffset; - ch = that.containerSize.height; - cw = that.containerSize.width; - width = ($.ui.hasScroll(ce, "left") ? ce.scrollWidth : cw ); - height = ($.ui.hasScroll(ce) ? ce.scrollHeight : ch); - - that.parentData = { - element: ce, left: co.left, top: co.top, width: width, height: height - }; - } - }, - - resize: function( event ) { - var woset, hoset, isParent, isOffsetRelative, - that = $(this).data("ui-resizable"), - o = that.options, - co = that.containerOffset, cp = that.position, - pRatio = that._aspectRatio || event.shiftKey, - cop = { top:0, left:0 }, ce = that.containerElement; - - if (ce[0] !== document && (/static/).test(ce.css("position"))) { - cop = co; - } - - if (cp.left < (that._helper ? co.left : 0)) { - that.size.width = that.size.width + (that._helper ? (that.position.left - co.left) : (that.position.left - cop.left)); - if (pRatio) { - that.size.height = that.size.width / that.aspectRatio; - } - that.position.left = o.helper ? co.left : 0; - } - - if (cp.top < (that._helper ? co.top : 0)) { - that.size.height = that.size.height + (that._helper ? (that.position.top - co.top) : that.position.top); - if (pRatio) { - that.size.width = that.size.height * that.aspectRatio; - } - that.position.top = that._helper ? co.top : 0; - } - - that.offset.left = that.parentData.left+that.position.left; - that.offset.top = that.parentData.top+that.position.top; - - woset = Math.abs( (that._helper ? that.offset.left - cop.left : (that.offset.left - cop.left)) + that.sizeDiff.width ); - hoset = Math.abs( (that._helper ? that.offset.top - cop.top : (that.offset.top - co.top)) + that.sizeDiff.height ); - - isParent = that.containerElement.get(0) === that.element.parent().get(0); - isOffsetRelative = /relative|absolute/.test(that.containerElement.css("position")); - - if(isParent && isOffsetRelative) { - woset -= that.parentData.left; - } - - if (woset + that.size.width >= that.parentData.width) { - that.size.width = that.parentData.width - woset; - if (pRatio) { - that.size.height = that.size.width / that.aspectRatio; - } - } - - if (hoset + that.size.height >= that.parentData.height) { - that.size.height = that.parentData.height - hoset; - if (pRatio) { - that.size.width = that.size.height * that.aspectRatio; - } - } - }, - - stop: function(){ - var that = $(this).data("ui-resizable"), - o = that.options, - co = that.containerOffset, - cop = that.containerPosition, - ce = that.containerElement, - helper = $(that.helper), - ho = helper.offset(), - w = helper.outerWidth() - that.sizeDiff.width, - h = helper.outerHeight() - that.sizeDiff.height; - - if (that._helper && !o.animate && (/relative/).test(ce.css("position"))) { - $(this).css({ left: ho.left - cop.left - co.left, width: w, height: h }); - } - - if (that._helper && !o.animate && (/static/).test(ce.css("position"))) { - $(this).css({ left: ho.left - cop.left - co.left, width: w, height: h }); - } - - } -}); - -$.ui.plugin.add("resizable", "alsoResize", { - - start: function () { - var that = $(this).data("ui-resizable"), - o = that.options, - _store = function (exp) { - $(exp).each(function() { - var el = $(this); - el.data("ui-resizable-alsoresize", { - width: parseInt(el.width(), 10), height: parseInt(el.height(), 10), - left: parseInt(el.css("left"), 10), top: parseInt(el.css("top"), 10) - }); - }); - }; - - if (typeof(o.alsoResize) === "object" && !o.alsoResize.parentNode) { - if (o.alsoResize.length) { o.alsoResize = o.alsoResize[0]; _store(o.alsoResize); } - else { $.each(o.alsoResize, function (exp) { _store(exp); }); } - }else{ - _store(o.alsoResize); - } - }, - - resize: function (event, ui) { - var that = $(this).data("ui-resizable"), - o = that.options, - os = that.originalSize, - op = that.originalPosition, - delta = { - height: (that.size.height - os.height) || 0, width: (that.size.width - os.width) || 0, - top: (that.position.top - op.top) || 0, left: (that.position.left - op.left) || 0 - }, - - _alsoResize = function (exp, c) { - $(exp).each(function() { - var el = $(this), start = $(this).data("ui-resizable-alsoresize"), style = {}, - css = c && c.length ? c : el.parents(ui.originalElement[0]).length ? ["width", "height"] : ["width", "height", "top", "left"]; - - $.each(css, function (i, prop) { - var sum = (start[prop]||0) + (delta[prop]||0); - if (sum && sum >= 0) { - style[prop] = sum || null; - } - }); - - el.css(style); - }); - }; - - if (typeof(o.alsoResize) === "object" && !o.alsoResize.nodeType) { - $.each(o.alsoResize, function (exp, c) { _alsoResize(exp, c); }); - }else{ - _alsoResize(o.alsoResize); - } - }, - - stop: function () { - $(this).removeData("resizable-alsoresize"); - } -}); - -$.ui.plugin.add("resizable", "ghost", { - - start: function() { - - var that = $(this).data("ui-resizable"), o = that.options, cs = that.size; - - that.ghost = that.originalElement.clone(); - that.ghost - .css({ opacity: 0.25, display: "block", position: "relative", height: cs.height, width: cs.width, margin: 0, left: 0, top: 0 }) - .addClass("ui-resizable-ghost") - .addClass(typeof o.ghost === "string" ? o.ghost : ""); - - that.ghost.appendTo(that.helper); - - }, - - resize: function(){ - var that = $(this).data("ui-resizable"); - if (that.ghost) { - that.ghost.css({ position: "relative", height: that.size.height, width: that.size.width }); - } - }, - - stop: function() { - var that = $(this).data("ui-resizable"); - if (that.ghost && that.helper) { - that.helper.get(0).removeChild(that.ghost.get(0)); - } - } - -}); - -$.ui.plugin.add("resizable", "grid", { - - resize: function() { - var that = $(this).data("ui-resizable"), - o = that.options, - cs = that.size, - os = that.originalSize, - op = that.originalPosition, - a = that.axis, - grid = typeof o.grid === "number" ? [o.grid, o.grid] : o.grid, - gridX = (grid[0]||1), - gridY = (grid[1]||1), - ox = Math.round((cs.width - os.width) / gridX) * gridX, - oy = Math.round((cs.height - os.height) / gridY) * gridY, - newWidth = os.width + ox, - newHeight = os.height + oy, - isMaxWidth = o.maxWidth && (o.maxWidth < newWidth), - isMaxHeight = o.maxHeight && (o.maxHeight < newHeight), - isMinWidth = o.minWidth && (o.minWidth > newWidth), - isMinHeight = o.minHeight && (o.minHeight > newHeight); - - o.grid = grid; - - if (isMinWidth) { - newWidth = newWidth + gridX; - } - if (isMinHeight) { - newHeight = newHeight + gridY; - } - if (isMaxWidth) { - newWidth = newWidth - gridX; - } - if (isMaxHeight) { - newHeight = newHeight - gridY; - } - - if (/^(se|s|e)$/.test(a)) { - that.size.width = newWidth; - that.size.height = newHeight; - } else if (/^(ne)$/.test(a)) { - that.size.width = newWidth; - that.size.height = newHeight; - that.position.top = op.top - oy; - } else if (/^(sw)$/.test(a)) { - that.size.width = newWidth; - that.size.height = newHeight; - that.position.left = op.left - ox; - } else { - that.size.width = newWidth; - that.size.height = newHeight; - that.position.top = op.top - oy; - that.position.left = op.left - ox; - } - } - -}); - -})(jQuery); -(function( $, undefined ) { - -$.widget("ui.selectable", $.ui.mouse, { - version: "1.10.3", - options: { - appendTo: "body", - autoRefresh: true, - distance: 0, - filter: "*", - tolerance: "touch", - - // callbacks - selected: null, - selecting: null, - start: null, - stop: null, - unselected: null, - unselecting: null - }, - _create: function() { - var selectees, - that = this; - - this.element.addClass("ui-selectable"); - - this.dragged = false; - - // cache selectee children based on filter - this.refresh = function() { - selectees = $(that.options.filter, that.element[0]); - selectees.addClass("ui-selectee"); - selectees.each(function() { - var $this = $(this), - pos = $this.offset(); - $.data(this, "selectable-item", { - element: this, - $element: $this, - left: pos.left, - top: pos.top, - right: pos.left + $this.outerWidth(), - bottom: pos.top + $this.outerHeight(), - startselected: false, - selected: $this.hasClass("ui-selected"), - selecting: $this.hasClass("ui-selecting"), - unselecting: $this.hasClass("ui-unselecting") - }); - }); - }; - this.refresh(); - - this.selectees = selectees.addClass("ui-selectee"); - - this._mouseInit(); - - this.helper = $("
    "); - }, - - _destroy: function() { - this.selectees - .removeClass("ui-selectee") - .removeData("selectable-item"); - this.element - .removeClass("ui-selectable ui-selectable-disabled"); - this._mouseDestroy(); - }, - - _mouseStart: function(event) { - var that = this, - options = this.options; - - this.opos = [event.pageX, event.pageY]; - - if (this.options.disabled) { - return; - } - - this.selectees = $(options.filter, this.element[0]); - - this._trigger("start", event); - - $(options.appendTo).append(this.helper); - // position helper (lasso) - this.helper.css({ - "left": event.pageX, - "top": event.pageY, - "width": 0, - "height": 0 - }); - - if (options.autoRefresh) { - this.refresh(); - } - - this.selectees.filter(".ui-selected").each(function() { - var selectee = $.data(this, "selectable-item"); - selectee.startselected = true; - if (!event.metaKey && !event.ctrlKey) { - selectee.$element.removeClass("ui-selected"); - selectee.selected = false; - selectee.$element.addClass("ui-unselecting"); - selectee.unselecting = true; - // selectable UNSELECTING callback - that._trigger("unselecting", event, { - unselecting: selectee.element - }); - } - }); - - $(event.target).parents().addBack().each(function() { - var doSelect, - selectee = $.data(this, "selectable-item"); - if (selectee) { - doSelect = (!event.metaKey && !event.ctrlKey) || !selectee.$element.hasClass("ui-selected"); - selectee.$element - .removeClass(doSelect ? "ui-unselecting" : "ui-selected") - .addClass(doSelect ? "ui-selecting" : "ui-unselecting"); - selectee.unselecting = !doSelect; - selectee.selecting = doSelect; - selectee.selected = doSelect; - // selectable (UN)SELECTING callback - if (doSelect) { - that._trigger("selecting", event, { - selecting: selectee.element - }); - } else { - that._trigger("unselecting", event, { - unselecting: selectee.element - }); - } - return false; - } - }); - - }, - - _mouseDrag: function(event) { - - this.dragged = true; - - if (this.options.disabled) { - return; - } - - var tmp, - that = this, - options = this.options, - x1 = this.opos[0], - y1 = this.opos[1], - x2 = event.pageX, - y2 = event.pageY; - - if (x1 > x2) { tmp = x2; x2 = x1; x1 = tmp; } - if (y1 > y2) { tmp = y2; y2 = y1; y1 = tmp; } - this.helper.css({left: x1, top: y1, width: x2-x1, height: y2-y1}); - - this.selectees.each(function() { - var selectee = $.data(this, "selectable-item"), - hit = false; - - //prevent helper from being selected if appendTo: selectable - if (!selectee || selectee.element === that.element[0]) { - return; - } - - if (options.tolerance === "touch") { - hit = ( !(selectee.left > x2 || selectee.right < x1 || selectee.top > y2 || selectee.bottom < y1) ); - } else if (options.tolerance === "fit") { - hit = (selectee.left > x1 && selectee.right < x2 && selectee.top > y1 && selectee.bottom < y2); - } - - if (hit) { - // SELECT - if (selectee.selected) { - selectee.$element.removeClass("ui-selected"); - selectee.selected = false; - } - if (selectee.unselecting) { - selectee.$element.removeClass("ui-unselecting"); - selectee.unselecting = false; - } - if (!selectee.selecting) { - selectee.$element.addClass("ui-selecting"); - selectee.selecting = true; - // selectable SELECTING callback - that._trigger("selecting", event, { - selecting: selectee.element - }); - } - } else { - // UNSELECT - if (selectee.selecting) { - if ((event.metaKey || event.ctrlKey) && selectee.startselected) { - selectee.$element.removeClass("ui-selecting"); - selectee.selecting = false; - selectee.$element.addClass("ui-selected"); - selectee.selected = true; - } else { - selectee.$element.removeClass("ui-selecting"); - selectee.selecting = false; - if (selectee.startselected) { - selectee.$element.addClass("ui-unselecting"); - selectee.unselecting = true; - } - // selectable UNSELECTING callback - that._trigger("unselecting", event, { - unselecting: selectee.element - }); - } - } - if (selectee.selected) { - if (!event.metaKey && !event.ctrlKey && !selectee.startselected) { - selectee.$element.removeClass("ui-selected"); - selectee.selected = false; - - selectee.$element.addClass("ui-unselecting"); - selectee.unselecting = true; - // selectable UNSELECTING callback - that._trigger("unselecting", event, { - unselecting: selectee.element - }); - } - } - } - }); - - return false; - }, - - _mouseStop: function(event) { - var that = this; - - this.dragged = false; - - $(".ui-unselecting", this.element[0]).each(function() { - var selectee = $.data(this, "selectable-item"); - selectee.$element.removeClass("ui-unselecting"); - selectee.unselecting = false; - selectee.startselected = false; - that._trigger("unselected", event, { - unselected: selectee.element - }); - }); - $(".ui-selecting", this.element[0]).each(function() { - var selectee = $.data(this, "selectable-item"); - selectee.$element.removeClass("ui-selecting").addClass("ui-selected"); - selectee.selecting = false; - selectee.selected = true; - selectee.startselected = true; - that._trigger("selected", event, { - selected: selectee.element - }); - }); - this._trigger("stop", event); - - this.helper.remove(); - - return false; - } - -}); - -})(jQuery); -(function( $, undefined ) { - -/*jshint loopfunc: true */ - -function isOverAxis( x, reference, size ) { - return ( x > reference ) && ( x < ( reference + size ) ); -} - -function isFloating(item) { - return (/left|right/).test(item.css("float")) || (/inline|table-cell/).test(item.css("display")); -} - -$.widget("ui.sortable", $.ui.mouse, { - version: "1.10.3", - widgetEventPrefix: "sort", - ready: false, - options: { - appendTo: "parent", - axis: false, - connectWith: false, - containment: false, - cursor: "auto", - cursorAt: false, - dropOnEmpty: true, - forcePlaceholderSize: false, - forceHelperSize: false, - grid: false, - handle: false, - helper: "original", - items: "> *", - opacity: false, - placeholder: false, - revert: false, - scroll: true, - scrollSensitivity: 20, - scrollSpeed: 20, - scope: "default", - tolerance: "intersect", - zIndex: 1000, - - // callbacks - activate: null, - beforeStop: null, - change: null, - deactivate: null, - out: null, - over: null, - receive: null, - remove: null, - sort: null, - start: null, - stop: null, - update: null - }, - _create: function() { - - var o = this.options; - this.containerCache = {}; - this.element.addClass("ui-sortable"); - - //Get the items - this.refresh(); - - //Let's determine if the items are being displayed horizontally - this.floating = this.items.length ? o.axis === "x" || isFloating(this.items[0].item) : false; - - //Let's determine the parent's offset - this.offset = this.element.offset(); - - //Initialize mouse events for interaction - this._mouseInit(); - - //We're ready to go - this.ready = true; - - }, - - _destroy: function() { - this.element - .removeClass("ui-sortable ui-sortable-disabled"); - this._mouseDestroy(); - - for ( var i = this.items.length - 1; i >= 0; i-- ) { - this.items[i].item.removeData(this.widgetName + "-item"); - } - - return this; - }, - - _setOption: function(key, value){ - if ( key === "disabled" ) { - this.options[ key ] = value; - - this.widget().toggleClass( "ui-sortable-disabled", !!value ); - } else { - // Don't call widget base _setOption for disable as it adds ui-state-disabled class - $.Widget.prototype._setOption.apply(this, arguments); - } - }, - - _mouseCapture: function(event, overrideHandle) { - var currentItem = null, - validHandle = false, - that = this; - - if (this.reverting) { - return false; - } - - if(this.options.disabled || this.options.type === "static") { - return false; - } - - //We have to refresh the items data once first - this._refreshItems(event); - - //Find out if the clicked node (or one of its parents) is a actual item in this.items - $(event.target).parents().each(function() { - if($.data(this, that.widgetName + "-item") === that) { - currentItem = $(this); - return false; - } - }); - if($.data(event.target, that.widgetName + "-item") === that) { - currentItem = $(event.target); - } - - if(!currentItem) { - return false; - } - if(this.options.handle && !overrideHandle) { - $(this.options.handle, currentItem).find("*").addBack().each(function() { - if(this === event.target) { - validHandle = true; - } - }); - if(!validHandle) { - return false; - } - } - - this.currentItem = currentItem; - this._removeCurrentsFromItems(); - return true; - - }, - - _mouseStart: function(event, overrideHandle, noActivation) { - - var i, body, - o = this.options; - - this.currentContainer = this; - - //We only need to call refreshPositions, because the refreshItems call has been moved to mouseCapture - this.refreshPositions(); - - //Create and append the visible helper - this.helper = this._createHelper(event); - - //Cache the helper size - this._cacheHelperProportions(); - - /* - * - Position generation - - * This block generates everything position related - it's the core of draggables. - */ - - //Cache the margins of the original element - this._cacheMargins(); - - //Get the next scrolling parent - this.scrollParent = this.helper.scrollParent(); - - //The element's absolute position on the page minus margins - this.offset = this.currentItem.offset(); - this.offset = { - top: this.offset.top - this.margins.top, - left: this.offset.left - this.margins.left - }; - - $.extend(this.offset, { - click: { //Where the click happened, relative to the element - left: event.pageX - this.offset.left, - top: event.pageY - this.offset.top - }, - parent: this._getParentOffset(), - relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper - }); - - // Only after we got the offset, we can change the helper's position to absolute - // TODO: Still need to figure out a way to make relative sorting possible - this.helper.css("position", "absolute"); - this.cssPosition = this.helper.css("position"); - - //Generate the original position - this.originalPosition = this._generatePosition(event); - this.originalPageX = event.pageX; - this.originalPageY = event.pageY; - - //Adjust the mouse offset relative to the helper if "cursorAt" is supplied - (o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt)); - - //Cache the former DOM position - this.domPosition = { prev: this.currentItem.prev()[0], parent: this.currentItem.parent()[0] }; - - //If the helper is not the original, hide the original so it's not playing any role during the drag, won't cause anything bad this way - if(this.helper[0] !== this.currentItem[0]) { - this.currentItem.hide(); - } - - //Create the placeholder - this._createPlaceholder(); - - //Set a containment if given in the options - if(o.containment) { - this._setContainment(); - } - - if( o.cursor && o.cursor !== "auto" ) { // cursor option - body = this.document.find( "body" ); - - // support: IE - this.storedCursor = body.css( "cursor" ); - body.css( "cursor", o.cursor ); - - this.storedStylesheet = $( "" ).appendTo( body ); - } - - if(o.opacity) { // opacity option - if (this.helper.css("opacity")) { - this._storedOpacity = this.helper.css("opacity"); - } - this.helper.css("opacity", o.opacity); - } - - if(o.zIndex) { // zIndex option - if (this.helper.css("zIndex")) { - this._storedZIndex = this.helper.css("zIndex"); - } - this.helper.css("zIndex", o.zIndex); - } - - //Prepare scrolling - if(this.scrollParent[0] !== document && this.scrollParent[0].tagName !== "HTML") { - this.overflowOffset = this.scrollParent.offset(); - } - - //Call callbacks - this._trigger("start", event, this._uiHash()); - - //Recache the helper size - if(!this._preserveHelperProportions) { - this._cacheHelperProportions(); - } - - - //Post "activate" events to possible containers - if( !noActivation ) { - for ( i = this.containers.length - 1; i >= 0; i-- ) { - this.containers[ i ]._trigger( "activate", event, this._uiHash( this ) ); - } - } - - //Prepare possible droppables - if($.ui.ddmanager) { - $.ui.ddmanager.current = this; - } - - if ($.ui.ddmanager && !o.dropBehaviour) { - $.ui.ddmanager.prepareOffsets(this, event); - } - - this.dragging = true; - - this.helper.addClass("ui-sortable-helper"); - this._mouseDrag(event); //Execute the drag once - this causes the helper not to be visible before getting its correct position - return true; - - }, - - _mouseDrag: function(event) { - var i, item, itemElement, intersection, - o = this.options, - scrolled = false; - - //Compute the helpers position - this.position = this._generatePosition(event); - this.positionAbs = this._convertPositionTo("absolute"); - - if (!this.lastPositionAbs) { - this.lastPositionAbs = this.positionAbs; - } - - //Do scrolling - if(this.options.scroll) { - if(this.scrollParent[0] !== document && this.scrollParent[0].tagName !== "HTML") { - - if((this.overflowOffset.top + this.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity) { - this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop + o.scrollSpeed; - } else if(event.pageY - this.overflowOffset.top < o.scrollSensitivity) { - this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop - o.scrollSpeed; - } - - if((this.overflowOffset.left + this.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity) { - this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft + o.scrollSpeed; - } else if(event.pageX - this.overflowOffset.left < o.scrollSensitivity) { - this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft - o.scrollSpeed; - } - - } else { - - if(event.pageY - $(document).scrollTop() < o.scrollSensitivity) { - scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed); - } else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity) { - scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed); - } - - if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity) { - scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed); - } else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity) { - scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed); - } - - } - - if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour) { - $.ui.ddmanager.prepareOffsets(this, event); - } - } - - //Regenerate the absolute position used for position checks - this.positionAbs = this._convertPositionTo("absolute"); - - //Set the helper position - if(!this.options.axis || this.options.axis !== "y") { - this.helper[0].style.left = this.position.left+"px"; - } - if(!this.options.axis || this.options.axis !== "x") { - this.helper[0].style.top = this.position.top+"px"; - } - - //Rearrange - for (i = this.items.length - 1; i >= 0; i--) { - - //Cache variables and intersection, continue if no intersection - item = this.items[i]; - itemElement = item.item[0]; - intersection = this._intersectsWithPointer(item); - if (!intersection) { - continue; - } - - // Only put the placeholder inside the current Container, skip all - // items form other containers. This works because when moving - // an item from one container to another the - // currentContainer is switched before the placeholder is moved. - // - // Without this moving items in "sub-sortables" can cause the placeholder to jitter - // beetween the outer and inner container. - if (item.instance !== this.currentContainer) { - continue; - } - - // cannot intersect with itself - // no useless actions that have been done before - // no action if the item moved is the parent of the item checked - if (itemElement !== this.currentItem[0] && - this.placeholder[intersection === 1 ? "next" : "prev"]()[0] !== itemElement && - !$.contains(this.placeholder[0], itemElement) && - (this.options.type === "semi-dynamic" ? !$.contains(this.element[0], itemElement) : true) - ) { - - this.direction = intersection === 1 ? "down" : "up"; - - if (this.options.tolerance === "pointer" || this._intersectsWithSides(item)) { - this._rearrange(event, item); - } else { - break; - } - - this._trigger("change", event, this._uiHash()); - break; - } - } - - //Post events to containers - this._contactContainers(event); - - //Interconnect with droppables - if($.ui.ddmanager) { - $.ui.ddmanager.drag(this, event); - } - - //Call callbacks - this._trigger("sort", event, this._uiHash()); - - this.lastPositionAbs = this.positionAbs; - return false; - - }, - - _mouseStop: function(event, noPropagation) { - - if(!event) { - return; - } - - //If we are using droppables, inform the manager about the drop - if ($.ui.ddmanager && !this.options.dropBehaviour) { - $.ui.ddmanager.drop(this, event); - } - - if(this.options.revert) { - var that = this, - cur = this.placeholder.offset(), - axis = this.options.axis, - animation = {}; - - if ( !axis || axis === "x" ) { - animation.left = cur.left - this.offset.parent.left - this.margins.left + (this.offsetParent[0] === document.body ? 0 : this.offsetParent[0].scrollLeft); - } - if ( !axis || axis === "y" ) { - animation.top = cur.top - this.offset.parent.top - this.margins.top + (this.offsetParent[0] === document.body ? 0 : this.offsetParent[0].scrollTop); - } - this.reverting = true; - $(this.helper).animate( animation, parseInt(this.options.revert, 10) || 500, function() { - that._clear(event); - }); - } else { - this._clear(event, noPropagation); - } - - return false; - - }, - - cancel: function() { - - if(this.dragging) { - - this._mouseUp({ target: null }); - - if(this.options.helper === "original") { - this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper"); - } else { - this.currentItem.show(); - } - - //Post deactivating events to containers - for (var i = this.containers.length - 1; i >= 0; i--){ - this.containers[i]._trigger("deactivate", null, this._uiHash(this)); - if(this.containers[i].containerCache.over) { - this.containers[i]._trigger("out", null, this._uiHash(this)); - this.containers[i].containerCache.over = 0; - } - } - - } - - if (this.placeholder) { - //$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node! - if(this.placeholder[0].parentNode) { - this.placeholder[0].parentNode.removeChild(this.placeholder[0]); - } - if(this.options.helper !== "original" && this.helper && this.helper[0].parentNode) { - this.helper.remove(); - } - - $.extend(this, { - helper: null, - dragging: false, - reverting: false, - _noFinalSort: null - }); - - if(this.domPosition.prev) { - $(this.domPosition.prev).after(this.currentItem); - } else { - $(this.domPosition.parent).prepend(this.currentItem); - } - } - - return this; - - }, - - serialize: function(o) { - - var items = this._getItemsAsjQuery(o && o.connected), - str = []; - o = o || {}; - - $(items).each(function() { - var res = ($(o.item || this).attr(o.attribute || "id") || "").match(o.expression || (/(.+)[\-=_](.+)/)); - if (res) { - str.push((o.key || res[1]+"[]")+"="+(o.key && o.expression ? res[1] : res[2])); - } - }); - - if(!str.length && o.key) { - str.push(o.key + "="); - } - - return str.join("&"); - - }, - - toArray: function(o) { - - var items = this._getItemsAsjQuery(o && o.connected), - ret = []; - - o = o || {}; - - items.each(function() { ret.push($(o.item || this).attr(o.attribute || "id") || ""); }); - return ret; - - }, - - /* Be careful with the following core functions */ - _intersectsWith: function(item) { - - var x1 = this.positionAbs.left, - x2 = x1 + this.helperProportions.width, - y1 = this.positionAbs.top, - y2 = y1 + this.helperProportions.height, - l = item.left, - r = l + item.width, - t = item.top, - b = t + item.height, - dyClick = this.offset.click.top, - dxClick = this.offset.click.left, - isOverElementHeight = ( this.options.axis === "x" ) || ( ( y1 + dyClick ) > t && ( y1 + dyClick ) < b ), - isOverElementWidth = ( this.options.axis === "y" ) || ( ( x1 + dxClick ) > l && ( x1 + dxClick ) < r ), - isOverElement = isOverElementHeight && isOverElementWidth; - - if ( this.options.tolerance === "pointer" || - this.options.forcePointerForContainers || - (this.options.tolerance !== "pointer" && this.helperProportions[this.floating ? "width" : "height"] > item[this.floating ? "width" : "height"]) - ) { - return isOverElement; - } else { - - return (l < x1 + (this.helperProportions.width / 2) && // Right Half - x2 - (this.helperProportions.width / 2) < r && // Left Half - t < y1 + (this.helperProportions.height / 2) && // Bottom Half - y2 - (this.helperProportions.height / 2) < b ); // Top Half - - } - }, - - _intersectsWithPointer: function(item) { - - var isOverElementHeight = (this.options.axis === "x") || isOverAxis(this.positionAbs.top + this.offset.click.top, item.top, item.height), - isOverElementWidth = (this.options.axis === "y") || isOverAxis(this.positionAbs.left + this.offset.click.left, item.left, item.width), - isOverElement = isOverElementHeight && isOverElementWidth, - verticalDirection = this._getDragVerticalDirection(), - horizontalDirection = this._getDragHorizontalDirection(); - - if (!isOverElement) { - return false; - } - - return this.floating ? - ( ((horizontalDirection && horizontalDirection === "right") || verticalDirection === "down") ? 2 : 1 ) - : ( verticalDirection && (verticalDirection === "down" ? 2 : 1) ); - - }, - - _intersectsWithSides: function(item) { - - var isOverBottomHalf = isOverAxis(this.positionAbs.top + this.offset.click.top, item.top + (item.height/2), item.height), - isOverRightHalf = isOverAxis(this.positionAbs.left + this.offset.click.left, item.left + (item.width/2), item.width), - verticalDirection = this._getDragVerticalDirection(), - horizontalDirection = this._getDragHorizontalDirection(); - - if (this.floating && horizontalDirection) { - return ((horizontalDirection === "right" && isOverRightHalf) || (horizontalDirection === "left" && !isOverRightHalf)); - } else { - return verticalDirection && ((verticalDirection === "down" && isOverBottomHalf) || (verticalDirection === "up" && !isOverBottomHalf)); - } - - }, - - _getDragVerticalDirection: function() { - var delta = this.positionAbs.top - this.lastPositionAbs.top; - return delta !== 0 && (delta > 0 ? "down" : "up"); - }, - - _getDragHorizontalDirection: function() { - var delta = this.positionAbs.left - this.lastPositionAbs.left; - return delta !== 0 && (delta > 0 ? "right" : "left"); - }, - - refresh: function(event) { - this._refreshItems(event); - this.refreshPositions(); - return this; - }, - - _connectWith: function() { - var options = this.options; - return options.connectWith.constructor === String ? [options.connectWith] : options.connectWith; - }, - - _getItemsAsjQuery: function(connected) { - - var i, j, cur, inst, - items = [], - queries = [], - connectWith = this._connectWith(); - - if(connectWith && connected) { - for (i = connectWith.length - 1; i >= 0; i--){ - cur = $(connectWith[i]); - for ( j = cur.length - 1; j >= 0; j--){ - inst = $.data(cur[j], this.widgetFullName); - if(inst && inst !== this && !inst.options.disabled) { - queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element) : $(inst.options.items, inst.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"), inst]); - } - } - } - } - - queries.push([$.isFunction(this.options.items) ? this.options.items.call(this.element, null, { options: this.options, item: this.currentItem }) : $(this.options.items, this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"), this]); - - for (i = queries.length - 1; i >= 0; i--){ - queries[i][0].each(function() { - items.push(this); - }); - } - - return $(items); - - }, - - _removeCurrentsFromItems: function() { - - var list = this.currentItem.find(":data(" + this.widgetName + "-item)"); - - this.items = $.grep(this.items, function (item) { - for (var j=0; j < list.length; j++) { - if(list[j] === item.item[0]) { - return false; - } - } - return true; - }); - - }, - - _refreshItems: function(event) { - - this.items = []; - this.containers = [this]; - - var i, j, cur, inst, targetData, _queries, item, queriesLength, - items = this.items, - queries = [[$.isFunction(this.options.items) ? this.options.items.call(this.element[0], event, { item: this.currentItem }) : $(this.options.items, this.element), this]], - connectWith = this._connectWith(); - - if(connectWith && this.ready) { //Shouldn't be run the first time through due to massive slow-down - for (i = connectWith.length - 1; i >= 0; i--){ - cur = $(connectWith[i]); - for (j = cur.length - 1; j >= 0; j--){ - inst = $.data(cur[j], this.widgetFullName); - if(inst && inst !== this && !inst.options.disabled) { - queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element[0], event, { item: this.currentItem }) : $(inst.options.items, inst.element), inst]); - this.containers.push(inst); - } - } - } - } - - for (i = queries.length - 1; i >= 0; i--) { - targetData = queries[i][1]; - _queries = queries[i][0]; - - for (j=0, queriesLength = _queries.length; j < queriesLength; j++) { - item = $(_queries[j]); - - item.data(this.widgetName + "-item", targetData); // Data for target checking (mouse manager) - - items.push({ - item: item, - instance: targetData, - width: 0, height: 0, - left: 0, top: 0 - }); - } - } - - }, - - refreshPositions: function(fast) { - - //This has to be redone because due to the item being moved out/into the offsetParent, the offsetParent's position will change - if(this.offsetParent && this.helper) { - this.offset.parent = this._getParentOffset(); - } - - var i, item, t, p; - - for (i = this.items.length - 1; i >= 0; i--){ - item = this.items[i]; - - //We ignore calculating positions of all connected containers when we're not over them - if(item.instance !== this.currentContainer && this.currentContainer && item.item[0] !== this.currentItem[0]) { - continue; - } - - t = this.options.toleranceElement ? $(this.options.toleranceElement, item.item) : item.item; - - if (!fast) { - item.width = t.outerWidth(); - item.height = t.outerHeight(); - } - - p = t.offset(); - item.left = p.left; - item.top = p.top; - } - - if(this.options.custom && this.options.custom.refreshContainers) { - this.options.custom.refreshContainers.call(this); - } else { - for (i = this.containers.length - 1; i >= 0; i--){ - p = this.containers[i].element.offset(); - this.containers[i].containerCache.left = p.left; - this.containers[i].containerCache.top = p.top; - this.containers[i].containerCache.width = this.containers[i].element.outerWidth(); - this.containers[i].containerCache.height = this.containers[i].element.outerHeight(); - } - } - - return this; - }, - - _createPlaceholder: function(that) { - that = that || this; - var className, - o = that.options; - - if(!o.placeholder || o.placeholder.constructor === String) { - className = o.placeholder; - o.placeholder = { - element: function() { - - var nodeName = that.currentItem[0].nodeName.toLowerCase(), - element = $( "<" + nodeName + ">", that.document[0] ) - .addClass(className || that.currentItem[0].className+" ui-sortable-placeholder") - .removeClass("ui-sortable-helper"); - - if ( nodeName === "tr" ) { - that.currentItem.children().each(function() { - $( " ", that.document[0] ) - .attr( "colspan", $( this ).attr( "colspan" ) || 1 ) - .appendTo( element ); - }); - } else if ( nodeName === "img" ) { - element.attr( "src", that.currentItem.attr( "src" ) ); - } - - if ( !className ) { - element.css( "visibility", "hidden" ); - } - - return element; - }, - update: function(container, p) { - - // 1. If a className is set as 'placeholder option, we don't force sizes - the class is responsible for that - // 2. The option 'forcePlaceholderSize can be enabled to force it even if a class name is specified - if(className && !o.forcePlaceholderSize) { - return; - } - - //If the element doesn't have a actual height by itself (without styles coming from a stylesheet), it receives the inline height from the dragged item - if(!p.height()) { p.height(that.currentItem.innerHeight() - parseInt(that.currentItem.css("paddingTop")||0, 10) - parseInt(that.currentItem.css("paddingBottom")||0, 10)); } - if(!p.width()) { p.width(that.currentItem.innerWidth() - parseInt(that.currentItem.css("paddingLeft")||0, 10) - parseInt(that.currentItem.css("paddingRight")||0, 10)); } - } - }; - } - - //Create the placeholder - that.placeholder = $(o.placeholder.element.call(that.element, that.currentItem)); - - //Append it after the actual current item - that.currentItem.after(that.placeholder); - - //Update the size of the placeholder (TODO: Logic to fuzzy, see line 316/317) - o.placeholder.update(that, that.placeholder); - - }, - - _contactContainers: function(event) { - var i, j, dist, itemWithLeastDistance, posProperty, sizeProperty, base, cur, nearBottom, floating, - innermostContainer = null, - innermostIndex = null; - - // get innermost container that intersects with item - for (i = this.containers.length - 1; i >= 0; i--) { - - // never consider a container that's located within the item itself - if($.contains(this.currentItem[0], this.containers[i].element[0])) { - continue; - } - - if(this._intersectsWith(this.containers[i].containerCache)) { - - // if we've already found a container and it's more "inner" than this, then continue - if(innermostContainer && $.contains(this.containers[i].element[0], innermostContainer.element[0])) { - continue; - } - - innermostContainer = this.containers[i]; - innermostIndex = i; - - } else { - // container doesn't intersect. trigger "out" event if necessary - if(this.containers[i].containerCache.over) { - this.containers[i]._trigger("out", event, this._uiHash(this)); - this.containers[i].containerCache.over = 0; - } - } - - } - - // if no intersecting containers found, return - if(!innermostContainer) { - return; - } - - // move the item into the container if it's not there already - if(this.containers.length === 1) { - if (!this.containers[innermostIndex].containerCache.over) { - this.containers[innermostIndex]._trigger("over", event, this._uiHash(this)); - this.containers[innermostIndex].containerCache.over = 1; - } - } else { - - //When entering a new container, we will find the item with the least distance and append our item near it - dist = 10000; - itemWithLeastDistance = null; - floating = innermostContainer.floating || isFloating(this.currentItem); - posProperty = floating ? "left" : "top"; - sizeProperty = floating ? "width" : "height"; - base = this.positionAbs[posProperty] + this.offset.click[posProperty]; - for (j = this.items.length - 1; j >= 0; j--) { - if(!$.contains(this.containers[innermostIndex].element[0], this.items[j].item[0])) { - continue; - } - if(this.items[j].item[0] === this.currentItem[0]) { - continue; - } - if (floating && !isOverAxis(this.positionAbs.top + this.offset.click.top, this.items[j].top, this.items[j].height)) { - continue; - } - cur = this.items[j].item.offset()[posProperty]; - nearBottom = false; - if(Math.abs(cur - base) > Math.abs(cur + this.items[j][sizeProperty] - base)){ - nearBottom = true; - cur += this.items[j][sizeProperty]; - } - - if(Math.abs(cur - base) < dist) { - dist = Math.abs(cur - base); itemWithLeastDistance = this.items[j]; - this.direction = nearBottom ? "up": "down"; - } - } - - //Check if dropOnEmpty is enabled - if(!itemWithLeastDistance && !this.options.dropOnEmpty) { - return; - } - - if(this.currentContainer === this.containers[innermostIndex]) { - return; - } - - itemWithLeastDistance ? this._rearrange(event, itemWithLeastDistance, null, true) : this._rearrange(event, null, this.containers[innermostIndex].element, true); - this._trigger("change", event, this._uiHash()); - this.containers[innermostIndex]._trigger("change", event, this._uiHash(this)); - this.currentContainer = this.containers[innermostIndex]; - - //Update the placeholder - this.options.placeholder.update(this.currentContainer, this.placeholder); - - this.containers[innermostIndex]._trigger("over", event, this._uiHash(this)); - this.containers[innermostIndex].containerCache.over = 1; - } - - - }, - - _createHelper: function(event) { - - var o = this.options, - helper = $.isFunction(o.helper) ? $(o.helper.apply(this.element[0], [event, this.currentItem])) : (o.helper === "clone" ? this.currentItem.clone() : this.currentItem); - - //Add the helper to the DOM if that didn't happen already - if(!helper.parents("body").length) { - $(o.appendTo !== "parent" ? o.appendTo : this.currentItem[0].parentNode)[0].appendChild(helper[0]); - } - - if(helper[0] === this.currentItem[0]) { - this._storedCSS = { width: this.currentItem[0].style.width, height: this.currentItem[0].style.height, position: this.currentItem.css("position"), top: this.currentItem.css("top"), left: this.currentItem.css("left") }; - } - - if(!helper[0].style.width || o.forceHelperSize) { - helper.width(this.currentItem.width()); - } - if(!helper[0].style.height || o.forceHelperSize) { - helper.height(this.currentItem.height()); - } - - return helper; - - }, - - _adjustOffsetFromHelper: function(obj) { - if (typeof obj === "string") { - obj = obj.split(" "); - } - if ($.isArray(obj)) { - obj = {left: +obj[0], top: +obj[1] || 0}; - } - if ("left" in obj) { - this.offset.click.left = obj.left + this.margins.left; - } - if ("right" in obj) { - this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left; - } - if ("top" in obj) { - this.offset.click.top = obj.top + this.margins.top; - } - if ("bottom" in obj) { - this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top; - } - }, - - _getParentOffset: function() { - - - //Get the offsetParent and cache its position - this.offsetParent = this.helper.offsetParent(); - var po = this.offsetParent.offset(); - - // This is a special case where we need to modify a offset calculated on start, since the following happened: - // 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent - // 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that - // the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag - if(this.cssPosition === "absolute" && this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) { - po.left += this.scrollParent.scrollLeft(); - po.top += this.scrollParent.scrollTop(); - } - - // This needs to be actually done for all browsers, since pageX/pageY includes this information - // with an ugly IE fix - if( this.offsetParent[0] === document.body || (this.offsetParent[0].tagName && this.offsetParent[0].tagName.toLowerCase() === "html" && $.ui.ie)) { - po = { top: 0, left: 0 }; - } - - return { - top: po.top + (parseInt(this.offsetParent.css("borderTopWidth"),10) || 0), - left: po.left + (parseInt(this.offsetParent.css("borderLeftWidth"),10) || 0) - }; - - }, - - _getRelativeOffset: function() { - - if(this.cssPosition === "relative") { - var p = this.currentItem.position(); - return { - top: p.top - (parseInt(this.helper.css("top"),10) || 0) + this.scrollParent.scrollTop(), - left: p.left - (parseInt(this.helper.css("left"),10) || 0) + this.scrollParent.scrollLeft() - }; - } else { - return { top: 0, left: 0 }; - } - - }, - - _cacheMargins: function() { - this.margins = { - left: (parseInt(this.currentItem.css("marginLeft"),10) || 0), - top: (parseInt(this.currentItem.css("marginTop"),10) || 0) - }; - }, - - _cacheHelperProportions: function() { - this.helperProportions = { - width: this.helper.outerWidth(), - height: this.helper.outerHeight() - }; - }, - - _setContainment: function() { - - var ce, co, over, - o = this.options; - if(o.containment === "parent") { - o.containment = this.helper[0].parentNode; - } - if(o.containment === "document" || o.containment === "window") { - this.containment = [ - 0 - this.offset.relative.left - this.offset.parent.left, - 0 - this.offset.relative.top - this.offset.parent.top, - $(o.containment === "document" ? document : window).width() - this.helperProportions.width - this.margins.left, - ($(o.containment === "document" ? document : window).height() || document.body.parentNode.scrollHeight) - this.helperProportions.height - this.margins.top - ]; - } - - if(!(/^(document|window|parent)$/).test(o.containment)) { - ce = $(o.containment)[0]; - co = $(o.containment).offset(); - over = ($(ce).css("overflow") !== "hidden"); - - this.containment = [ - co.left + (parseInt($(ce).css("borderLeftWidth"),10) || 0) + (parseInt($(ce).css("paddingLeft"),10) || 0) - this.margins.left, - co.top + (parseInt($(ce).css("borderTopWidth"),10) || 0) + (parseInt($(ce).css("paddingTop"),10) || 0) - this.margins.top, - co.left+(over ? Math.max(ce.scrollWidth,ce.offsetWidth) : ce.offsetWidth) - (parseInt($(ce).css("borderLeftWidth"),10) || 0) - (parseInt($(ce).css("paddingRight"),10) || 0) - this.helperProportions.width - this.margins.left, - co.top+(over ? Math.max(ce.scrollHeight,ce.offsetHeight) : ce.offsetHeight) - (parseInt($(ce).css("borderTopWidth"),10) || 0) - (parseInt($(ce).css("paddingBottom"),10) || 0) - this.helperProportions.height - this.margins.top - ]; - } - - }, - - _convertPositionTo: function(d, pos) { - - if(!pos) { - pos = this.position; - } - var mod = d === "absolute" ? 1 : -1, - scroll = this.cssPosition === "absolute" && !(this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, - scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName); - - return { - top: ( - pos.top + // The absolute mouse position - this.offset.relative.top * mod + // Only for relative positioned nodes: Relative offset from element to offset parent - this.offset.parent.top * mod - // The offsetParent's offset without borders (offset + border) - ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) * mod) - ), - left: ( - pos.left + // The absolute mouse position - this.offset.relative.left * mod + // Only for relative positioned nodes: Relative offset from element to offset parent - this.offset.parent.left * mod - // The offsetParent's offset without borders (offset + border) - ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ) * mod) - ) - }; - - }, - - _generatePosition: function(event) { - - var top, left, - o = this.options, - pageX = event.pageX, - pageY = event.pageY, - scroll = this.cssPosition === "absolute" && !(this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName); - - // This is another very weird special case that only happens for relative elements: - // 1. If the css position is relative - // 2. and the scroll parent is the document or similar to the offset parent - // we have to refresh the relative offset during the scroll so there are no jumps - if(this.cssPosition === "relative" && !(this.scrollParent[0] !== document && this.scrollParent[0] !== this.offsetParent[0])) { - this.offset.relative = this._getRelativeOffset(); - } - - /* - * - Position constraining - - * Constrain the position to a mix of grid, containment. - */ - - if(this.originalPosition) { //If we are not dragging yet, we won't check for options - - if(this.containment) { - if(event.pageX - this.offset.click.left < this.containment[0]) { - pageX = this.containment[0] + this.offset.click.left; - } - if(event.pageY - this.offset.click.top < this.containment[1]) { - pageY = this.containment[1] + this.offset.click.top; - } - if(event.pageX - this.offset.click.left > this.containment[2]) { - pageX = this.containment[2] + this.offset.click.left; - } - if(event.pageY - this.offset.click.top > this.containment[3]) { - pageY = this.containment[3] + this.offset.click.top; - } - } - - if(o.grid) { - top = this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1]; - pageY = this.containment ? ( (top - this.offset.click.top >= this.containment[1] && top - this.offset.click.top <= this.containment[3]) ? top : ((top - this.offset.click.top >= this.containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top; - - left = this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0]; - pageX = this.containment ? ( (left - this.offset.click.left >= this.containment[0] && left - this.offset.click.left <= this.containment[2]) ? left : ((left - this.offset.click.left >= this.containment[0]) ? left - o.grid[0] : left + o.grid[0])) : left; - } - - } - - return { - top: ( - pageY - // The absolute mouse position - this.offset.click.top - // Click offset (relative to the element) - this.offset.relative.top - // Only for relative positioned nodes: Relative offset from element to offset parent - this.offset.parent.top + // The offsetParent's offset without borders (offset + border) - ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) )) - ), - left: ( - pageX - // The absolute mouse position - this.offset.click.left - // Click offset (relative to the element) - this.offset.relative.left - // Only for relative positioned nodes: Relative offset from element to offset parent - this.offset.parent.left + // The offsetParent's offset without borders (offset + border) - ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() )) - ) - }; - - }, - - _rearrange: function(event, i, a, hardRefresh) { - - a ? a[0].appendChild(this.placeholder[0]) : i.item[0].parentNode.insertBefore(this.placeholder[0], (this.direction === "down" ? i.item[0] : i.item[0].nextSibling)); - - //Various things done here to improve the performance: - // 1. we create a setTimeout, that calls refreshPositions - // 2. on the instance, we have a counter variable, that get's higher after every append - // 3. on the local scope, we copy the counter variable, and check in the timeout, if it's still the same - // 4. this lets only the last addition to the timeout stack through - this.counter = this.counter ? ++this.counter : 1; - var counter = this.counter; - - this._delay(function() { - if(counter === this.counter) { - this.refreshPositions(!hardRefresh); //Precompute after each DOM insertion, NOT on mousemove - } - }); - - }, - - _clear: function(event, noPropagation) { - - this.reverting = false; - // We delay all events that have to be triggered to after the point where the placeholder has been removed and - // everything else normalized again - var i, - delayedTriggers = []; - - // We first have to update the dom position of the actual currentItem - // Note: don't do it if the current item is already removed (by a user), or it gets reappended (see #4088) - if(!this._noFinalSort && this.currentItem.parent().length) { - this.placeholder.before(this.currentItem); - } - this._noFinalSort = null; - - if(this.helper[0] === this.currentItem[0]) { - for(i in this._storedCSS) { - if(this._storedCSS[i] === "auto" || this._storedCSS[i] === "static") { - this._storedCSS[i] = ""; - } - } - this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper"); - } else { - this.currentItem.show(); - } - - if(this.fromOutside && !noPropagation) { - delayedTriggers.push(function(event) { this._trigger("receive", event, this._uiHash(this.fromOutside)); }); - } - if((this.fromOutside || this.domPosition.prev !== this.currentItem.prev().not(".ui-sortable-helper")[0] || this.domPosition.parent !== this.currentItem.parent()[0]) && !noPropagation) { - delayedTriggers.push(function(event) { this._trigger("update", event, this._uiHash()); }); //Trigger update callback if the DOM position has changed - } - - // Check if the items Container has Changed and trigger appropriate - // events. - if (this !== this.currentContainer) { - if(!noPropagation) { - delayedTriggers.push(function(event) { this._trigger("remove", event, this._uiHash()); }); - delayedTriggers.push((function(c) { return function(event) { c._trigger("receive", event, this._uiHash(this)); }; }).call(this, this.currentContainer)); - delayedTriggers.push((function(c) { return function(event) { c._trigger("update", event, this._uiHash(this)); }; }).call(this, this.currentContainer)); - } - } - - - //Post events to containers - for (i = this.containers.length - 1; i >= 0; i--){ - if(!noPropagation) { - delayedTriggers.push((function(c) { return function(event) { c._trigger("deactivate", event, this._uiHash(this)); }; }).call(this, this.containers[i])); - } - if(this.containers[i].containerCache.over) { - delayedTriggers.push((function(c) { return function(event) { c._trigger("out", event, this._uiHash(this)); }; }).call(this, this.containers[i])); - this.containers[i].containerCache.over = 0; - } - } - - //Do what was originally in plugins - if ( this.storedCursor ) { - this.document.find( "body" ).css( "cursor", this.storedCursor ); - this.storedStylesheet.remove(); - } - if(this._storedOpacity) { - this.helper.css("opacity", this._storedOpacity); - } - if(this._storedZIndex) { - this.helper.css("zIndex", this._storedZIndex === "auto" ? "" : this._storedZIndex); - } - - this.dragging = false; - if(this.cancelHelperRemoval) { - if(!noPropagation) { - this._trigger("beforeStop", event, this._uiHash()); - for (i=0; i < delayedTriggers.length; i++) { - delayedTriggers[i].call(this, event); - } //Trigger all delayed events - this._trigger("stop", event, this._uiHash()); - } - - this.fromOutside = false; - return false; - } - - if(!noPropagation) { - this._trigger("beforeStop", event, this._uiHash()); - } - - //$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node! - this.placeholder[0].parentNode.removeChild(this.placeholder[0]); - - if(this.helper[0] !== this.currentItem[0]) { - this.helper.remove(); - } - this.helper = null; - - if(!noPropagation) { - for (i=0; i < delayedTriggers.length; i++) { - delayedTriggers[i].call(this, event); - } //Trigger all delayed events - this._trigger("stop", event, this._uiHash()); - } - - this.fromOutside = false; - return true; - - }, - - _trigger: function() { - if ($.Widget.prototype._trigger.apply(this, arguments) === false) { - this.cancel(); - } - }, - - _uiHash: function(_inst) { - var inst = _inst || this; - return { - helper: inst.helper, - placeholder: inst.placeholder || $([]), - position: inst.position, - originalPosition: inst.originalPosition, - offset: inst.positionAbs, - item: inst.currentItem, - sender: _inst ? _inst.element : null - }; - } - -}); - -})(jQuery); diff --git a/src/vendor/jquery/jquery.flot.fillbelow.js b/src/vendor/jquery/jquery.flot.fillbelow.js new file mode 100644 index 00000000000..ede2cb5e2d0 --- /dev/null +++ b/src/vendor/jquery/jquery.flot.fillbelow.js @@ -0,0 +1,288 @@ +(function($) { + "use strict"; + + var options = { + series: { + fillBelowTo: null + } + }; + + function init(plot) { + function findBelowSeries( series, allseries ) { + + var i; + + for ( i = 0; i < allseries.length; ++i ) { + if ( allseries[ i ].id === series.fillBelowTo ) { + return allseries[ i ]; + } + } + + return null; + } + + /* top and bottom doesn't actually matter for this, we're just using it to help make this easier to think about */ + /* this is a vector cross product operation */ + function segmentIntersection(top_left_x, top_left_y, top_right_x, top_right_y, bottom_left_x, bottom_left_y, bottom_right_x, bottom_right_y) { + var top_delta_x, top_delta_y, bottom_delta_x, bottom_delta_y, + s, t; + + top_delta_x = top_right_x - top_left_x; + top_delta_y = top_right_y - top_left_y; + bottom_delta_x = bottom_right_x - bottom_left_x; + bottom_delta_y = bottom_right_y - bottom_left_y; + + s = ( + (-top_delta_y * (top_left_x - bottom_left_x)) + (top_delta_x * (top_left_y - bottom_left_y)) + ) / ( + -bottom_delta_x * top_delta_y + top_delta_x * bottom_delta_y + ); + + t = ( + (bottom_delta_x * (top_left_y - bottom_left_y)) - (bottom_delta_y * (top_left_x - bottom_left_x)) + ) / ( + -bottom_delta_x * top_delta_y + top_delta_x * bottom_delta_y + ); + + // Collision detected + if (s >= 0 && s <= 1 && t >= 0 && t <= 1) { + return [ + top_left_x + (t * top_delta_x), // X + top_left_y + (t * top_delta_y) // Y + ]; + } + + // No collision + return null; + } + + function plotDifferenceArea(plot, ctx, series) { + if ( series.fillBelowTo === null ) { + return; + } + + var otherseries, + + ps, + points, + + otherps, + otherpoints, + + plotOffset, + fillStyle; + + function openPolygon(x, y) { + ctx.beginPath(); + ctx.moveTo( + series.xaxis.p2c(x) + plotOffset.left, + series.yaxis.p2c(y) + plotOffset.top + ); + + } + + function closePolygon() { + ctx.closePath(); + ctx.fill(); + } + + function validateInput() { + if (points.length/ps !== otherpoints.length/otherps) { + console.error("Refusing to graph inconsistent number of points"); + return false; + } + + var i; + for (i = 0; i < (points.length / ps); i++) { + if ( + points[i * ps] !== null && + otherpoints[i * otherps] !== null && + points[i * ps] !== otherpoints[i * otherps] + ) { + console.error("Refusing to graph points without matching value"); + return false; + } + } + + return true; + } + + function findNextStart(start_i, end_i) { + console.assert(end_i > start_i, "expects the end index to be greater than the start index"); + + var start = ( + start_i === 0 || + points[start_i - 1] === null || + otherpoints[start_i - 1] === null + ), + equal = false, + i, + intersect; + + for (i = start_i; i < end_i; i++) { + // Take note of null points + if ( + points[(i * ps) + 1] === null || + otherpoints[(i * ps) + 1] === null + ) { + equal = false; + start = true; + } + + // Take note of equal points + else if (points[(i * ps) + 1] === otherpoints[(i * otherps) + 1]) { + equal = true; + start = false; + } + + + else if (points[(i * ps) + 1] > otherpoints[(i * otherps) + 1]) { + // If we begin above the desired point + if (start) { + openPolygon(points[i * ps], points[(i * ps) + 1]); + } + + // If an equal point preceeds this, start the polygon at that equal point + else if (equal) { + openPolygon(points[(i - 1) * ps], points[((i - 1) * ps) + 1]); + } + + // Otherwise, find the intersection point, and start it there + else { + intersect = intersectionPoint(i); + openPolygon(intersect[0], intersect[1]); + } + + topTraversal(i, end_i); + return; + } + + // If we go below equal, equal at any preceeding point is irrelevant + else { + start = false; + equal = false; + } + } + } + + function intersectionPoint(right_i) { + console.assert(right_i > 0, "expects the second point in the series line segment"); + + var i, intersect; + + for (i = 1; i < (otherpoints.length/otherps); i++) { + intersect = segmentIntersection( + points[(right_i - 1) * ps], points[((right_i - 1) * ps) + 1], + points[right_i * ps], points[(right_i * ps) + 1], + + otherpoints[(i - 1) * otherps], otherpoints[((i - 1) * otherps) + 1], + otherpoints[i * otherps], otherpoints[(i * otherps) + 1] + ); + + if (intersect !== null) { + return intersect; + } + } + + console.error("intersectionPoint() should only be called when an intersection happens"); + } + + function bottomTraversal(start_i, end_i) { + console.assert(start_i >= end_i, "the start should be the rightmost point, and the end should be the leftmost (excluding the equal or intersecting point)"); + + var i; + + for (i = start_i; i >= end_i; i--) { + ctx.lineTo( + otherseries.xaxis.p2c(otherpoints[i * otherps]) + plotOffset.left, + otherseries.yaxis.p2c(otherpoints[(i * otherps) + 1]) + plotOffset.top + ); + } + + closePolygon(); + } + + function topTraversal(start_i, end_i) { + console.assert(start_i <= end_i, "the start should be the rightmost point, and the end should be the leftmost (excluding the equal or intersecting point)"); + + var i, + intersect; + + for (i = start_i; i < end_i; i++) { + if (points[(i * ps) + 1] === null && i > start_i) { + bottomTraversal(i - 1, start_i); + findNextStart(i, end_i); + return; + } + + else if (points[(i * ps) + 1] === otherpoints[(i * otherps) + 1]) { + bottomTraversal(i, start_i); + findNextStart(i, end_i); + return; + } + + else if (points[(i * ps) + 1] < otherpoints[(i * otherps) + 1]) { + intersect = intersectionPoint(i); + ctx.lineTo( + series.xaxis.p2c(intersect[0]) + plotOffset.left, + series.yaxis.p2c(intersect[1]) + plotOffset.top + ); + bottomTraversal(i, start_i); + findNextStart(i, end_i); + return; + + } + + else { + ctx.lineTo( + series.xaxis.p2c(points[i * ps]) + plotOffset.left, + series.yaxis.p2c(points[(i * ps) + 1]) + plotOffset.top + ); + } + } + + bottomTraversal(end_i, start_i); + } + + + // Begin processing + + otherseries = findBelowSeries( series, plot.getData() ); + + if ( !otherseries ) { + return; + } + + ps = series.datapoints.pointsize; + points = series.datapoints.points; + otherps = otherseries.datapoints.pointsize; + otherpoints = otherseries.datapoints.points; + plotOffset = plot.getPlotOffset(); + + if (!validateInput()) { + return; + } + + + // Flot's getFillStyle() should probably be exposed somewhere + fillStyle = $.color.parse(series.color); + fillStyle.a = 0.4; + fillStyle.normalize(); + ctx.fillStyle = fillStyle.toString(); + + + // Begin recursive bi-directional traversal + findNextStart(0, points.length/ps); + } + + plot.hooks.drawSeries.push(plotDifferenceArea); + } + + $.plot.plugins.push({ + init: init, + options: options, + name: "fillbelow", + version: "0.1.0" + }); + +})(jQuery); diff --git a/src/vendor/jquery/jquery.flot.js b/src/vendor/jquery/jquery.flot.js index 87a1b17c742..dc9e3677851 100644 --- a/src/vendor/jquery/jquery.flot.js +++ b/src/vendor/jquery/jquery.flot.js @@ -1416,7 +1416,8 @@ Licensed under the MIT license. var info = surface.getTextInfo(layer, t.label, font, null, maxWidth); - labelWidth = Math.max(labelWidth, info.width); + /// Grafana fix, add +1 to label width + labelWidth = Math.max(labelWidth, info.width + 1); labelHeight = Math.max(labelHeight, info.height); } diff --git a/tasks/options/ngmin.js b/tasks/options/ngmin.js index 39dbb7a540a..1a25ebee279 100644 --- a/tasks/options/ngmin.js +++ b/tasks/options/ngmin.js @@ -8,8 +8,10 @@ module.exports = function(config) { 'app/directives/**/*.js', 'app/services/**/*.js', 'app/filters/**/*.js', + 'app/features/**/*.js', 'app/panels/**/*.js', 'app/routes/**/*.js', + 'plugins/**/*.js', 'app/app.js', 'vendor/angular/**/*.js', ], diff --git a/tasks/options/ngtemplates.js b/tasks/options/ngtemplates.js index 7971a102e6c..bf6eb86930a 100644 --- a/tasks/options/ngtemplates.js +++ b/tasks/options/ngtemplates.js @@ -15,4 +15,4 @@ module.exports = function(config) { } } }; -}; \ No newline at end of file +}; diff --git a/tasks/options/requirejs.js b/tasks/options/requirejs.js index a5e9008ac3c..a889b9fd154 100644 --- a/tasks/options/requirejs.js +++ b/tasks/options/requirejs.js @@ -54,12 +54,13 @@ module.exports = function(config,grunt) { 'timepicker', 'datepicker', 'lodash', - 'filters/all', 'jquery.flot', - 'services/all', 'angular-strap', - 'directives/all', 'angular-dragdrop', + 'services/all', + 'features/all', + 'directives/all', + 'filters/all', 'controllers/all', 'routes/all', 'components/partials',