UNPKG

@spalger/kibana

Version:

Kibana is an open source (Apache Licensed), browser based analytics and search dashboard for Elasticsearch. Kibana is a snap to setup and start using. Kibana strives to be easy to get started with, while also being flexible and powerful, just like Elastic

234 lines (189 loc) 8.31 kB
define(function (require) { var _ = require('lodash'); var $ = require('jquery'); require('gridster'); var app = require('ui/modules').get('app/dashboard'); app.directive('dashboardGrid', function ($compile, Notifier) { return { restrict: 'E', require: '^dashboardApp', // must inherit from the dashboardApp link: function ($scope, $el) { var notify = new Notifier(); var $container = $el; $el = $('<ul>').appendTo($container); var $window = $(window); var $body = $(document.body); // appState from controller var $state = $scope.state; var gridster; // defined in init() // number of columns to render var COLS = 12; // number of pixed between each column/row var SPACER = 10; // pixels used by all of the spacers (gridster puts have a spacer on the ends) var spacerSize = SPACER * COLS; // debounced layout function is safe to call as much as possible var safeLayout = _.debounce(layout, 200); function init() { $el.addClass('gridster'); gridster = $el.gridster({ max_cols: COLS, min_cols: COLS, autogenerate_stylesheet: false, resize: { enabled: true, stop: readGridsterChangeHandler }, draggable: { handle: '.panel-heading, .panel-title', stop: readGridsterChangeHandler } }).data('gridster'); // This is necessary to enable text selection within gridster elements // http://stackoverflow.com/questions/21561027/text-not-selectable-from-editable-div-which-is-draggable $el.on('mousedown', function () { gridster.disable().disable_resize(); }).on('mouseup', function () { gridster.enable().enable_resize(); }); $scope.$watchCollection('state.panels', function (panels) { var currentPanels = gridster.$widgets.toArray().map(function (el) { return getPanelFor(el); }); // panels that are now missing from the panels array var removed = _.difference(currentPanels, panels); // panels that have been added var added = _.difference(panels, currentPanels); if (removed.length) removed.forEach(removePanel); if (added.length) added.forEach(addPanel); // ensure that every panel can be serialized now that we are done $state.panels.forEach(makePanelSerializeable); // alert interested parties that we have finished processing changes to the panels // TODO: change this from event based to calling a method on dashboardApp if (added.length || removed.length) $scope.$root.$broadcast('change:vis'); }); $scope.$on('$destroy', function () { $window.off('resize', safeLayout); if (!gridster) return; gridster.$widgets.each(function (i, el) { var panel = getPanelFor(el); removePanel(panel); // stop any animations panel.$el.stop(); // not that we will, but lets be safe makePanelSerializeable(panel); }); }); safeLayout(); $window.on('resize', safeLayout); $scope.$on('ready:vis', safeLayout); } // return the panel object for an element. // // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // ALWAYS CALL makePanelSerializeable AFTER YOU ARE DONE WITH IT // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! function getPanelFor(el) { var $panel = el.jquery ? el : $(el); var panel = $panel.data('panel'); panel.$el = $panel; panel.$scope = $panel.data('$scope'); return panel; } // since the $el and $scope are circular structures, they need to be // removed from panel before it can be serialized (we also wouldn't // want them to show up in the url) function makePanelSerializeable(panel) { delete panel.$el; delete panel.$scope; } // tell gridster to remove the panel, and cleanup our metadata function removePanel(panel) { // remove from grister 'silently' (don't reorganize after) gridster.remove_widget(panel.$el); // destroy the scope panel.$scope.$destroy(); panel.$el.removeData('panel'); panel.$el.removeData('$scope'); } // tell gridster to add the panel, and create additional meatadata like $scope function addPanel(panel) { _.defaults(panel, { size_x: 3, size_y: 2 }); // ignore panels that don't have vis id's if (!panel.id) { // In the interest of backwards compat if (panel.visId) { panel.id = panel.visId; panel.type = 'visualization'; delete panel.visId; } else { throw new Error('missing object id on panel'); } } panel.$scope = $scope.$new(); panel.$scope.panel = panel; panel.$el = $compile('<li><dashboard-panel></li>')(panel.$scope); // tell gridster to use the widget gridster.add_widget(panel.$el, panel.size_x, panel.size_y, panel.col, panel.row); // update size/col/etc. refreshPanelStats(panel); // stash the panel and it's scope in the element's data panel.$el.data('panel', panel); panel.$el.data('$scope', panel.$scope); } // ensure that the panel object has the latest size/pos info function refreshPanelStats(panel) { var data = panel.$el.coords().grid; panel.size_x = data.size_x; panel.size_y = data.size_y; panel.col = data.col; panel.row = data.row; } // when gridster tell us it made a change, update each of the panel objects function readGridsterChangeHandler(e, ui, $widget) { // ensure that our panel objects keep their size in sync gridster.$widgets.each(function (i, el) { var panel = getPanelFor(el); refreshPanelStats(panel); panel.$scope.$broadcast('resize'); makePanelSerializeable(panel); $scope.$root.$broadcast('change:vis'); }); } // calculate the position and sizing of the gridster el, and the columns within it // then tell gridster to "reflow" -- which is definitely not supported. // we may need to consider using a different library function reflowGridster() { // https://github.com/gcphost/gridster-responsive/blob/97fe43d4b312b409696b1d702e1afb6fbd3bba71/jquery.gridster.js#L1208-L1235 var g = gridster; g.options.widget_margins = [SPACER / 2, SPACER / 2]; g.options.widget_base_dimensions = [($container.width() - spacerSize) / COLS, 100]; g.min_widget_width = (g.options.widget_margins[0] * 2) + g.options.widget_base_dimensions[0]; g.min_widget_height = (g.options.widget_margins[1] * 2) + g.options.widget_base_dimensions[1]; // var serializedGrid = g.serialize(); g.$widgets.each(function (i, widget) { g.resize_widget($(widget)); }); g.generate_grid_and_stylesheet(); g.generate_stylesheet({ namespace: '.gridster' }); g.get_widgets_from_DOM(); // We can't call this method if the gridmap is empty. This was found // when the user double clicked the "New Dashboard" icon. See // https://github.com/elastic/kibana4/issues/390 if (gridster.gridmap.length > 0) g.set_dom_grid_height(); g.drag_api.set_limits(COLS * g.min_widget_width); } function layout() { var complete = notify.event('reflow dashboard'); reflowGridster(); readGridsterChangeHandler(); complete(); } init(); } }; }); });