angular-ui-grid
Version:
A data grid for Angular
1,249 lines (1,125 loc) • 53.5 kB
JavaScript
/*!
* ui-grid - v4.9.1 - 2020-10-26
* Copyright (c) 2020 ; License: MIT
*/
(function () {
'use strict';
/**
* @ngdoc overview
* @name ui.grid.edit
* @description
*
* # ui.grid.edit
*
* <div class="alert alert-success" role="alert"><strong>Stable</strong> This feature is stable. There should no longer be breaking api changes without a deprecation warning.</div>
*
* This module provides cell editing capability to ui.grid. The goal was to emulate keying data in a spreadsheet via
* a keyboard.
* <br/>
* <br/>
* To really get the full spreadsheet-like data entry, the ui.grid.cellNav module should be used. This will allow the
* user to key data and then tab, arrow, or enter to the cells beside or below.
*
* <div doc-module-components="ui.grid.edit"></div>
*/
var module = angular.module('ui.grid.edit', ['ui.grid']);
/**
* @ngdoc object
* @name ui.grid.edit.constant:uiGridEditConstants
*
* @description constants available in edit module
*/
module.constant('uiGridEditConstants', {
EDITABLE_CELL_TEMPLATE: /EDITABLE_CELL_TEMPLATE/g,
// must be lowercase because template bulder converts to lower
EDITABLE_CELL_DIRECTIVE: /editable_cell_directive/g,
events: {
BEGIN_CELL_EDIT: 'uiGridEventBeginCellEdit',
END_CELL_EDIT: 'uiGridEventEndCellEdit',
CANCEL_CELL_EDIT: 'uiGridEventCancelCellEdit'
}
});
/**
* @ngdoc service
* @name ui.grid.edit.service:uiGridEditService
*
* @description Services for editing features
*/
module.service('uiGridEditService', ['$q', 'uiGridConstants', 'gridUtil',
function ($q, uiGridConstants, gridUtil) {
var service = {
initializeGrid: function (grid) {
service.defaultGridOptions(grid.options);
grid.registerColumnBuilder(service.editColumnBuilder);
grid.edit = {};
/**
* @ngdoc object
* @name ui.grid.edit.api:PublicApi
*
* @description Public Api for edit feature
*/
var publicApi = {
events: {
edit: {
/**
* @ngdoc event
* @name afterCellEdit
* @eventOf ui.grid.edit.api:PublicApi
* @description raised when cell editing is complete
* <pre>
* gridApi.edit.on.afterCellEdit(scope,function(rowEntity, colDef) {})
* </pre>
* @param {object} rowEntity the options.data element that was edited
* @param {object} colDef the column that was edited
* @param {object} newValue new value
* @param {object} oldValue old value
*/
afterCellEdit: function (rowEntity, colDef, newValue, oldValue) {
},
/**
* @ngdoc event
* @name beginCellEdit
* @eventOf ui.grid.edit.api:PublicApi
* @description raised when cell editing starts on a cell
* <pre>
* gridApi.edit.on.beginCellEdit(scope,function(rowEntity, colDef) {})
* </pre>
* @param {object} rowEntity the options.data element that was edited
* @param {object} colDef the column that was edited
* @param {object} triggerEvent the event that triggered the edit. Useful to prevent losing keystrokes on some
* complex editors
*/
beginCellEdit: function (rowEntity, colDef, triggerEvent) {
},
/**
* @ngdoc event
* @name cancelCellEdit
* @eventOf ui.grid.edit.api:PublicApi
* @description raised when cell editing is cancelled on a cell
* <pre>
* gridApi.edit.on.cancelCellEdit(scope,function(rowEntity, colDef) {})
* </pre>
* @param {object} rowEntity the options.data element that was edited
* @param {object} colDef the column that was edited
*/
cancelCellEdit: function (rowEntity, colDef) {
}
}
},
methods: {
edit: { }
}
};
grid.api.registerEventsFromObject(publicApi.events);
// grid.api.registerMethodsFromObject(publicApi.methods);
},
defaultGridOptions: function (gridOptions) {
/**
* @ngdoc object
* @name ui.grid.edit.api:GridOptions
*
* @description Options for configuring the edit feature, these are available to be
* set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
*/
/**
* @ngdoc object
* @name enableCellEdit
* @propertyOf ui.grid.edit.api:GridOptions
* @description If defined, sets the default value for the editable flag on each individual colDefs
* if their individual enableCellEdit configuration is not defined. Defaults to undefined.
*/
/**
* @ngdoc object
* @name cellEditableCondition
* @propertyOf ui.grid.edit.api:GridOptions
* @description If specified, either a value or function to be used by all columns before editing.
* If false, then editing of cell is not allowed.
* @example
* <pre>
* function($scope, triggerEvent) {
* //use $scope.row.entity, $scope.col.colDef and triggerEvent to determine if editing is allowed
* return true;
* }
* </pre>
*/
gridOptions.cellEditableCondition = gridOptions.cellEditableCondition === undefined ? true : gridOptions.cellEditableCondition;
/**
* @ngdoc object
* @name editableCellTemplate
* @propertyOf ui.grid.edit.api:GridOptions
* @description If specified, cellTemplate to use as the editor for all columns.
* <br/> defaults to 'ui-grid/cellTextEditor'
*/
/**
* @ngdoc object
* @name enableCellEditOnFocus
* @propertyOf ui.grid.edit.api:GridOptions
* @description If true, then editor is invoked as soon as cell receives focus. Default false.
* <br/>_requires cellNav feature and the edit feature to be enabled_
*/
// enableCellEditOnFocus can only be used if cellnav module is used
gridOptions.enableCellEditOnFocus = gridOptions.enableCellEditOnFocus === undefined ? false : gridOptions.enableCellEditOnFocus;
},
/**
* @ngdoc service
* @name editColumnBuilder
* @methodOf ui.grid.edit.service:uiGridEditService
* @description columnBuilder function that adds edit properties to grid column
* @returns {promise} promise that will load any needed templates when resolved
*/
editColumnBuilder: function (colDef, col, gridOptions) {
var promises = [];
/**
* @ngdoc object
* @name ui.grid.edit.api:ColumnDef
*
* @description Column Definition for edit feature, these are available to be
* set using the ui-grid {@link ui.grid.class:GridOptions.columnDef gridOptions.columnDefs}
*/
/**
* @ngdoc object
* @name enableCellEdit
* @propertyOf ui.grid.edit.api:ColumnDef
* @description enable editing on column
*/
colDef.enableCellEdit = colDef.enableCellEdit === undefined ? (gridOptions.enableCellEdit === undefined ?
(colDef.type !== 'object') : gridOptions.enableCellEdit) : colDef.enableCellEdit;
/**
* @ngdoc object
* @name cellEditableCondition
* @propertyOf ui.grid.edit.api:ColumnDef
* @description If specified, either a value or function evaluated before editing cell. If falsy, then editing of cell is not allowed.
* @example
* <pre>
* function($scope, triggerEvent) {
* //use $scope.row.entity, $scope.col.colDef and triggerEvent to determine if editing is allowed
* return true;
* }
* </pre>
*/
colDef.cellEditableCondition = colDef.cellEditableCondition === undefined ? gridOptions.cellEditableCondition : colDef.cellEditableCondition;
/**
* @ngdoc object
* @name editableCellTemplate
* @propertyOf ui.grid.edit.api:ColumnDef
* @description cell template to be used when editing this column. Can be Url or text template
* <br/>Defaults to gridOptions.editableCellTemplate
*/
if (colDef.enableCellEdit) {
colDef.editableCellTemplate = colDef.editableCellTemplate || gridOptions.editableCellTemplate || 'ui-grid/cellEditor';
promises.push(gridUtil.getTemplate(colDef.editableCellTemplate)
.then(
function (template) {
col.editableCellTemplate = template;
},
function (res) {
// Todo handle response error here?
throw new Error("Couldn't fetch/use colDef.editableCellTemplate '" + colDef.editableCellTemplate + "'");
}));
}
/**
* @ngdoc object
* @name enableCellEditOnFocus
* @propertyOf ui.grid.edit.api:ColumnDef
* @requires ui.grid.cellNav
* @description If true, then editor is invoked as soon as cell receives focus. Default false.
* <br>_requires both the cellNav feature and the edit feature to be enabled_
*/
// enableCellEditOnFocus can only be used if cellnav module is used
colDef.enableCellEditOnFocus = colDef.enableCellEditOnFocus === undefined ? gridOptions.enableCellEditOnFocus : colDef.enableCellEditOnFocus;
/**
* @ngdoc string
* @name editModelField
* @propertyOf ui.grid.edit.api:ColumnDef
* @description a bindable string value that is used when binding to edit controls instead of colDef.field
* <br/> example: You have a complex property on and object like state:{abbrev: 'MS',name: 'Mississippi'}. The
* grid should display state.name in the cell and sort/filter based on the state.name property but the editor
* requires the full state object.
* <br/>colDef.field = 'state.name'
* <br/>colDef.editModelField = 'state'
*/
// colDef.editModelField
return $q.all(promises);
},
/**
* @ngdoc service
* @name isStartEditKey
* @methodOf ui.grid.edit.service:uiGridEditService
* @description Determines if a keypress should start editing. Decorate this service to override with your
* own key events. See service decorator in angular docs.
* @param {Event} evt keydown event
* @returns {boolean} true if an edit should start
*/
isStartEditKey: function (evt) {
return !(evt.metaKey ||
evt.keyCode === uiGridConstants.keymap.ESC ||
evt.keyCode === uiGridConstants.keymap.SHIFT ||
evt.keyCode === uiGridConstants.keymap.CTRL ||
evt.keyCode === uiGridConstants.keymap.ALT ||
evt.keyCode === uiGridConstants.keymap.WIN ||
evt.keyCode === uiGridConstants.keymap.CAPSLOCK ||
evt.keyCode === uiGridConstants.keymap.LEFT ||
(evt.keyCode === uiGridConstants.keymap.TAB && evt.shiftKey) ||
evt.keyCode === uiGridConstants.keymap.RIGHT ||
evt.keyCode === uiGridConstants.keymap.TAB ||
evt.keyCode === uiGridConstants.keymap.UP ||
(evt.keyCode === uiGridConstants.keymap.ENTER && evt.shiftKey) ||
evt.keyCode === uiGridConstants.keymap.DOWN ||
evt.keyCode === uiGridConstants.keymap.ENTER);
}
};
return service;
}]);
/**
* @ngdoc directive
* @name ui.grid.edit.directive:uiGridEdit
* @element div
* @restrict A
*
* @description Adds editing features to the ui-grid directive.
*
* @example
<example module="app">
<file name="app.js">
var app = angular.module('app', ['ui.grid', 'ui.grid.edit']);
app.controller('MainCtrl', ['$scope', function ($scope) {
$scope.data = [
{ name: 'Bob', title: 'CEO' },
{ name: 'Frank', title: 'Lowly Developer' }
];
$scope.columnDefs = [
{name: 'name', enableCellEdit: true},
{name: 'title', enableCellEdit: true}
];
}]);
</file>
<file name="index.html">
<div ng-controller="MainCtrl">
<div ui-grid="{ data: data, columnDefs: columnDefs }" ui-grid-edit></div>
</div>
</file>
</example>
*/
module.directive('uiGridEdit', ['gridUtil', 'uiGridEditService', function (gridUtil, uiGridEditService) {
return {
replace: true,
priority: 0,
require: '^uiGrid',
scope: false,
compile: function () {
return {
pre: function ($scope, $elm, $attrs, uiGridCtrl) {
uiGridEditService.initializeGrid(uiGridCtrl.grid);
},
post: function ($scope, $elm, $attrs, uiGridCtrl) {
}
};
}
};
}]);
/**
* @ngdoc directive
* @name ui.grid.edit.directive:uiGridRenderContainer
* @element div
* @restrict A
*
* @description Adds keydown listeners to renderContainer element so we can capture when to begin edits
*
*/
module.directive('uiGridViewport', [ 'uiGridEditConstants',
function ( uiGridEditConstants) {
return {
replace: true,
priority: -99998, // run before cellNav
require: ['^uiGrid', '^uiGridRenderContainer'],
scope: false,
compile: function () {
return {
post: function ($scope, $elm, $attrs, controllers) {
var uiGridCtrl = controllers[0];
// Skip attaching if edit and cellNav is not enabled
if (!uiGridCtrl.grid.api.edit || !uiGridCtrl.grid.api.cellNav) { return; }
var containerId = controllers[1].containerId;
// no need to process for other containers
if (containerId !== 'body') {
return;
}
// refocus on the grid
$scope.$on(uiGridEditConstants.events.CANCEL_CELL_EDIT, function () {
uiGridCtrl.focus();
});
$scope.$on(uiGridEditConstants.events.END_CELL_EDIT, function () {
uiGridCtrl.focus();
});
}
};
}
};
}]);
/**
* @ngdoc directive
* @name ui.grid.edit.directive:uiGridCell
* @element div
* @restrict A
*
* @description Stacks on top of ui.grid.uiGridCell to provide in-line editing capabilities to the cell
* Editing Actions.
*
* Binds edit start events to the uiGridCell element. When the events fire, the gridCell element is appended
* with the columnDef.editableCellTemplate element ('cellEditor.html' by default).
*
* The editableCellTemplate should respond to uiGridEditConstants.events.BEGIN\_CELL\_EDIT angular event
* and do the initial steps needed to edit the cell (setfocus on input element, etc).
*
* When the editableCellTemplate recognizes that the editing is ended (blur event, Enter key, etc.)
* it should emit the uiGridEditConstants.events.END\_CELL\_EDIT event.
*
* If editableCellTemplate recognizes that the editing has been cancelled (esc key)
* it should emit the uiGridEditConstants.events.CANCEL\_CELL\_EDIT event. The original value
* will be set back on the model by the uiGridCell directive.
*
* Events that invoke editing:
* - dblclick
* - F2 keydown (when using cell selection)
*
* Events that end editing:
* - Dependent on the specific editableCellTemplate
* - Standards should be blur and enter keydown
*
* Events that cancel editing:
* - Dependent on the specific editableCellTemplate
* - Standards should be Esc keydown
*
* Grid Events that end editing:
* - uiGridConstants.events.GRID_SCROLL
*
*/
/**
* @ngdoc object
* @name ui.grid.edit.api:GridRow
*
* @description GridRow options for edit feature, these are available to be
* set internally only, by other features
*/
/**
* @ngdoc object
* @name enableCellEdit
* @propertyOf ui.grid.edit.api:GridRow
* @description enable editing on row, grouping for example might disable editing on group header rows
*/
module.directive('uiGridCell',
['$compile', '$injector', '$timeout', 'uiGridConstants', 'uiGridEditConstants', 'gridUtil', '$parse', 'uiGridEditService', '$rootScope', '$q',
function ($compile, $injector, $timeout, uiGridConstants, uiGridEditConstants, gridUtil, $parse, uiGridEditService, $rootScope, $q) {
var touchstartTimeout = 500;
if ($injector.has('uiGridCellNavService')) {
var uiGridCellNavService = $injector.get('uiGridCellNavService');
}
return {
priority: -100, // run after default uiGridCell directive
restrict: 'A',
scope: false,
require: '?^uiGrid',
link: function ($scope, $elm, $attrs, uiGridCtrl) {
var html,
origCellValue,
cellModel,
cancelTouchstartTimeout,
inEdit = false;
var editCellScope;
if (!$scope.col.colDef.enableCellEdit) {
return;
}
var cellNavNavigateDereg = function() {};
var viewPortKeyDownDereg = function() {};
var setEditable = function() {
if ($scope.col.colDef.enableCellEdit && $scope.row.enableCellEdit !== false) {
if (!$scope.beginEditEventsWired) { // prevent multiple attachments
registerBeginEditEvents();
}
} else {
if ($scope.beginEditEventsWired) {
cancelBeginEditEvents();
}
}
};
setEditable();
var rowWatchDereg = $scope.$watch('row', function (n, o) {
if (n !== o) {
setEditable();
}
});
$scope.$on('$destroy', function destroyEvents() {
rowWatchDereg();
// unbind all jquery events in order to avoid memory leaks
$elm.off();
});
function registerBeginEditEvents() {
$elm.on('dblclick', beginEdit);
// Add touchstart handling. If the users starts a touch and it doesn't end after X milliseconds, then start the edit
$elm.on('touchstart', touchStart);
if (uiGridCtrl && uiGridCtrl.grid.api.cellNav) {
viewPortKeyDownDereg = uiGridCtrl.grid.api.cellNav.on.viewPortKeyDown($scope, function (evt, rowCol) {
if (rowCol === null) {
return;
}
if (rowCol.row === $scope.row && rowCol.col === $scope.col && !$scope.col.colDef.enableCellEditOnFocus) {
// important to do this before scrollToIfNecessary
beginEditKeyDown(evt);
}
});
cellNavNavigateDereg = uiGridCtrl.grid.api.cellNav.on.navigate($scope, function (newRowCol, oldRowCol, evt) {
if ($scope.col.colDef.enableCellEditOnFocus) {
// Don't begin edit if the cell hasn't changed
if (newRowCol.row === $scope.row && newRowCol.col === $scope.col &&
(evt === null || (evt && (evt.type === 'click' || evt.type === 'keydown')))) {
$timeout(function() {
beginEdit(evt);
});
}
}
});
}
$scope.beginEditEventsWired = true;
}
function touchStart(event) {
// jQuery masks events
if (typeof(event.originalEvent) !== 'undefined' && event.originalEvent !== undefined) {
event = event.originalEvent;
}
// Bind touchend handler
$elm.on('touchend', touchEnd);
// Start a timeout
cancelTouchstartTimeout = $timeout(function() { }, touchstartTimeout);
// Timeout's done! Start the edit
cancelTouchstartTimeout.then(function () {
// Use setTimeout to start the edit because beginEdit expects to be outside of $digest
setTimeout(beginEdit, 0);
// Undbind the touchend handler, we don't need it anymore
$elm.off('touchend', touchEnd);
}).catch(angular.noop);
}
// Cancel any touchstart timeout
function touchEnd() {
$timeout.cancel(cancelTouchstartTimeout);
$elm.off('touchend', touchEnd);
}
function cancelBeginEditEvents() {
$elm.off('dblclick', beginEdit);
$elm.off('keydown', beginEditKeyDown);
$elm.off('touchstart', touchStart);
cellNavNavigateDereg();
viewPortKeyDownDereg();
$scope.beginEditEventsWired = false;
}
function beginEditKeyDown(evt) {
if (uiGridEditService.isStartEditKey(evt)) {
beginEdit(evt);
}
}
function shouldEdit(col, row, triggerEvent) {
return !row.isSaving &&
( angular.isFunction(col.colDef.cellEditableCondition) ?
col.colDef.cellEditableCondition($scope, triggerEvent) :
col.colDef.cellEditableCondition );
}
function beginEdit(triggerEvent) {
// we need to scroll the cell into focus before invoking the editor
$scope.grid.api.core.scrollToIfNecessary($scope.row, $scope.col)
.then(function () {
beginEditAfterScroll(triggerEvent);
});
}
/**
* @ngdoc property
* @name editDropdownOptionsArray
* @propertyOf ui.grid.edit.api:ColumnDef
* @description an array of values in the format
* [ {id: xxx, value: xxx} ], which is populated
* into the edit dropdown
*
*/
/**
* @ngdoc property
* @name editDropdownIdLabel
* @propertyOf ui.grid.edit.api:ColumnDef
* @description the label for the "id" field
* in the editDropdownOptionsArray. Defaults
* to 'id'
* @example
* <pre>
* $scope.gridOptions = {
* columnDefs: [
* {name: 'status', editableCellTemplate: 'ui-grid/dropdownEditor',
* editDropdownOptionsArray: [{code: 1, status: 'active'}, {code: 2, status: 'inactive'}],
* editDropdownIdLabel: 'code', editDropdownValueLabel: 'status' }
* ],
* </pre>
*
*/
/**
* @ngdoc property
* @name editDropdownRowEntityOptionsArrayPath
* @propertyOf ui.grid.edit.api:ColumnDef
* @description a path to a property on row.entity containing an
* array of values in the format
* [ {id: xxx, value: xxx} ], which will be used to populate
* the edit dropdown. This can be used when the dropdown values are dependent on
* the backing row entity.
* If this property is set then editDropdownOptionsArray will be ignored.
* @example
* <pre>
* $scope.gridOptions = {
* columnDefs: [
* {name: 'status', editableCellTemplate: 'ui-grid/dropdownEditor',
* editDropdownRowEntityOptionsArrayPath: 'foo.bars[0].baz',
* editDropdownIdLabel: 'code', editDropdownValueLabel: 'status' }
* ],
* </pre>
*
*/
/**
* @ngdoc service
* @name editDropdownOptionsFunction
* @methodOf ui.grid.edit.api:ColumnDef
* @description a function returning an array of values in the format
* [ {id: xxx, value: xxx} ], which will be used to populate
* the edit dropdown. This can be used when the dropdown values are dependent on
* the backing row entity with some kind of algorithm.
* If this property is set then both editDropdownOptionsArray and
* editDropdownRowEntityOptionsArrayPath will be ignored.
* @param {object} rowEntity the options.data element that the returned array refers to
* @param {object} colDef the column that implements this dropdown
* @returns {object} an array of values in the format
* [ {id: xxx, value: xxx} ] used to populate the edit dropdown
* @example
* <pre>
* $scope.gridOptions = {
* columnDefs: [
* {name: 'status', editableCellTemplate: 'ui-grid/dropdownEditor',
* editDropdownOptionsFunction: function(rowEntity, colDef) {
* if (rowEntity.foo === 'bar') {
* return [{id: 'bar1', value: 'BAR 1'},
* {id: 'bar2', value: 'BAR 2'},
* {id: 'bar3', value: 'BAR 3'}];
* } else {
* return [{id: 'foo1', value: 'FOO 1'},
* {id: 'foo2', value: 'FOO 2'}];
* }
* },
* editDropdownIdLabel: 'code', editDropdownValueLabel: 'status' }
* ],
* </pre>
*
*/
/**
* @ngdoc property
* @name editDropdownValueLabel
* @propertyOf ui.grid.edit.api:ColumnDef
* @description the label for the "value" field
* in the editDropdownOptionsArray. Defaults
* to 'value'
* @example
* <pre>
* $scope.gridOptions = {
* columnDefs: [
* {name: 'status', editableCellTemplate: 'ui-grid/dropdownEditor',
* editDropdownOptionsArray: [{code: 1, status: 'active'}, {code: 2, status: 'inactive'}],
* editDropdownIdLabel: 'code', editDropdownValueLabel: 'status' }
* ],
* </pre>
*
*/
/**
* @ngdoc property
* @name editDropdownFilter
* @propertyOf ui.grid.edit.api:ColumnDef
* @description A filter that you would like to apply to the values in the options list
* of the dropdown. For example if you were using angular-translate you might set this
* to `'translate'`
* @example
* <pre>
* $scope.gridOptions = {
* columnDefs: [
* {name: 'status', editableCellTemplate: 'ui-grid/dropdownEditor',
* editDropdownOptionsArray: [{code: 1, status: 'active'}, {code: 2, status: 'inactive'}],
* editDropdownIdLabel: 'code', editDropdownValueLabel: 'status', editDropdownFilter: 'translate' }
* ],
* </pre>
*
*/
function beginEditAfterScroll(triggerEvent) {
// If we are already editing, then just skip this so we don't try editing twice...
if (inEdit) {
return;
}
if (!shouldEdit($scope.col, $scope.row, triggerEvent)) {
return;
}
var modelField = $scope.row.getQualifiedColField($scope.col);
if ($scope.col.colDef.editModelField) {
modelField = gridUtil.preEval('row.entity.' + $scope.col.colDef.editModelField);
}
cellModel = $parse(modelField);
// get original value from the cell
origCellValue = cellModel($scope);
html = $scope.col.editableCellTemplate;
html = html.replace(uiGridConstants.MODEL_COL_FIELD, modelField);
html = html.replace(uiGridConstants.COL_FIELD, 'grid.getCellValue(row, col)');
var optionFilter = $scope.col.colDef.editDropdownFilter ? '|' + $scope.col.colDef.editDropdownFilter : '';
html = html.replace(uiGridConstants.CUSTOM_FILTERS, optionFilter);
var inputType = 'text';
switch ($scope.col.colDef.type) {
case 'boolean':
inputType = 'checkbox';
break;
case 'number':
inputType = 'number';
break;
case 'date':
inputType = 'date';
break;
}
html = html.replace('INPUT_TYPE', inputType);
// In order to fill dropdown options we use:
// - A function/promise or
// - An array inside of row entity if no function exists or
// - A single array for the whole column if none of the previous exists.
var editDropdownOptionsFunction = $scope.col.colDef.editDropdownOptionsFunction;
if (editDropdownOptionsFunction) {
$q.when(editDropdownOptionsFunction($scope.row.entity, $scope.col.colDef))
.then(function(result) {
$scope.editDropdownOptionsArray = result;
});
} else {
var editDropdownRowEntityOptionsArrayPath = $scope.col.colDef.editDropdownRowEntityOptionsArrayPath;
if (editDropdownRowEntityOptionsArrayPath) {
$scope.editDropdownOptionsArray = resolveObjectFromPath($scope.row.entity, editDropdownRowEntityOptionsArrayPath);
}
else {
$scope.editDropdownOptionsArray = $scope.col.colDef.editDropdownOptionsArray;
}
}
$scope.editDropdownIdLabel = $scope.col.colDef.editDropdownIdLabel ? $scope.col.colDef.editDropdownIdLabel : 'id';
$scope.editDropdownValueLabel = $scope.col.colDef.editDropdownValueLabel ? $scope.col.colDef.editDropdownValueLabel : 'value';
var createEditor = function() {
inEdit = true;
cancelBeginEditEvents();
var cellElement = angular.element(html);
$elm.append(cellElement);
editCellScope = $scope.$new();
$compile(cellElement)(editCellScope);
var gridCellContentsEl = angular.element($elm.children()[0]);
gridCellContentsEl.addClass('ui-grid-cell-contents-hidden');
};
if (!$rootScope.$$phase) {
$scope.$apply(createEditor);
} else {
createEditor();
}
// stop editing when grid is scrolled
var deregOnGridScroll = $scope.col.grid.api.core.on.scrollBegin($scope, function () {
if ($scope.grid.disableScrolling) {
return;
}
endEdit();
$scope.grid.api.edit.raise.afterCellEdit($scope.row.entity, $scope.col.colDef, cellModel($scope), origCellValue);
deregOnGridScroll();
deregOnEndCellEdit();
deregOnCancelCellEdit();
});
// end editing
var deregOnEndCellEdit = $scope.$on(uiGridEditConstants.events.END_CELL_EDIT, function () {
endEdit();
$scope.grid.api.edit.raise.afterCellEdit($scope.row.entity, $scope.col.colDef, cellModel($scope), origCellValue);
deregOnEndCellEdit();
deregOnGridScroll();
deregOnCancelCellEdit();
});
// cancel editing
var deregOnCancelCellEdit = $scope.$on(uiGridEditConstants.events.CANCEL_CELL_EDIT, function () {
cancelEdit();
deregOnCancelCellEdit();
deregOnGridScroll();
deregOnEndCellEdit();
});
$scope.$broadcast(uiGridEditConstants.events.BEGIN_CELL_EDIT, triggerEvent);
$timeout(function () {
// execute in a timeout to give any complex editor templates a cycle to completely render
$scope.grid.api.edit.raise.beginCellEdit($scope.row.entity, $scope.col.colDef, triggerEvent);
});
}
function endEdit() {
$scope.grid.disableScrolling = false;
if (!inEdit) {
return;
}
// sometimes the events can't keep up with the keyboard and grid focus is lost, so always focus
// back to grid here. The focus call needs to be before the $destroy and removal of the control,
// otherwise ng-model-options of UpdateOn: 'blur' will not work.
if (uiGridCtrl && uiGridCtrl.grid.api.cellNav) {
uiGridCtrl.focus();
}
var gridCellContentsEl = angular.element($elm.children()[0]);
// remove edit element
editCellScope.$destroy();
var children = $elm.children();
for (var i = 1; i < children.length; i++) {
angular.element(children[i]).remove();
}
gridCellContentsEl.removeClass('ui-grid-cell-contents-hidden');
inEdit = false;
registerBeginEditEvents();
$scope.grid.api.core.notifyDataChange( uiGridConstants.dataChange.EDIT );
}
function cancelEdit() {
$scope.grid.disableScrolling = false;
if (!inEdit) {
return;
}
cellModel.assign($scope, origCellValue);
$scope.$apply();
$scope.grid.api.edit.raise.cancelCellEdit($scope.row.entity, $scope.col.colDef);
endEdit();
}
// resolves a string path against the given object
// shamelessly borrowed from
// http://stackoverflow.com/questions/6491463/accessing-nested-javascript-objects-with-string-key
function resolveObjectFromPath(object, path) {
path = path.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties
path = path.replace(/^\./, ''); // strip a leading dot
var a = path.split('.');
while (a.length) {
var n = a.shift();
if (n in object) {
object = object[n];
} else {
return;
}
}
return object;
}
}
};
}]);
/**
* @ngdoc directive
* @name ui.grid.edit.directive:uiGridEditor
* @element div
* @restrict A
*
* @description input editor directive for editable fields.
* Provides EndEdit and CancelEdit events
*
* Events that end editing:
* blur and enter keydown
*
* Events that cancel editing:
* - Esc keydown
*
*/
module.directive('uiGridEditor',
['gridUtil', 'uiGridConstants', 'uiGridEditConstants','$timeout', 'uiGridEditService',
function (gridUtil, uiGridConstants, uiGridEditConstants, $timeout, uiGridEditService) {
return {
scope: true,
require: ['?^uiGrid', '?^uiGridRenderContainer', 'ngModel'],
compile: function () {
return {
pre: function ($scope, $elm, $attrs) {
},
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 () {
// must be in a timeout since it requires a new digest cycle
$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)) {
var code = typeof evt.which === 'number' ? evt.which : evt.keyCode;
if (code > 0) {
ngModel.$setViewValue(String.fromCharCode(code), 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);
});
}
});
if ($elm[0]) {
$elm[0].focus();
}
$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 {
$scope.$emit(uiGridEditConstants.events.END_CELL_EDIT);
}
$scope.deepEdit = false;
};
$elm.on('click', function (evt) {
if ($elm[0].type !== 'checkbox') {
$scope.deepEdit = true;
$scope.$applyAsync(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();
});
}
};
}
};
}]);
/**
* @ngdoc directive
* @name ui.grid.edit.directive:input
* @element input
* @restrict E
*
* @description directive to provide binding between input[date] value and ng-model for angular 1.2
* It is similar to input[date] directive of angular 1.3
*
* Supported date format for input is 'yyyy-MM-dd'
* The directive will set the $valid property of input element and the enclosing form to false if
* model is invalid date or value of input is entered wrong.
*
*/
module.directive('uiGridEditor', ['$filter', function ($filter) {
function parseDateString(dateString) {
if (typeof(dateString) === 'undefined' || dateString === '') {
return null;
}
var parts = dateString.split('-');
if (parts.length !== 3) {
return null;
}
var year = parseInt(parts[0], 10);
var month = parseInt(parts[1], 10);
var day = parseInt(parts[2], 10);
if (month < 1 || year < 1 || day < 1) {
return null;
}
return new Date(year, (month - 1), day);
}
return {
priority: -100, // run after default uiGridEditor directive
require: '?ngModel',
link: function (scope, element, attrs, ngModel) {
if (angular.version.minor === 2 && attrs.type && attrs.type === 'date' && ngModel) {
ngModel.$formatters.push(function (modelValue) {
ngModel.$setValidity(null,(!modelValue || !isNaN(modelValue.getTime())));
return $filter('date')(modelValue, 'yyyy-MM-dd');
});
ngModel.$parsers.push(function (viewValue) {
if (viewValue && viewValue.length > 0) {
var dateValue = parseDateString(viewValue);
ngModel.$setValidity(null, (dateValue && !isNaN(dateValue.getTime())));
return dateValue;
}
else {
ngModel.$setValidity(null, true);
return null;
}
});
}
}
};
}]);
/**
* @ngdoc directive
* @name ui.grid.edit.directive:uiGridEditDropdown
* @element div
* @restrict A
*
* @description dropdown editor for editable fields.
* Provides EndEdit and CancelEdit events
*
* Events that end editing:
* blur and enter keydown, and any left/right nav
*
* Events that cancel editing:
* - Esc keydown
*
*/
module.directive('uiGridEditDropdown',
['uiGridConstants', 'uiGridEditConstants', '$timeout',
function (uiGridConstants, uiGridEditConstants, $timeout) {
return {
require: ['?^uiGrid', '?^uiGridRenderContainer'],
scope: true,
compile: function () {
return {
pre: function ($scope, $elm, $attrs) {
},
post: function ($scope, $elm, $attrs, controllers) {
var uiGridCtrl = controllers[0];
var renderContainerCtrl = controllers[1];
// set focus at start of edit
$scope.$on(uiGridEditConstants.events.BEGIN_CELL_EDIT, function () {
$timeout(function() {
$elm[0].focus();
});
$elm[0].style.width = ($elm[0].parentElement.offsetWidth - 1) + 'px';
$elm.on('blur', function (evt) {
$scope.stopEdit(evt);
});
});
$scope.stopEdit = function (evt) {
// no need to validate a dropdown - invalid values shouldn't be
// available in the list
$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 {
// 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 jquery events to prevent memory leaks
$elm.off();
});
}
};
}
};
}]);
/**
* @ngdoc directive
* @name ui.grid.edit.directive:uiGridEditFileChooser
* @element div
* @restrict A
*
* @description input editor directive for editable fields.
* Provides EndEdit and CancelEdit events
*
* Events that end editing:
* blur and enter keydown
*
* Events that cancel editing:
* - Esc keydown
*
*/
module.directive('uiGridEditFileChooser',
['gridUtil', 'uiGridConstants', 'uiGridEditConstants',
function (gridUtil, uiGridConstants, uiGridEditConstants) {
return {
scope: true,
require: ['?^uiGrid', '?^uiGridRenderContainer'],
compile: function () {
return {
pre: function ($scope, $elm, $attrs) {
},
post: function ($scope, $elm) {
function handleFileSelect(event) {
var target = event.srcElement || event.target;
if (target && target.files && target.files.length > 0) {
/**
* @ngdoc property
* @name editFileChooserCallback
* @propertyOf ui.grid.edit.api:ColumnDef
* @description A function that should be called when any files have been chosen
* by the user. You should use this to process the files appropriately for your
* application.
*
* It passes the gridCol, the gridRow (from which you can get gridRow.entity),
* and the files. The files are in the format as returned from the file chooser,
* an array of fil