I'm creating an application that uses ui-grid which has to support IE 11. Localization worked fine, except when editing numerical cells (the decimal separator doesn't change).
I read that the way to solve this is creating a custom editor directive. I created a editableCellTemplate:
var customNumberTemplate = '<div><form name="inputForm"><input type="text" class="customNumber" custom-number ng-class="\'colt\' + col.uid" ng-model="MODEL_COL_FIELD" pattern="^[+-]?([0-9]+([,][0-9]*)?|[,][0-9]+)$"></form></div>';
Notice it has type text and a pattern to validate a real number, but with "," as the separator. I tried giving it type='number', however this doens't work, as it tries to run both my validator and the standard html-5 validator (can't have a ",").
For the directive I copied ui-grid's "uiGridEditor" and added two functions. The intent behind this was to when a user open's a cell, I convert it to a string, and when he closes it I convert it back to a float. This apparently isn't possible, the grid always returns me the value as a string, probably because the template's type is set to text (I tried editing the $elm's type to 'numeric' but that does't work). Here's the code, the two functions are on top:
var _initCustomNumberDirective = function () {
_angularController.directive('customNumber', ['gridUtil', 'uiGridConstants', 'uiGridEditConstants', '$timeout', 'uiGridEditService',
function (gridUtil, uiGridConstants, uiGridEditConstants, $timeout, uiGridEditService) {
return {
scope: true,
require: ['?^uiGrid', '?^uiGridRenderContainer', 'ngModel'],
compile: function () {
// START OF MY CODE
var beforeEdit = function ($scope, $elm) {
if ($elm.length > 0 && $elm[0].classList.contains('customNumber')) {
var modelField = $scope.row.getQualifiedColField($scope.col)
if (eval('!isNaN($scope.' + modelField + ')')) {
eval('$scope.' + modelField + '= $scope.' + modelField + '.toString().replace(".", ",")');
}
}
}
var editionComplete = function ($scope, $elm) {
if ($elm.length > 0 && $elm[0].classList.contains('customNumber')) {
$elm[0].value = $elm[0].value.replace(',', '.');
if (!isNaN($elm[0].value)) {
var modelField = $scope.row.getQualifiedColField($scope.col)
eval('$scope.' + modelField + ' = parseFloat($elm[0].value)');
}
}
}
// END OF MY CODE
return {
pre: function ($scope, $elm, $attrs) {
beforeEdit($scope, $elm); // MY CODE
},
post: function ($scope, $elm, $attrs, controllers) {
var uiGridCtrl, renderContainerCtrl, ngModel;
if (controllers[0]) { uiGridCtrl = controllers[0]; }
if (controllers[1]) { renderContainerCtrl = controllers[1]; }
if (controllers[2]) { ngModel = controllers[2]; }
//set focus at start of edit
$scope.$on(uiGridEditConstants.events.BEGIN_CELL_EDIT, function (evt, triggerEvent) {
$timeout(function () {
$elm[0].focus();
//only select text if it is not being replaced below in the cellNav viewPortKeyPress
if ($elm[0].select && ($scope.col.colDef.enableCellEditOnFocus || !(uiGridCtrl && uiGridCtrl.grid.api.cellNav))) {
$elm[0].select();
}
else {
//some browsers (Chrome) stupidly, imo, support the w3 standard that number, email, ...
//fields should not allow setSelectionRange. We ignore the error for those browsers
//https://www.w3.org/Bugs/Public/show_bug.cgi?id=24796
try {
$elm[0].setSelectionRange($elm[0].value.length, $elm[0].value.length);
}
catch (ex) {
//ignore
}
}
});
//set the keystroke that started the edit event
//we must do this because the BeginEdit is done in a different event loop than the intitial
//keydown event
//fire this event for the keypress that is received
if (uiGridCtrl && uiGridCtrl.grid.api.cellNav) {
var viewPortKeyDownUnregister = uiGridCtrl.grid.api.cellNav.on.viewPortKeyPress($scope, function (evt, rowCol) {
if (uiGridEditService.isStartEditKey(evt)) {
ngModel.$setViewValue(String.fromCharCode(typeof evt.which === 'number' ? evt.which : evt.keyCode), evt);
ngModel.$render();
}
viewPortKeyDownUnregister();
});
}
// macOS will blur the checkbox when clicked in Safari and Firefox,
// to get around this, we disable the blur handler on mousedown,
// and then focus the checkbox and re-enable the blur handler after $timeout
$elm.on('mousedown', function (evt) {
if ($elm[0].type === 'checkbox') {
$elm.off('blur', $scope.stopEdit);
$timeout(function () {
$elm[0].focus();
$elm.on('blur', $scope.stopEdit);
});
}
});
$elm.on('blur', $scope.stopEdit);
});
$scope.deepEdit = false;
$scope.stopEdit = function (evt) {
if ($scope.inputForm && !$scope.inputForm.$valid) {
evt.stopPropagation();
$scope.$emit(uiGridEditConstants.events.CANCEL_CELL_EDIT);
}
else {
editionComplete($scope, $elm); // MY CODE
$scope.$emit(uiGridEditConstants.events.END_CELL_EDIT);
}
$scope.deepEdit = false;
};
$elm.on('click', function (evt) {
if ($elm[0].type !== 'checkbox') {
$scope.deepEdit = true;
$timeout(function () {
$scope.grid.disableScrolling = true;
});
}
});
$elm.on('keydown', function (evt) {
switch (evt.keyCode) {
case uiGridConstants.keymap.ESC:
evt.stopPropagation();
$scope.$emit(uiGridEditConstants.events.CANCEL_CELL_EDIT);
break;
}
if ($scope.deepEdit &&
(evt.keyCode === uiGridConstants.keymap.LEFT ||
evt.keyCode === uiGridConstants.keymap.RIGHT ||
evt.keyCode === uiGridConstants.keymap.UP ||
evt.keyCode === uiGridConstants.keymap.DOWN)) {
evt.stopPropagation();
}
// Pass the keydown event off to the cellNav service, if it exists
else if (uiGridCtrl && uiGridCtrl.grid.api.cellNav) {
evt.uiGridTargetRenderContainerId = renderContainerCtrl.containerId;
if (uiGridCtrl.cellNav.handleKeyDown(evt) !== null) {
$scope.stopEdit(evt);
}
}
else {
//handle enter and tab for editing not using cellNav
switch (evt.keyCode) {
case uiGridConstants.keymap.ENTER: // Enter (Leave Field)
case uiGridConstants.keymap.TAB:
evt.stopPropagation();
evt.preventDefault();
$scope.stopEdit(evt);
break;
}
}
return true;
});
$scope.$on('$destroy', function unbindEvents() {
// unbind all jquery events in order to avoid memory leaks
$elm.off();
});
}
};
}
};
}]);
}
Because I couldn't make the editor return a number, I found a work-around by directly modifying the grid's value, this however doesn't work when the cell doens't go through our validations (has to be < a number, etc), since I already edited directly on the grid, I'll still have the wrong value.
My question then is: can I make my custom editor return a number instead of a string, and if not, how can I implement this functionality without it?