Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Drag aggregations to sort instead of having up/down arrows. #6566

Merged
merged 15 commits into from
May 27, 2016
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 () {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the future, just use string templates instead of escaping the single quote. Or should not.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. Bad habit!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't bother changing it. That was just a note for posterity's sake.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👌

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);
});
});
});
40 changes: 25 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
15 changes: 8 additions & 7 deletions src/plugins/kibana/public/visualize/editor/agg.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,14 @@ 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.$on('drag-end', e => {
$scope.editorOpen = $scope.editorWasOpen;
});

$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
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