Skip to content

Commit

Permalink
Merge pull request #6566 from bevacqua/feature/sort-dimensions-dragging
Browse files Browse the repository at this point in the history
Drag aggregations to sort instead of having up/down arrows.
  • Loading branch information
bevacqua committed May 27, 2016
2 parents 00d01cf + 431ba4c commit 1cf2979
Show file tree
Hide file tree
Showing 20 changed files with 343 additions and 41 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@
"commander": "2.8.1",
"css-loader": "0.17.0",
"d3": "3.5.6",
"dragula": "3.7.0",
"elasticsearch": "10.1.2",
"elasticsearch-browser": "10.1.2",
"expiry-js": "0.1.7",
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/dev_mode/public/vis_debug_spy_panel.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ function VisDetailsSpyProvider(Notifier, $filter, $rootScope, config) {
template: visDebugSpyPanelTemplate,
order: 5,
link: function ($scope, $el) {
$scope.$watch('vis.getState() | json', function (json) {
$scope.$watch('vis.getEnabledState() | json', function (json) {
$scope.visStateJson = json;
});
}
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/kibana/public/discover/controllers/discover.js
Original file line number Diff line number Diff line change
Expand Up @@ -496,7 +496,7 @@ app.controller('discover', function ($scope, config, courier, $route, $window, N

// we have a vis, just modify the aggs
if ($scope.vis) {
const visState = $scope.vis.getState();
const visState = $scope.vis.getEnabledState();
visState.aggs = visStateAggs;

$scope.vis.setState(visState);
Expand Down
2 changes: 2 additions & 0 deletions src/plugins/kibana/public/settings/styles/main.less
Original file line number Diff line number Diff line change
Expand Up @@ -201,3 +201,5 @@ kbn-settings-indices {
.kbn-settings-indices-create {
.time-and-pattern > div {}
}

@import "~ui/dragula/gu-dragula.less";
121 changes: 121 additions & 0 deletions src/plugins/kibana/public/visualize/editor/__tests__/draggable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import angular from 'angular';
import sinon from 'sinon';
import expect from 'expect.js';
import ngMock from 'ng_mock';

let init;
let $rootScope;
let $compile;

describe('draggable_* directives', function () {

beforeEach(ngMock.module('kibana'));
beforeEach(ngMock.inject(function ($injector) {
$rootScope = $injector.get('$rootScope');
$compile = $injector.get('$compile');
init = function init(markup = '') {
const $parentScope = $rootScope.$new();
$parentScope.items = [
{ name: 'item_1' },
{ name: 'item_2' },
{ name: 'item_3' }
];

// create the markup
const $elem = angular.element(`<div draggable-container="items">`);
$elem.html(markup);

// compile the directive
$compile($elem)($parentScope);
$parentScope.$apply();

const $scope = $elem.scope();

return { $parentScope, $scope, $elem };
};
}));

describe('draggable_container directive', function () {
it('should expose the drake', function () {
const { $scope } = init();
expect($scope.drake).to.be.an(Object);
});

it('should expose the controller', function () {
const { $scope } = init();
expect($scope.draggableContainerCtrl).to.be.an(Object);
});

it('should pull item list from directive attribute', function () {
const { $scope, $parentScope } = init();
expect($scope.draggableContainerCtrl.getList()).to.eql($parentScope.items);
});

it('should not be able to move extraneous DOM elements', function () {
const bare = angular.element(`<div>`);
const { $scope } = init();
expect($scope.drake.canMove(bare[0])).to.eql(false);
});

it('should not be able to move non-[draggable-item] elements', function () {
const bare = angular.element(`<div>`);
const { $scope, $elem } = init();
$elem.append(bare);
expect($scope.drake.canMove(bare[0])).to.eql(false);
});

it('shouldn\'t be able to move extraneous [draggable-item] elements', function () {
const anotherParent = angular.element(`<div draggable-container="items">`);
const item = angular.element(`<div draggable-item="items[0]">`);
const scope = $rootScope.$new();
anotherParent.append(item);
$compile(anotherParent)(scope);
$compile(item)(scope);
scope.$apply();
const { $scope } = init();
expect($scope.drake.canMove(item[0])).to.eql(false);
});

it('shouldn\'t be able to move [draggable-item] if it has a handle', function () {
const { $scope, $elem } = init(`
<div draggable-item="items[0]">
<div draggable-handle></div>
</div>
`);
const item = $elem.find(`[draggable-item]`);
expect($scope.drake.canMove(item[0])).to.eql(false);
});

it('should be able to move [draggable-item] by its handle', function () {
const { $scope, $elem } = init(`
<div draggable-item="items[0]">
<div draggable-handle></div>
</div>
`);
const handle = $elem.find(`[draggable-handle]`);
expect($scope.drake.canMove(handle[0])).to.eql(true);
});
});

describe('draggable_item', function () {
it('should be required to be a child to [draggable-container]', function () {
const item = angular.element(`<div draggable-item="items[0]">`);
const scope = $rootScope.$new();
expect(() => {
$compile(item)(scope);
scope.$apply();
}).to.throwException(/controller(.+)draggableContainer(.+)required/i);
});
});

describe('draggable_handle', function () {
it('should be required to be a child to [draggable-item]', function () {
const handle = angular.element(`<div draggable-handle>`);
const scope = $rootScope.$new();
expect(() => {
$compile(handle)(scope);
scope.$apply();
}).to.throwException(/controller(.+)draggableItem(.+)required/i);
});
});
});
41 changes: 26 additions & 15 deletions src/plugins/kibana/public/visualize/editor/agg.html
Original file line number Diff line number Diff line change
Expand Up @@ -27,30 +27,40 @@

<!-- controls !!!actually disabling buttons will break tooltips¡¡¡ -->
<div class="vis-editor-agg-header-controls btn-group">
<!-- up button -->
<!-- disable aggregation -->
<button
aria-label="Increase Priority"
ng-if="stats.count > 1"
ng-class="{ disabled: $first }"
ng-click="moveUp(agg)"
tooltip="Increase Priority"
ng-if="agg.enabled && canRemove(agg)"
ng-click="agg.enabled = false"
aria-label="Disable aggregation"
tooltip="Disable aggregation"
tooltip-append-to-body="true"
type="button"
class="btn btn-xs">
<i aria-hidden="true" class="fa fa-toggle-on"></i>
</button>

<!-- enable aggregation -->
<button
ng-if="!agg.enabled"
ng-click="agg.enabled = true"
aria-label="Enable aggregation"
tooltip="Enable aggregation"
tooltip-append-to-body="true"
type="button"
class="btn btn-xs btn-primary">
<i aria-hidden="true" class="fa fa-caret-up"></i>
class="btn btn-xs">
<i aria-hidden="true" class="fa fa-toggle-off"></i>
</button>

<!-- down button -->
<!-- drag handle -->
<button
aria-label="Decrease Priority"
draggable-handle
aria-label="Modify Priority by Dragging"
ng-if="stats.count > 1"
ng-class="{ disabled: $last }"
ng-click="moveDown(agg)"
tooltip="Decrease Priority"
tooltip="Modify Priority by Dragging"
tooltip-append-to-body="true"
type="button"
class="btn btn-xs btn-primary">
<i aria-hidden="true" class="fa fa-caret-down"></i>
class="btn btn-xs">
<i aria-hidden="true" class="fa fa-arrows-v"></i>
</button>

<!-- remove button -->
Expand Down Expand Up @@ -79,5 +89,6 @@

<vis-editor-agg-add
ng-if="$index + 1 === stats.count"
ng-hide="dragging"
class="vis-editor-agg-add vis-editor-agg-add-subagg">
</vis-editor-agg-add>
17 changes: 10 additions & 7 deletions src/plugins/kibana/public/visualize/editor/agg.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,16 @@ uiModules
return label ? label : '';
};

function move(below, agg) {
_.move($scope.vis.aggs, agg, below, function (otherAgg) {
return otherAgg.schema.group === agg.schema.group;
});
}
$scope.moveUp = _.partial(move, false);
$scope.moveDown = _.partial(move, true);
$scope.$on('drag-start', e => {
$scope.editorWasOpen = $scope.editorOpen;
$scope.editorOpen = false;
$scope.$emit('agg-drag-start', $scope.agg);
});

$scope.$on('drag-end', e => {
$scope.editorOpen = $scope.editorWasOpen;
$scope.$emit('agg-drag-end', $scope.agg);
});

$scope.remove = function (agg) {
const aggs = $scope.vis.aggs;
Expand Down
4 changes: 2 additions & 2 deletions src/plugins/kibana/public/visualize/editor/agg_group.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
{{ groupName }}
</div>

<div class="vis-editor-agg-group" ng-class="groupName">
<div ng-class="groupName" draggable-container="vis.aggs" class="vis-editor-agg-group">
<!-- wrapper needed for nesting-indicator -->
<div ng-repeat="agg in group" class="vis-editor-agg-wrapper">
<div ng-repeat="agg in group" draggable-item="agg" class="vis-editor-agg-wrapper">
<!-- agg.html - controls for aggregation -->
<ng-form vis-editor-agg name="aggForm" class="vis-editor-agg"></ng-form>
</div>
Expand Down
3 changes: 3 additions & 0 deletions src/plugins/kibana/public/visualize/editor/agg_group.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ uiModules
if (count < schema.max) return true;
});
});

$scope.$on('agg-drag-start', e => $scope.dragging = true);
$scope.$on('agg-drag-end', e => $scope.dragging = false);
}
};

Expand Down
88 changes: 88 additions & 0 deletions src/plugins/kibana/public/visualize/editor/draggable_container.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import _ from 'lodash';
import $ from 'jquery';
import dragula from 'dragula';
import uiModules from 'ui/modules';

uiModules
.get('app/visualize')
.directive('draggableContainer', function () {

return {
restrict: 'A',
scope: true,
controllerAs: 'draggableContainerCtrl',
controller($scope, $attrs, $parse) {
this.getList = () => $parse($attrs.draggableContainer)($scope);
},
link($scope, $el, attr) {
const drake = dragula({
containers: $el.toArray(),
moves(el, source, handle) {
const itemScope = $(el).scope();
if (!('draggableItemCtrl' in itemScope)) {
return; // only [draggable-item] is draggable
}
return itemScope.draggableItemCtrl.moves(handle);
}
});

const drakeEvents = [
'cancel',
'cloned',
'drag',
'dragend',
'drop',
'out',
'over',
'remove',
'shadow'
];
const prettifiedDrakeEvents = {
drag: 'start',
dragend: 'end'
};

drakeEvents.forEach(type => {
drake.on(type, (el, ...args) => forwardEvent(type, el, ...args));
});
drake.on('drag', markDragging(true));
drake.on('dragend', markDragging(false));
drake.on('drop', drop);
$scope.$on('$destroy', drake.destroy);
$scope.drake = drake;

function markDragging(isDragging) {
return el => {
const scope = $(el).scope();
scope.isDragging = isDragging;
scope.$apply();
};
}

function forwardEvent(type, el, ...args) {
const name = `drag-${prettifiedDrakeEvents[type] || type}`;
const scope = $(el).scope();
scope.$broadcast(name, el, ...args);
}

function drop(el, target, source, sibling) {
const list = $scope.draggableContainerCtrl.getList();
const itemScope = $(el).scope();
const item = itemScope.draggableItemCtrl.getItem();
const toIndex = getSiblingItemIndex(list, sibling);
_.move(list, item, toIndex);
}

function getSiblingItemIndex(list, sibling) {
if (!sibling) { // means the item was dropped at the end of the list
return list.length - 1;
}
const siblingScope = $(sibling).scope();
const siblingItem = siblingScope.draggableItemCtrl.getItem();
const siblingIndex = list.indexOf(siblingItem);
return siblingIndex;
}
}
};

});
14 changes: 14 additions & 0 deletions src/plugins/kibana/public/visualize/editor/draggable_handle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import uiModules from 'ui/modules';

uiModules
.get('app/visualize')
.directive('draggableHandle', function () {
return {
restrict: 'A',
require: '^draggableItem',
link($scope, $el, attr, ctrl) {
ctrl.registerHandle($el);
$el.addClass('gu-handle');
}
};
});
29 changes: 29 additions & 0 deletions src/plugins/kibana/public/visualize/editor/draggable_item.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import $ from 'jquery';
import uiModules from 'ui/modules';

uiModules
.get('app/visualize')
.directive('draggableItem', function () {
return {
restrict: 'A',
require: '^draggableContainer',
scope: true,
controllerAs: 'draggableItemCtrl',
controller($scope, $attrs, $parse) {
const dragHandles = $();

this.getItem = () => $parse($attrs.draggableItem)($scope);
this.registerHandle = $el => {
dragHandles.push(...$el);
};
this.moves = handle => {
const $handle = $(handle);
const $anywhereInParentChain = $handle.parents().addBack();
const movable = dragHandles.is($anywhereInParentChain);
return movable;
};
},
link($scope, $el, attr) {
}
};
});
Loading

0 comments on commit 1cf2979

Please sign in to comment.