9

I am using AngularJS Bootstrap datepicker inside of the ui-grid (3.0.0 - unstable)

I have implemented the datepicker through the custom cellTemplate (ui-grid):

{
      field: 'Wiedervorl',
      displayName: 'Wiedervorl.',
      enableCellEdit: true,
      enableCellEditOnFocus: true,
      enableHiding: false,
      cellTemplate: '<div><input ng-model="row.entity.Wiedervorl" ng-change="grid.appScope.addModifyFlag(row.entity)" ng-click="opened = true;" datepicker-popup="dd.MM.yyyy" is-open="opened" datepicker-options="grid.appScope.dateOptions" datepicker-append-to-body="true" type="text" /></div>',
      cellFilter: 'date',
      cellClass: function () {
        return 'text-left';
      },
      filter: {
        placeholder: 'date',
        condition: uiGridConstants.filter.CONTAINS
      },
      width: '7%'
    },

There is a flag 'opened' inside of the datepicker input which is isolated exactly for one instance of this input element (datepicker instance).

The problem is, that after opening one more datepicker in another row, the previous one doesn't close and multiple datepickers can be opened at the same time.

enter image description here

I tried to add "ng-blur" to the input, but it executed at the moment, when datepicker is opening.

Any ideas, how to accomplish the closing of previous datepicker instance inside of the UI-Grid ? To make only one active at a time.

Thanks

4 Answers 4

8

Maybe it will be helpful for someone. I did a detailed investigation into how to integrate a Bootstrap detepicker to a UI Grid.

At first, you need to use a custom editableCellTemplate and a custom directive which correctly processes END_EDIT and CANCEL_EDIT events.

Below you can find the code of my directive:

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 uib-datepicker-popup is-open="isOpen" ng-model="' + attrs.rowField + '" ng-change="changeDate($event)" on-open-focus="false" disabled/></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 = $('ul', cellElement);
                        var datepickerPosition = {
                            width: datepickerElement.outerWidth(),
                            height: datepickerElement.outerHeight()
                        };
                        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;
                        }

                        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) {
                            newOffsetValues.top = cellPosition.offset.top - requiredPixels + 10;
                        } else if (freePixelsOnBottom >= requiredPixels && freePixelsOnTop < requiredPixels) {
                            newOffsetValues.top = cellPosition.offset.top - freePixelsOnTop + 10;
                        } else {
                            newOffsetValues.top = gridPosition.height - datepickerPosition.height + gridPosition.offset.top - 20;
                        }

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

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

                    $scope.isOpen = true;

                    var uiGridCtrl = controllers[0];
                    var renderContainerCtrl = controllers[1];

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

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

                    $scope.changeDate = function (evt) {
                        $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);
                    });

                    $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;
                    });
                }
            };
        }
    };
}]);

Note! My directive requires jQuery for setting the correct position of the datepicker inside the grid. If you don't use jQuery, you should comment calling the setCorrectPosition() in the directive. In this case datepicker will be placed in the grid according to the standard behavior.

You can find some extra information on how use it and examples code at Github: https://github.com/Joiler/ui-grid-edit-datepicker

Plunker: http://plnkr.co/edit/4mNr86cN6wFOLYQ02QND

Sign up to request clarification or add additional context in comments.

12 Comments

This was very, very useful. It needs some modification if you have multiple grids on screen though. I'll have to submit a PR with a tweak.
@RichardTurner, I integrated it only with Angular Bootstrap Datepicker which doesn't provide time support.
Thanks @JoilerWhite - I have made a new directive based on github.com/adamalbrecht/ngQuickDate and your example code. It has a date and time option :-)
I am following this answer and also the plunker given below. I completely copied this directive in my module. I also have jQuery in place. I used a similar column Definition. And also I made a same filter in my module. Still I am unable to see any date picker working. Any suggestions..what could I be missing
I see the module name you have used is ui.grid.edit. There already is a directive with the same name which is the part of library itself. I am confused whether there is a connection between the two ?? How can you name two module same?
|
5

Just in-case if somebody comes across like I did, ui-grid also already have it's own built-in date picker option.

 $scope.gridOptions.columnDefs = [
    { name: 'registered', 
      displayName: 'Registered', 
      type: 'date', 
      cellFilter:'date:"yyyy-MM-dd"'}
];

Unless a specific type of external date-picker is required like OP, this seems pretty handy and easy to use.

1 Comment

One of the main problems with this is that it is simply setting the input type to date, which is an HTML5 feature that many common browsers (most importantly Firefox) don't implement.
2

You need to implement a directive with the date picker in it, and that directive needs to provide END_EDIT and CANCEL_EDIT events to the grid. Refer the edit tutorial: http://ui-grid.info/docs/#/tutorial/201_editable (just above ColumnDef options).

You can see the code associated with the dropdown directive in https://github.com/angular-ui/ng-grid/blob/master/src/features/edit/js/gridEdit.js, second block from the bottom

2 Comments

Ok, I took in account your advice and tried to create directive. I created it. It seems to work but not smoothly. As I see you are very familiar with ui-grid... Could you help me to solve one more problem, please? Here is my code: note.io/1AxMc54 I put the list with problems there. I show undesirable behavior on the following link: screencast.com/t/meAwibtT
There's a similar discussion going on in the gitter channel gitter.im/angular-ui/ng-grid (look for @joanne16) based on @c0bra's blog post: brianhann.com/ui-grid-and-dropdowns. In general you need to listen for cellNav and do end_cell_edit, I think that will deal with much of what you're looking for.
0
app.directive('uiGridEditDatepicker', ['$timeout', '$document', 'uiGridConstants', 'uiGridEditConstants', function($timeout, $document, uiGridConstants, uiGridEditConstants) {
return {
    template: function(element, attrs) {    
        var html = '<div class="datepicker-wrapper" ><input type="text" uib-datepicker-popup datepicker-append-to-body="true" is-open="isOpen" ng-model="datePickerValue" ng-change="changeDate($event)" popup-placement="auto top"/></div>';
        return html;
    },
    require: ['?^uiGrid', '?^uiGridRenderContainer'],
    scope: true,
    compile: function() {
        return {
            pre: function($scope, $elm, $attrs) {

            },
            post: function($scope, $elm, $attrs, controllers) {

                $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, body > div > .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;
                });
            }
        };
    }
};
}]);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.