UNPKG

devextreme

Version:

HTML5 JavaScript Component Suite for Responsive Web Development

721 lines (617 loc) • 32.4 kB
"use strict"; var $ = require("../../core/renderer"), eventsEngine = require("../../events/core/events_engine"), modules = require("./ui.grid_core.modules"), gridCoreUtils = require("./ui.grid_core.utils"), commonUtils = require("../../core/utils/common"), each = require("../../core/utils/iterator").each, typeUtils = require("../../core/utils/type"), extend = require("../../core/utils/extend").extend, focused = require("../widget/selectors").focused, equalByValue = commonUtils.equalByValue, messageLocalization = require("../../localization/message"), Button = require("../button"), pointerEvents = require("../../events/pointer"), ValidationEngine = require("../validation_engine"), Validator = require("../validator"), Tooltip = require("../tooltip"), Overlay = require("../overlay"), themes = require("../themes"); var INVALIDATE_CLASS = "invalid", REVERT_TOOLTIP_CLASS = "revert-tooltip", ROWS_VIEW_CLASS = "rowsview", INVALID_MESSAGE_CLASS = "dx-invalid-message", WIDGET_INVALID_MESSAGE_CLASS = "invalid-message", INVALID_MESSAGE_ALWAYS_CLASS = "dx-invalid-message-always", REVERT_BUTTON_CLASS = "dx-revert-button", CELL_HIGHLIGHT_OUTLINE = "dx-highlight-outline", VALIDATOR_CLASS = "validator", INSERT_INDEX = "__DX_INSERT_INDEX__", PADDING_BETWEEN_TOOLTIPS = 2, EDIT_MODE_ROW = "row", EDIT_MODE_FORM = "form", EDIT_MODE_BATCH = "batch", EDIT_MODE_CELL = "cell", EDIT_MODE_POPUP = "popup", FORM_BASED_MODES = [EDIT_MODE_POPUP, EDIT_MODE_FORM]; var ValidatingController = modules.Controller.inherit(function () { return { init: function init() { this._editingController = this.getController("editing"); this.createAction("onRowValidating"); }, _rowValidating: function _rowValidating(editData, validate) { var that = this, brokenRules = validate ? validate.brokenRules || validate.brokenRule && [validate.brokenRule] : [], isValid = validate ? validate.isValid : editData.isValid, parameters = { brokenRules: brokenRules, isValid: isValid, key: editData.key, newData: editData.data, oldData: editData.oldData, errorText: null }; that.executeAction("onRowValidating", parameters); editData.isValid = parameters.isValid; editData.errorText = parameters.errorText; return parameters; }, validate: function validate(isFull) { var that = this, isValid = true, editingController = that._editingController; isFull = isFull || editingController.getEditMode() === EDIT_MODE_ROW; if (that._isValidationInProgress) { return false; } that._isValidationInProgress = true; if (isFull) { each(editingController._editData, function (index, editData) { var validationResult; if (editData.type && editData.type !== "remove") { validationResult = that.validateGroup(editData); if (!validationResult.isValid) { each(validationResult.brokenRules, function () { var value = this.validator.option("adapter").getValue(); if (value === undefined) { value = null; } if (this.column) { editingController.updateFieldValue({ key: editData.key, column: this.column }, value, null, true); } }); } isValid = isValid && validationResult.isValid; } }); } else if (that._currentCellValidator) { isValid = that.validateGroup(that._currentCellValidator._findGroup()).isValid; } that._isValidationInProgress = false; return isValid; }, validateGroup: function validateGroup(editData) { var that = this, validateGroup = ValidationEngine.getGroupConfig(editData), validationResults; if (validateGroup && validateGroup.validators.length) { validationResults = ValidationEngine.validateGroup(editData); } return that._rowValidating(editData, validationResults); }, updateEditData: function updateEditData(editData) { var editMode = this._editingController.getEditMode(); if (FORM_BASED_MODES.indexOf(editMode) === -1) { this.setDisableApplyValidationResults(true); editData.isValid = ValidationEngine.getGroupConfig(editData) ? ValidationEngine.validateGroup(editData).isValid : true; this.setDisableApplyValidationResults(false); } else { editData.isValid = true; } }, setValidator: function setValidator(validator) { this._currentCellValidator = validator; }, getValidator: function getValidator() { return this._currentCellValidator; }, removeValidators: function removeValidators(editIndex) { var that = this, editingController = that._editingController; each(editingController._editData, function (index, editData) { var validateGroup = ValidationEngine.getGroupConfig(editData); if (!typeUtils.isDefined(editIndex) || editIndex === index) { if (validateGroup) { for (var i = 0; i < validateGroup.validators.length; i++) { validateGroup.validators[i]._dispose(); i--; } } } }); }, createValidator: function createValidator(parameters, $container) { var that = this, editingController = that._editingController, column = parameters.column, editData, editIndex, defaultValidationResult = function defaultValidationResult(options) { if (options.brokenRule) { options.brokenRule.columnIndex = column.index; options.brokenRule.column = column; } if ($container && !that.getDisableApplyValidationResults()) { if (!options.isValid) { var $focus = $container.find(":focus"); editingController.showHighlighting($container, true); if (!focused($focus)) { eventsEngine.trigger($focus, "focus"); eventsEngine.trigger($focus, pointerEvents.down); } } $container.toggleClass(that.addWidgetPrefix(INVALIDATE_CLASS), !options.isValid); } }, getValue = function getValue() { var value = column.calculateCellValue(editData.data || {}); return value !== undefined ? value : parameters.value; }, visibleColumns, columnsController, showEditorAlways = column.showEditorAlways; if (!column.validationRules || !Array.isArray(column.validationRules) || typeUtils.isDefined(column.command)) return; editIndex = editingController.getIndexByKey(parameters.key, editingController._editData); if (editIndex < 0) { if (!showEditorAlways) { columnsController = that.getController("columns"); visibleColumns = columnsController && columnsController.getVisibleColumns() || []; showEditorAlways = visibleColumns.some(function (column) { return column.showEditorAlways; }); } if (showEditorAlways) { editIndex = editingController._addEditData({ key: parameters.key, oldData: parameters.data }); } } if (editIndex >= 0) { editData = editingController._editData[editIndex]; var useDefaultValidator = $container && $container.hasClass("dx-widget"); $container && $container.addClass(that.addWidgetPrefix(VALIDATOR_CLASS)); var validator = new Validator($container || $("<div>"), { name: column.caption, validationRules: extend(true, [], column.validationRules), validationGroup: editData, adapter: useDefaultValidator ? null : { getValue: getValue, applyValidationResults: defaultValidationResult }, dataGetter: function dataGetter() { return gridCoreUtils.createObjectWithChanges(editData.oldData, editData.data); } }); if (useDefaultValidator) { var adapter = validator.option("adapter"); if (adapter) { adapter.getValue = getValue; } } return validator; } }, setDisableApplyValidationResults: function setDisableApplyValidationResults(flag) { this._disableApplyValidationResults = flag; }, getDisableApplyValidationResults: function getDisableApplyValidationResults() { return this._disableApplyValidationResults; } }; }()); module.exports = { defaultOptions: function defaultOptions() { return { /** * @name GridBaseOptions.onRowValidating * @publicName onRowValidating * @type function(e) * @type_function_param1 e:object * @type_function_param1_field4 brokenRules:Array<RequiredRule,NumericRule,RangeRule,StringLengthRule,CustomRule,CompareRule,PatternRule,EmailRule> * @type_function_param1_field5 isValid:boolean * @type_function_param1_field6 key:any * @type_function_param1_field7 newData:object * @type_function_param1_field8 oldData:object * @type_function_param1_field9 errorText:string * @extends Action * @action */ editing: { texts: { /** * @name GridBaseOptions.editing.texts.validationCancelChanges * @publicName validationCancelChanges * @type string * @default "Cancel changes" */ validationCancelChanges: messageLocalization.format("dxDataGrid-validationCancelChanges") } } }; }, controllers: { validating: ValidatingController }, extenders: { controllers: { editing: { _addEditData: function _addEditData(options, row) { var that = this, validatingController = that.getController("validating"), editDataIndex = that.callBase(options, row), editData; if (editDataIndex >= 0) { editData = that._editData[editDataIndex]; validatingController.updateEditData(editData); } return editDataIndex; }, _updateRowAndPageIndices: function _updateRowAndPageIndices() { var that = this, startInsertIndex = that.getView("rowsView").getTopVisibleItemIndex(), rowIndex = startInsertIndex; each(that._editData, function (_, editData) { if (!editData.isValid && editData.pageIndex !== that._pageIndex) { editData.pageIndex = that._pageIndex; if (editData.type === "insert") { editData.rowIndex = startInsertIndex; } else { editData.rowIndex = rowIndex; } rowIndex++; } }); }, _needInsertItem: function _needInsertItem(editData) { var result = this.callBase.apply(this, arguments); if (result && !editData.isValid) { result = editData.key.pageIndex === this._pageIndex; } return result; }, processItems: function processItems(items, changeType) { var that = this, i, itemsCount, insertCount = 0, editData = that._editData, dataController = that.getController("data"), getIndexByEditData = function getIndexByEditData(editData, items) { var index = -1, isInsert = editData.type === "insert", key = editData.key; each(items, function (i, item) { if (equalByValue(key, isInsert ? item : dataController.keyOf(item))) { index = i; return false; } }); return index; }, addInValidItem = function addInValidItem(editData) { var data = { key: editData.key }, index = getIndexByEditData(editData, items), rowIndex; if (index >= 0) { return; } editData.rowIndex = editData.rowIndex > itemsCount ? editData.rowIndex % itemsCount : editData.rowIndex; rowIndex = editData.rowIndex; data[INSERT_INDEX] = 1; items.splice(rowIndex, 0, data); insertCount++; }; items = that.callBase(items, changeType); itemsCount = items.length; if (that.getEditMode() === EDIT_MODE_BATCH && changeType !== "prepend" && changeType !== "append") { for (i = 0; i < editData.length; i++) { if (editData[i].type && editData[i].pageIndex === that._pageIndex && editData[i].key.pageIndex !== that._pageIndex) { addInValidItem(editData[i]); } } } return items; }, processDataItem: function processDataItem(item) { var that = this, editIndex, editData, isInserted = item.data[INSERT_INDEX], key = isInserted ? item.data.key : item.key, editMode = that.getEditMode(); if (editMode === EDIT_MODE_BATCH && isInserted && key) { editIndex = gridCoreUtils.getIndexByKey(key, that._editData); if (editIndex >= 0) { editData = that._editData[editIndex]; if (editData.type !== "insert") { item.data = extend(true, {}, editData.oldData, editData.data); item.key = key; } } } that.callBase.apply(that, arguments); }, _afterInsertRow: function _afterInsertRow(options) { var validatingController = this.getController("validating"), invisibleColumns = commonUtils.grep(this.getController("columns").getInvisibleColumns(), function (column) { return !column.isBand; }); if (FORM_BASED_MODES.indexOf(this.getEditMode()) === -1) { each(invisibleColumns, function (_, column) { validatingController.createValidator({ column: column, key: options.key, value: column.calculateCellValue(options.data) }); }); } this.callBase(options); }, _beforeSaveEditData: function _beforeSaveEditData(editData, editIndex) { var that = this, isValid, isFullValid, result = that.callBase.apply(that, arguments), validatingController = that.getController("validating"); if (editData) { isValid = editData.type === "remove" || editData.isValid; if (isValid) { validatingController.removeValidators(editIndex); } result = result || !isValid; } else { isFullValid = validatingController.validate(true); that._updateRowAndPageIndices(); switch (that.getEditMode()) { case EDIT_MODE_CELL: if (!isFullValid) { that._focusEditingCell(); result = true; } break; case EDIT_MODE_BATCH: if (!isFullValid) { that._editRowIndex = -1; that._editColumnIndex = -1; that.getController("data").updateItems(); result = true; } break; case EDIT_MODE_ROW: case EDIT_MODE_POPUP: result = !isFullValid; break; } } return result; }, _beforeEditCell: function _beforeEditCell(rowIndex, columnIndex, item) { var result = this.callBase(rowIndex, columnIndex, item), $cell = this._rowsView._getCellElement(rowIndex, columnIndex), validator = $cell && $cell.data("dxValidator"), value = validator && validator.option("adapter").getValue(); if (this.getEditMode(this) === EDIT_MODE_CELL && (!validator || value !== undefined && validator.validate().isValid)) { return result; } }, _afterSaveEditData: function _afterSaveEditData() { var that = this; each(that._editData, function (_, editData) { that._showErrorRow(editData); }); }, _beforeCancelEditData: function _beforeCancelEditData() { var validatingController = this.getController("validating"); validatingController.removeValidators(); this.callBase(); }, _showErrorRow: function _showErrorRow(editData) { var $popupContent, errorHandling = this.getController("errorHandling"), items = this.getController("data").items(), rowIndex = this.getIndexByKey(editData.key, items); if (!editData.isValid && editData.errorText && rowIndex >= 0) { $popupContent = this.getPopupContent(); errorHandling && errorHandling.renderErrorRow(editData.errorText, rowIndex, $popupContent); } }, updateFieldValue: function updateFieldValue(e) { var that = this, editMode = that.getEditMode(); that.callBase.apply(that, arguments); if (editMode === EDIT_MODE_ROW || editMode === EDIT_MODE_BATCH && e.column.showEditorAlways) { var currentValidator = that.getController("validating").getValidator(); currentValidator && currentValidator.validate(); } }, showHighlighting: function showHighlighting($cell, skipValidation) { var isValid = true, validator; if (!skipValidation) { validator = $cell.data("dxValidator"); if (validator) { isValid = validator.validate().isValid; } } if (isValid) { this.callBase($cell); } }, getEditDataByKey: function getEditDataByKey(key) { return this._editData[gridCoreUtils.getIndexByKey(key, this._editData)]; } }, editorFactory: { _showRevertButton: function _showRevertButton($container, $targetElement) { var that = this; if ($targetElement && $targetElement.length) { return new Tooltip($("<div>").addClass(that.addWidgetPrefix(REVERT_TOOLTIP_CLASS)).appendTo($container), { animation: null, visible: true, target: $targetElement, container: $container, closeOnOutsideClick: false, closeOnTargetScroll: false, boundary: that._rowsView.element(), contentTemplate: function contentTemplate() { return new Button($("<div>").addClass(REVERT_BUTTON_CLASS), { icon: "revert", hint: that.option("editing.texts.validationCancelChanges"), onClick: function onClick() { that._editingController.cancelEditData(); } }).$element(); }, position: { my: "left top", at: "right top", of: $targetElement, offset: "1 0", collision: "flip" } }); } }, _showValidationMessage: function _showValidationMessage($cell, message, alignment, revertTooltip) { var that = this, needRepaint, $highlightContainer = $cell.find("." + CELL_HIGHLIGHT_OUTLINE), isMaterial = themes.isMaterial(), overlayTarget = $highlightContainer.length && !isMaterial ? $highlightContainer : $cell, isOverlayVisible = $cell.find(".dx-dropdowneditor-overlay").is(":visible"), myPosition = isOverlayVisible ? "top right" : "top " + alignment, atPosition = isOverlayVisible ? "top left" : "bottom " + alignment; new Overlay($("<div>").addClass(INVALID_MESSAGE_CLASS).addClass(INVALID_MESSAGE_ALWAYS_CLASS).addClass(that.addWidgetPrefix(WIDGET_INVALID_MESSAGE_CLASS)).text(message).appendTo($cell), { target: overlayTarget, container: $cell, shading: false, width: 'auto', height: 'auto', visible: true, animation: false, propagateOutsideClick: true, closeOnOutsideClick: false, closeOnTargetScroll: false, position: { collision: "flip", boundary: that._rowsView.element(), boundaryOffset: "0 0", my: myPosition, at: atPosition }, onPositioned: function onPositioned(e) { if (!needRepaint) { needRepaint = that._rowsView.updateFreeSpaceRowHeight(); if (needRepaint) { e.component.repaint(); } } that._shiftValidationMessageIfNeed(e.component.$content(), revertTooltip && revertTooltip.$content(), $cell); } }); }, _shiftValidationMessageIfNeed: function _shiftValidationMessageIfNeed($content, $revertContent, $cell) { if (!$revertContent) return; var contentOffset = $content.offset(), revertContentOffset = $revertContent.offset(); if (contentOffset.top === revertContentOffset.top && contentOffset.left + $content.width() > revertContentOffset.left) { var left = $revertContent.width() + PADDING_BETWEEN_TOOLTIPS; $content.css("left", revertContentOffset.left < $cell.offset().left ? -left : left); } }, _getTooltipsSelector: function _getTooltipsSelector() { return ".dx-editor-cell ." + this.addWidgetPrefix(REVERT_TOOLTIP_CLASS) + ", .dx-editor-cell ." + this.addWidgetPrefix(WIDGET_INVALID_MESSAGE_CLASS); }, init: function init() { this.callBase(); this._editingController = this.getController("editing"); this._rowsView = this.getView("rowsView"); }, loseFocus: function loseFocus(skipValidator) { if (!skipValidator) { this.getController("validating").setValidator(null); } this.callBase(); }, focus: function focus($element, hideBorder) { var that = this, $focus = $element && $element.closest(that._getFocusCellSelector()), validator = $focus && ($focus.data("dxValidator") || $element.find("." + that.addWidgetPrefix(VALIDATOR_CLASS)).eq(0).data("dxValidator")), rowOptions = $focus && $focus.closest(".dx-row").data("options"), editData = rowOptions ? that.getController("editing").getEditDataByKey(rowOptions.key) : null, validationResult, $tooltips = $focus && $focus.closest("." + that.addWidgetPrefix(ROWS_VIEW_CLASS)).find(that._getTooltipsSelector()), $cell = $focus && $focus.is("td") ? $focus : null, showValidationMessage = false, revertTooltip, column = $cell && that.getController("columns").getVisibleColumns()[$cell.index()]; if (!arguments.length) return that.callBase(); $tooltips && $tooltips.remove(); if (validator) { that.getController("validating").setValidator(validator); if (validator.option("adapter").getValue() !== undefined) { validationResult = validator.validate(); if (!validationResult.isValid) { hideBorder = true; showValidationMessage = true; } } } if (validationResult && !validationResult.isValid || editData && editData.type === "update") { if (that._editingController.getEditMode() === EDIT_MODE_CELL) { revertTooltip = that._showRevertButton($focus, $cell ? $focus.find("." + CELL_HIGHLIGHT_OUTLINE).first() : $focus); } } if (showValidationMessage && $cell && column && validationResult.brokenRule.message) { that._showValidationMessage($focus, validationResult.brokenRule.message, column.alignment, revertTooltip); } !hideBorder && that._rowsView.element() && that._rowsView.updateFreeSpaceRowHeight(); return that.callBase($element, hideBorder); } } }, views: { rowsView: { updateFreeSpaceRowHeight: function updateFreeSpaceRowHeight($table) { var that = this, $rowElements, $freeSpaceRowElement, $freeSpaceRowElements, $element = that.element(), $tooltipContent = $element && $element.find("." + that.addWidgetPrefix(WIDGET_INVALID_MESSAGE_CLASS) + " .dx-overlay-content"); that.callBase($table); if ($tooltipContent && $tooltipContent.length) { $rowElements = that._getRowElements(); $freeSpaceRowElements = that._getFreeSpaceRowElements($table); $freeSpaceRowElement = $freeSpaceRowElements.first(); if ($freeSpaceRowElement && $rowElements.length === 1 && (!$freeSpaceRowElement.is(":visible") || $tooltipContent.outerHeight() > $freeSpaceRowElement.outerHeight())) { $freeSpaceRowElements.show(); $freeSpaceRowElements.height($tooltipContent.outerHeight()); return true; } } }, _formItemPrepared: function _formItemPrepared(cellOptions, $container) { this.callBase.apply(this, arguments); this.getController("validating").createValidator(cellOptions, $container.find(".dx-widget").first()); }, _cellPrepared: function _cellPrepared($cell, parameters) { this.getController("validating").createValidator(parameters, $cell); this.callBase.apply(this, arguments); } } } } };