Skip to content

Commit

Permalink
feat(grid): use ui-grid-datepicker as date editor
Browse files Browse the repository at this point in the history
This commit uses a pre-built ui-datepicker as the date editor for the
ui-grid.  This allows the grid to edit dates with a datepicker.
  • Loading branch information
Jonathan Niles committed Aug 4, 2016
1 parent 76046bd commit 9a922b6
Show file tree
Hide file tree
Showing 7 changed files with 245 additions and 15 deletions.
7 changes: 3 additions & 4 deletions client/src/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,15 @@ var bhima = angular.module('bhima', [
'bhima.components', 'bhima.routes', 'ui.bootstrap', 'pascalprecht.translate',
'ngStorage', 'chart.js', 'tmh.dynamicLocale', 'ngFileUpload', 'ui.grid',
'ui.grid.selection', 'ui.grid.autoResize', 'ui.grid.resizeColumns',
'angularMoment', 'ngMessages', 'ui.grid.pagination', 'ui.grid.moveColumns',
'ui.grid.treeView', 'ui.grid.grouping', 'growlNotifications', 'ngAnimate',
'ngSanitize'
'ui.grid.edit', 'ui.grid.grouping', 'ui.grid.treeView',
'ui.grid.pagination', 'ui.grid.moveColumns', 'angularMoment', 'ngMessages',
'growlNotifications', 'ngAnimate', 'ngSanitize'
]);

