Skip to content

Commit

Permalink
Dashbaord App updates
Browse files Browse the repository at this point in the history
 - Loads visualizations
 - Uses AppState
 - Reflows the grid when the browser resizes (buggy, but good enough)

closes #60
closes #37
  • Loading branch information
Spencer Alger committed Apr 25, 2014
1 parent 3eee3a5 commit 6a30db8
Show file tree
Hide file tree
Showing 25 changed files with 524 additions and 224 deletions.
206 changes: 147 additions & 59 deletions src/kibana/apps/dashboard/directives/grid.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,89 +11,177 @@ define(function (require) {
return {
restrict: 'A',
scope : {
grid: '=',
control: '='
panels: '='
},
link: function ($scope, elem) {
var width = elem.width();
link: function ($scope, $el) {
var $container = $el.parent();
var $window = $(window);
var $body = $(document.body);
var gridster; // defined in init()

$scope.control = $scope.control || {};
// number of columns to render
var COLS = 12;
// number of pixed between each column/row
var SPACER = 10;

$scope.$watch('grid', function (grid) {
if (grid === void 0) return; // wait until we have something
var init = function () {
$el.addClass('gridster');

init();
$scope.control.unserializeGrid(grid);
});

var init = _.once(function () {
elem.addClass('gridster');

elem.on('click', 'li i.remove', function (event) {
var target = event.target.parentNode.parentNode;
gridster.remove_widget(target);
});

gridster = elem.gridster({
gridster = $el.gridster({
max_cols: COLS,
min_cols: COLS,
autogenerate_stylesheet: false,
widget_margins: [5, 5],
widget_base_dimensions: [((width - 100) / 12), 100],
min_cols: 12,
max_cols: 12,
resize: {
enabled: true
enabled: true,
stop: readGridsterChangeHandler
},
serialize_params: function (el, wgd) {
return {
col: wgd.col,
row: wgd.row,
size_x: wgd.size_x,
size_y: wgd.size_y,
params: el.data('params')
};
draggable: {
stop: readGridsterChangeHandler
}
}).data('gridster');
gridster.generate_stylesheet({namespace: '.gridster'});
});

$scope.control.clearGrid = function (cb) {
gridster.remove_all_widgets();
};
layout();
var safeLayout = _.debounce(layout, 200);
$window.on('resize', safeLayout);

$scope.$watchCollection('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
$scope.panels.forEach(makePanelSerializeable);

$scope.control.unserializeGrid = function (grid) {
if (typeof grid === 'string') {
grid = JSON.stringify(grid);
}
gridster.remove_all_widgets();
grid.forEach(function (panel) {
$scope.control.addWidget(panel);
// alert interested parties that we have finished processing changes to the panels
if (added.length || removed.length) $scope.$root.$broadcast('change:vis');
});

$scope.$on('$destroy', function () {
$window.off('destroy', safeLayout);

if (!gridster) return;
gridster.$widgets.each(function (el) {
removePanel(getPanelFor(el));
});
});
};

$scope.control.serializeGrid = function () {
return gridster.serialize();
// return the panel object for an element.
//
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// ALWAYS CALL makePanelSerializeable AFTER YOU ARE DONE WITH IT
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
var getPanelFor = function (el) {
var $el = el.jquery ? el : $(el);
var panel = $el.data('panel');

panel.$el = $el;
panel.$scope = $el.data('$scope');

return panel;
};

$scope.control.addWidget = function (panel) {
panel = 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)
var makePanelSerializeable = function (panel) {
delete panel.$el;
delete panel.$scope;
};

// tell gridster to remove the panel, and cleanup our metadata
var removePanel = function (panel) {
gridster.remove_widget(panel.$el);
panel.$scope.$destroy();

panel.$el.removeData('panel');
panel.$el.removeData('panel$scope');
};

// tell gridster to add the panel, and create additional meatadata like $scope
var addPanel = function (panel) {
_.defaults(panel, {
size_x: 3,
size_y: 2,
params: {
type: 'new'
}
size_y: 2
});

// ignore panels that don't have vis id's
if (!panel.visId) throw new Error('missing visId 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 in the element's data
panel.$el.data('panel', panel);
};

// ensure that the panel object has the latest size/pos info
var refreshPanelStats = function (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
var readGridsterChangeHandler = function (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);
makePanelSerializeable(panel);
$scope.$root.$broadcast('change:vis');
});
var wgd = gridster.add_widget('<li />',
panel.size_x, panel.size_y, panel.col, panel.row);
};

// 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
var layout = function () {
// pixels used by all of the spacers
var spacerSize = SPACER * (COLS - 1);
// horizontal padding on the container
var horizPadding = parseInt($container.css('paddingLeft'), 10) + parseInt($container.css('paddingRight'), 10);

// https://github.com/gcphost/gridster-responsive/blob/97fe43d4b312b409696b1d702e1afb6fbd3bba71/jquery.gridster.js#L1208-L1235

var template = '<dashboard-panel params=\'' + JSON.stringify(panel.params) + '\'></dashboard-panel>';
var g = gridster;

var element = $compile(template)($scope);
wgd.append(element);
wgd.data('params', panel.params);
g.options.widget_margins = [SPACER / 2, SPACER / 2];
g.options.widget_base_dimensions = [($container.width() - spacerSize - horizPadding) / 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();
g.set_dom_grid_height();
g.drag_api.set_limits(COLS * g.min_widget_width);
};

init();
}
};
});
Expand Down
30 changes: 23 additions & 7 deletions src/kibana/apps/dashboard/directives/panel.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,31 @@
define(function (require) {
var app = require('modules').get('app/dashboard');
var _ = require('lodash');

app.directive('dashboardPanel', function () {
require('apps/visualize/directives/visualize');

app.directive('dashboardPanel', function (savedVisualizations, Notifier) {
var notify = new Notifier();
return {
restrict: 'E',
scope: {
params: '@'
},
compile: function (elem, attrs) {
var params = JSON.parse(attrs.params);
elem.html(params.type + '<i class="link pull-right fa fa-times remove" />');
template: require('text!../partials/panel.html'),
requires: '^dashboardGrid',
link: function ($scope, $el) {
// receives panel object from the dashboard grid directive
$scope.$watch('visId', function (visId) {
delete $scope.vis;
if (!$scope.panel.visId) return;

savedVisualizations.get($scope.panel.visId)
.then(function (vis) {
$scope.vis = vis;
})
.catch(notify.fatal);
});

$scope.remove = function () {
_.pull($scope.panels, $scope.panel);
};
}
};
});
Expand Down
11 changes: 6 additions & 5 deletions src/kibana/apps/dashboard/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,17 @@
</div>

<ul class="nav navbar-nav pull-left">
<li><a class="navbar-link" ng-click="openSave()"><i class="fa fa-save"></i></a></li>
<li><a class="navbar-link" ng-click="openSave();"><i class="fa fa-save"></i></a></li>
<li><a class="navbar-link" ng-click="openLoad()"><i class="fa fa-folder-open"></i></a></li>
<li><a class="navbar-link" ng-click="gridControl.addWidget()"><i class="fa fa-plus"></i></a></li>
<li><a class="navbar-link" ng-click="openAdd()"><i class="fa fa-plus"></i></a></li>
<li><a class="navbar-link" ng-click="refresh()"><i class="fa fa-refresh"></i></a></li>
</ul>
</div>
</nav>
<config config-template="configTemplate" config-object="configurable" config-close="configClose" config-submit="configSubmit"></config>
<config config-template="configTemplate" config-object="opts"></config>

<div class="container-default">
<ul dashboard-grid grid="panels" control="gridControl"></ul>
<div class="gridster-container">
<ul dashboard-grid panels="$state.panels"></ul>
</div>

</div>
Loading

0 comments on commit 6a30db8

Please sign in to comment.