function bhimaConfig($stateProvider, $urlRouterProvider, $urlMatcherFactoryProvider) {

// allow trailing slashes in routes
$urlMatcherFactoryProvider.strictMode(false);
/* misc routes */

$stateProvider
.state('index', {
Expand Down
172 changes: 172 additions & 0 deletions client/src/js/directives/bhGridDateEditor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
/** forked from https://github.com/Joiler/ui-grid-edit-datepicker */

var app = angular.module('ui.grid.edit');

app.directive('uiGridEditDatepicker', ['$timeout', '$document', 'uiGridConstants', 'uiGridEditConstants', function($timeout, $document, uiGridConstants, uiGridEditConstants) {
return {
template: function(element, attrs) {
var html = '<div class="datepicker-wrapper"><input class="form-control" type="text" uib-datepicker-popup datepicker-append-to-body="true" show-button-bar="false" is-open="isOpen" ng-model="datePickerValue" ng-change="changeDate($event)"/></div>';
return html;
},
require: ['?^uiGrid', '?^uiGridRenderContainer'],
scope: true,
compile: function() {
return {
pre: function($scope, $elm, $attrs) {

},
post: function($scope, $elm, $attrs, controllers) {
var setCorrectPosition = function() {
var gridElement = $('.ui-grid-viewport');
var gridPosition = {
width: gridElement.outerWidth(),
height: gridElement.outerHeight(),
offset: gridElement.offset()
};

var cellElement = $($elm);
var cellPosition = {
width: cellElement.outerWidth(),
height: cellElement.outerHeight(),
offset: cellElement.offset()
};

var datepickerElement = $('body > .dropdown-menu');
var datepickerPosition = {
width: datepickerElement.outerWidth(),
height: datepickerElement.outerHeight()
};

var setCorrectTopPositionInGrid = function() {
var topPosition;
var freePixelsOnBottom = gridPosition.height - (cellPosition.offset.top - gridPosition.offset.top) - cellPosition.height;
var freePixelsOnTop = gridPosition.height - freePixelsOnBottom - cellPosition.height;
var requiredPixels = (datepickerPosition.height - cellPosition.height) / 2;
if (freePixelsOnBottom >= requiredPixels && freePixelsOnTop >= requiredPixels) {
topPosition = cellPosition.offset.top - requiredPixels + 10;
} else if (freePixelsOnBottom >= requiredPixels && freePixelsOnTop < requiredPixels) {
topPosition = cellPosition.offset.top - freePixelsOnTop + 10;
} else {
topPosition = gridPosition.height - datepickerPosition.height + gridPosition.offset.top - 20;
}
return topPosition;
};

var setCorrectTopPositionInWindow = function() {
var topPosition;
var windowHeight = window.innerHeight - 10;

var freePixelsOnBottom = windowHeight - cellPosition.offset.top;
var freePixelsOnTop = windowHeight - freePixelsOnBottom - cellPosition.height;
var requiredPixels = (datepickerPosition.height - cellPosition.height) / 2;
if (freePixelsOnBottom >= requiredPixels && freePixelsOnTop >= requiredPixels) {
topPosition = cellPosition.offset.top - requiredPixels;
} else if (freePixelsOnBottom >= requiredPixels && freePixelsOnTop < requiredPixels) {
topPosition = cellPosition.offset.top - freePixelsOnTop;
} else {
topPosition = windowHeight - datepickerPosition.height - 10;
}
return topPosition;
};


var newOffsetValues = {};

var isFreeOnRight = (gridPosition.width - (cellPosition.offset.left - gridPosition.offset.left) - cellPosition.width) > datepickerPosition.width;
if (isFreeOnRight) {
newOffsetValues.left = cellPosition.offset.left + cellPosition.width;
} else {
newOffsetValues.left = cellPosition.offset.left - datepickerPosition.width;
}

if (datepickerPosition.height < gridPosition.height) {
newOffsetValues.top = setCorrectTopPositionInGrid();
} else {
newOffsetValues.top = setCorrectTopPositionInWindow();
}

datepickerElement.offset(newOffsetValues);
datepickerElement.css('visibility', 'visible');
};

$timeout(function() {
setCorrectPosition();
}, 0);

$scope.datePickerValue = new Date($scope.row.entity[$scope.col.field]);
$scope.isOpen = true;
var uiGridCtrl = controllers[0];
var renderContainerCtrl = controllers[1];

var onWindowClick = function (evt) {
var classNamed = angular.element(evt.target).attr('class');
if (classNamed) {
var inDatepicker = (classNamed.indexOf('datepicker-calendar') > -1);
if (!inDatepicker && evt.target.nodeName !== 'INPUT') {
$scope.stopEdit(evt);
}
}
else {
$scope.stopEdit(evt);
}
};

var onCellClick = function (evt) {
angular.element(document.querySelectorAll('.ui-grid-cell-contents')).off('click', onCellClick);
$scope.stopEdit(evt);
};

$scope.changeDate = function (evt) {
$scope.row.entity[$scope.col.field] = $scope.datePickerValue;
$scope.stopEdit(evt);
};

$scope.$on(uiGridEditConstants.events.BEGIN_CELL_EDIT, function () {
if (uiGridCtrl.grid.api.cellNav) {
uiGridCtrl.grid.api.cellNav.on.navigate($scope, function (newRowCol, oldRowCol) {
$scope.stopEdit();
});
} else {
angular.element(document.querySelectorAll('.ui-grid-cell-contents')).on('click', onCellClick);
}
angular.element(window).on('click', onWindowClick);
});

$scope.$on('$destroy', function () {
angular.element(window).off('click', onWindowClick);
$('body > .dropdown-menu').remove();
});

$scope.stopEdit = function(evt) {
$scope.$emit(uiGridEditConstants.events.END_CELL_EDIT);
};

$elm.on('keydown', function(evt) {
switch (evt.keyCode) {
case uiGridConstants.keymap.ESC:
evt.stopPropagation();
$scope.$emit(uiGridEditConstants.events.CANCEL_CELL_EDIT);
break;
}
if (uiGridCtrl && uiGridCtrl.grid.api.cellNav) {
evt.uiGridTargetRenderContainerId = renderContainerCtrl.containerId;
if (uiGridCtrl.cellNav.handleKeyDown(evt) !== null) {
$scope.stopEdit(evt);
}
} else {
switch (evt.keyCode) {
case uiGridConstants.keymap.ENTER:
case uiGridConstants.keymap.TAB:
evt.stopPropagation();
evt.preventDefault();
$scope.stopEdit(evt);
break;
}
}
return true;
});
}
};
}
};
}]);
42 changes: 42 additions & 0 deletions client/src/js/services/grid/GridEditors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
angular.module('bhima.services')
.service('GridEditorService', GridEditorService);

GridEditorService.$inject = ['util'];

function GridEditorService(util) {

/**
* @constructor
*/
function GridEditors(gridOptions) {

this.gridOptions = gridOptions;
this.authenticated = false;

util.after(gridOptions, 'onRegisterApi', function onRegisterApi(api) {
this.api = api;

this.api.edit.on.beginCellEdit(null, function beginCellEdit() {
if (gridOptions.authenticateEdits && !this.authenticated) {
this.requestUserAuthentication();
}
}.bind(this));

}.bind(this));
}


/**
* @method requestUserAuthentication
*
* @description
* This method will use the user authentication modal to authenticate a
* user's edit session. It is currently unimplemented.
*/
GridEditors.prototype.requestUserAuthentication = function requestUserAuthentication() {
this.authenticated = true;
// noop()
};

return GridEditors;
}
1 change: 1 addition & 0 deletions client/src/partials/journal/journal.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
ui-grid-resize-columns
ui-grid-move-columns
ui-grid-selection
ui-grid-edit
ui-grid-grouping>

<bh-grid-loading-indicator
Expand Down
31 changes: 21 additions & 10 deletions client/src/partials/journal/journal.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ angular.module('bhima.controllers')
JournalController.$inject = [
'JournalService', 'GridSortingService', 'GridGroupingService',
'GridFilteringService', 'GridColumnService', 'JournalConfigService',
'SessionService', 'NotifyService', 'TransactionService'
'SessionService', 'NotifyService', 'TransactionService', 'GridEditorService'
];

/**
Expand All @@ -29,11 +29,11 @@ JournalController.$inject = [
*
* @module bhima/controllers/JournalController
*/
function JournalController(Journal, Sorting, Grouping, Filtering, Columns, Config, Session, Notify, Transactions) {
function JournalController(Journal, Sorting, Grouping, Filtering, Columns, Config, Session, Notify, Transactions, Editors) {
var vm = this;

// Journal utilities
var sorting, grouping, filtering, columnConfig, transactions;
var sorting, grouping, filtering, columnConfig, transactions, editors;

/** @const the cache alias for this controller */
var cacheKey = 'Journal';
Expand All @@ -44,6 +44,7 @@ function JournalController(Journal, Sorting, Grouping, Filtering, Columns, Confi
// options, it is also used by the grid to expose the API
vm.gridOptions = {
enableColumnMenus : false,
authenticateEdits : true,
appScopeProvider : vm
};

Expand All @@ -54,6 +55,7 @@ function JournalController(Journal, Sorting, Grouping, Filtering, Columns, Confi
grouping = new Grouping(vm.gridOptions);
columnConfig = new Columns(vm.gridOptions, cacheKey);
transactions = new Transactions(vm.gridOptions);
editors = new Editors(vm.gridOptions);

vm.loading = true;
Journal.read()
Expand Down Expand Up @@ -92,31 +94,40 @@ function JournalController(Journal, Sorting, Grouping, Filtering, Columns, Confi
* other columns. This can be avoided by setting default sort and group.
*/
var columns = [
{ field : 'uuid', displayName : 'TABLE.COLUMNS.ID', headerCellFilter: 'translate', visible: false },
{ field : 'project_name', displayName : 'TABLE.COLUMNS.PROJECT', headerCellFilter: 'translate', visible: false },
{ field : 'period_end', displayName : 'TABLE.COLUMNS.PERIOD', headerCellFilter: 'translate' , cellTemplate : 'partials/templates/bhPeriod.tmpl.html', visible: false },
{ field : 'trans_date', displayName : 'TABLE.COLUMNS.DATE', headerCellFilter: 'translate' , cellFilter : 'date:"mediumDate"', filter : { condition : filtering.byDate } },
{ field : 'uuid', displayName : 'TABLE.COLUMNS.ID', headerCellFilter: 'translate', visible: false, enableCellEdit: false},
{ field : 'project_name', displayName : 'TABLE.COLUMNS.PROJECT', headerCellFilter: 'translate', visible: false, enableCellEdit: false },
{ field : 'period_end', displayName : 'TABLE.COLUMNS.PERIOD', headerCellFilter: 'translate' , cellTemplate : 'partials/templates/bhPeriod.tmpl.html', visible: false, enableCellEdit: false},
{
field : 'trans_date',
displayName : 'TABLE.COLUMNS.DATE',
headerCellFilter: 'translate',
cellFilter : 'date:"mediumDate"',
filter : { condition : filtering.byDate },
editableCellTemplate: 'partials/journal/templates/date.edit.html',
enableCellEdit: true
},
{ field : 'description', displayName : 'TABLE.COLUMNS.DESCRIPTION', headerCellFilter: 'translate' },
{ field : 'account_number', displayName : 'TABLE.COLUMNS.ACCOUNT', headerCellFilter: 'translate' },
{ field : 'debit_equiv', displayName : 'TABLE.COLUMNS.DEBIT', headerCellFilter: 'translate', cellTemplate : '/partials/journal/templates/debit_equiv.cell.html' },
{ field : 'credit_equiv', displayName : 'TABLE.COLUMNS.CREDIT', headerCellFilter: 'translate', cellTemplate : '/partials/journal/templates/credit_equiv.cell.html' },
{ field : 'trans_id',
displayName : 'TABLE.COLUMNS.TRANSACTION',
headerCellFilter: 'translate' ,
headerCellFilter: 'translate',
sortingAlgorithm : sorting.transactionIds,
sort : { priority : 0, direction : 'asc' },
enableCellEdit: false
},

// @todo this should be formatted as a currency icon vs. an ID
{ field : 'currency_id', displayName : 'TABLE.COLUMNS.CURRENCY', headerCellFilter: 'translate', visible: false },
{ field : 'currency_id', displayName : 'TABLE.COLUMNS.CURRENCY', headerCellFilter: 'translate', visible: false, enableCellEdit: false},

// @todo this should be formatted showing the debtor/creditor
{ field : 'entity_uuid', displayName : 'TABLE.COLUMNS.RECIPIENT', headerCellFilter: 'translate', visible: false },
{ field : 'entity_type', displayName : 'TABLE.COLUMNS.RECIPIENT_TYPE', headerCellFilter: 'translate', visible: false },

{ field : 'reference_uuid', displayName : 'TABLE.COLUMNS.REFERENCE', headerCellFilter: 'translate', visible: false },
{ field : 'record_uuid', displayName : 'TABLE.COLUMNS.RECORD', headerCellFilter: 'translate', visible: false },
{ field : 'user', displayName : 'TABLE.COLUMNS.RESPONSIBLE', headerCellFilter: 'translate', visible: false }
{ field : 'user', displayName : 'TABLE.COLUMNS.RESPONSIBLE', headerCellFilter: 'translate', visible: false, enableCellEdit: false }
];

vm.gridOptions.columnDefs = columns;
Expand Down
5 changes: 5 additions & 0 deletions client/src/partials/journal/templates/date.edit.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<div>
<form name="DateForm">
<div ui-grid-edit-datepicker ng-class="'colt' + col.uid"></div>
</form>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@
data-vals="{{col.isRowHeader}}"
role="{{col.isRowHeader ? 'rowheader' : 'gridcell'}}"
ng-style="row.entity.type_id === 9 ? {'text-decoration':'line-through'} : {'text-decoration':'none'}"
ui-grid-cell>
ui-grid-cell>
</div>

0 comments on commit 9a922b6

Please sign in to comment.