| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715 |
- /*!
- * ui-grid - v4.4.6 - 2018-04-06
- * Copyright (c) 2018 ; License: MIT
- */
- (function () {
- 'use strict';
- /**
- * @ngdoc overview
- * @name ui.grid.rowEdit
- * @description
- *
- * # ui.grid.rowEdit
- *
- * <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 extends the edit feature to provide tracking and saving of rows
- * of data. The tutorial provides more information on how this feature is best
- * used {@link tutorial/205_row_editable here}.
- * <br/>
- * This feature depends on usage of the ui-grid-edit feature, and also benefits
- * from use of ui-grid-cellNav to provide the full spreadsheet-like editing
- * experience
- *
- */
- var module = angular.module('ui.grid.rowEdit', ['ui.grid', 'ui.grid.edit', 'ui.grid.cellNav']);
- /**
- * @ngdoc object
- * @name ui.grid.rowEdit.constant:uiGridRowEditConstants
- *
- * @description constants available in row edit module
- */
- module.constant('uiGridRowEditConstants', {
- });
- /**
- * @ngdoc service
- * @name ui.grid.rowEdit.service:uiGridRowEditService
- *
- * @description Services for row editing features
- */
- module.service('uiGridRowEditService', ['$interval', '$q', 'uiGridConstants', 'uiGridRowEditConstants', 'gridUtil',
- function ($interval, $q, uiGridConstants, uiGridRowEditConstants, gridUtil) {
- var service = {
- initializeGrid: function (scope, grid) {
- /**
- * @ngdoc object
- * @name ui.grid.rowEdit.api:PublicApi
- *
- * @description Public Api for rowEdit feature
- */
- grid.rowEdit = {};
- var publicApi = {
- events: {
- rowEdit: {
- /**
- * @ngdoc event
- * @eventOf ui.grid.rowEdit.api:PublicApi
- * @name saveRow
- * @description raised when a row is ready for saving. Once your
- * row has saved you may need to use angular.extend to update the
- * data entity with any changed data from your save (for example,
- * lock version information if you're using optimistic locking,
- * or last update time/user information).
- *
- * Your method should call setSavePromise somewhere in the body before
- * returning control. The feature will then wait, with the gridRow greyed out
- * whilst this promise is being resolved.
- *
- * <pre>
- * gridApi.rowEdit.on.saveRow(scope,function(rowEntity){})
- * </pre>
- * and somewhere within the event handler:
- * <pre>
- * gridApi.rowEdit.setSavePromise( rowEntity, savePromise)
- * </pre>
- * @param {object} rowEntity the options.data element that was edited
- * @returns {promise} Your saveRow method should return a promise, the
- * promise should either be resolved (implying successful save), or
- * rejected (implying an error).
- */
- saveRow: function (rowEntity) {
- }
- }
- },
- methods: {
- rowEdit: {
- /**
- * @ngdoc method
- * @methodOf ui.grid.rowEdit.api:PublicApi
- * @name setSavePromise
- * @description Sets the promise associated with the row save, mandatory that
- * the saveRow event handler calls this method somewhere before returning.
- * <pre>
- * gridApi.rowEdit.setSavePromise(rowEntity, savePromise)
- * </pre>
- * @param {object} rowEntity a data row from the grid for which a save has
- * been initiated
- * @param {promise} savePromise the promise that will be resolved when the
- * save is successful, or rejected if the save fails
- *
- */
- setSavePromise: function ( rowEntity, savePromise) {
- service.setSavePromise(grid, rowEntity, savePromise);
- },
- /**
- * @ngdoc method
- * @methodOf ui.grid.rowEdit.api:PublicApi
- * @name getDirtyRows
- * @description Returns all currently dirty rows
- * <pre>
- * gridApi.rowEdit.getDirtyRows(grid)
- * </pre>
- * @returns {array} An array of gridRows that are currently dirty
- *
- */
- getDirtyRows: function () {
- return grid.rowEdit.dirtyRows ? grid.rowEdit.dirtyRows : [];
- },
- /**
- * @ngdoc method
- * @methodOf ui.grid.rowEdit.api:PublicApi
- * @name getErrorRows
- * @description Returns all currently errored rows
- * <pre>
- * gridApi.rowEdit.getErrorRows(grid)
- * </pre>
- * @returns {array} An array of gridRows that are currently in error
- *
- */
- getErrorRows: function () {
- return grid.rowEdit.errorRows ? grid.rowEdit.errorRows : [];
- },
- /**
- * @ngdoc method
- * @methodOf ui.grid.rowEdit.api:PublicApi
- * @name flushDirtyRows
- * @description Triggers a save event for all currently dirty rows, could
- * be used where user presses a save button or navigates away from the page
- * <pre>
- * gridApi.rowEdit.flushDirtyRows(grid)
- * </pre>
- * @returns {promise} a promise that represents the aggregate of all
- * of the individual save promises - i.e. it will be resolved when all
- * the individual save promises have been resolved.
- *
- */
- flushDirtyRows: function () {
- return service.flushDirtyRows(grid);
- },
- /**
- * @ngdoc method
- * @methodOf ui.grid.rowEdit.api:PublicApi
- * @name setRowsDirty
- * @description Sets each of the rows passed in dataRows
- * to be dirty. note that if you have only just inserted the
- * rows into your data you will need to wait for a $digest cycle
- * before the gridRows are present - so often you would wrap this
- * call in a $interval or $timeout
- * <pre>
- * $interval( function() {
- * gridApi.rowEdit.setRowsDirty(myDataRows);
- * }, 0, 1);
- * </pre>
- * @param {array} dataRows the data entities for which the gridRows
- * should be set dirty.
- *
- */
- setRowsDirty: function ( dataRows) {
- service.setRowsDirty(grid, dataRows);
- },
- /**
- * @ngdoc method
- * @methodOf ui.grid.rowEdit.api:PublicApi
- * @name setRowsClean
- * @description Sets each of the rows passed in dataRows
- * to be clean, removing them from the dirty cache and the error cache,
- * and clearing the error flag and the dirty flag
- * <pre>
- * var gridRows = $scope.gridApi.rowEdit.getDirtyRows();
- * var dataRows = gridRows.map( function( gridRow ) { return gridRow.entity; });
- * $scope.gridApi.rowEdit.setRowsClean( dataRows );
- * </pre>
- * @param {array} dataRows the data entities for which the gridRows
- * should be set clean.
- *
- */
- setRowsClean: function ( dataRows) {
- service.setRowsClean(grid, dataRows);
- }
- }
- }
- };
- grid.api.registerEventsFromObject(publicApi.events);
- grid.api.registerMethodsFromObject(publicApi.methods);
- grid.api.core.on.renderingComplete( scope, function ( gridApi ) {
- grid.api.edit.on.afterCellEdit( scope, service.endEditCell );
- grid.api.edit.on.beginCellEdit( scope, service.beginEditCell );
- grid.api.edit.on.cancelCellEdit( scope, service.cancelEditCell );
- if ( grid.api.cellNav ) {
- grid.api.cellNav.on.navigate( scope, service.navigate );
- }
- });
- },
- defaultGridOptions: function (gridOptions) {
- /**
- * @ngdoc object
- * @name ui.grid.rowEdit.api:GridOptions
- *
- * @description Options for configuring the rowEdit feature, these are available to be
- * set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
- */
- },
- /**
- * @ngdoc method
- * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
- * @name saveRow
- * @description Returns a function that saves the specified row from the grid,
- * and returns a promise
- * @param {object} grid the grid for which dirty rows should be flushed
- * @param {GridRow} gridRow the row that should be saved
- * @returns {function} the saveRow function returns a function. That function
- * in turn, when called, returns a promise relating to the save callback
- */
- saveRow: function ( grid, gridRow ) {
- var self = this;
- return function() {
- gridRow.isSaving = true;
- if ( gridRow.rowEditSavePromise ){
- // don't save the row again if it's already saving - that causes stale object exceptions
- return gridRow.rowEditSavePromise;
- }
- var promise = grid.api.rowEdit.raise.saveRow( gridRow.entity );
- if ( gridRow.rowEditSavePromise ){
- gridRow.rowEditSavePromise.then( self.processSuccessPromise( grid, gridRow ), self.processErrorPromise( grid, gridRow ));
- } else {
- gridUtil.logError( 'A promise was not returned when saveRow event was raised, either nobody is listening to event, or event handler did not return a promise' );
- }
- return promise;
- };
- },
- /**
- * @ngdoc method
- * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
- * @name setSavePromise
- * @description Sets the promise associated with the row save, mandatory that
- * the saveRow event handler calls this method somewhere before returning.
- * <pre>
- * gridApi.rowEdit.setSavePromise(grid, rowEntity)
- * </pre>
- * @param {object} grid the grid for which dirty rows should be returned
- * @param {object} rowEntity a data row from the grid for which a save has
- * been initiated
- * @param {promise} savePromise the promise that will be resolved when the
- * save is successful, or rejected if the save fails
- *
- */
- setSavePromise: function (grid, rowEntity, savePromise) {
- var gridRow = grid.getRow( rowEntity );
- gridRow.rowEditSavePromise = savePromise;
- },
- /**
- * @ngdoc method
- * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
- * @name processSuccessPromise
- * @description Returns a function that processes the successful
- * resolution of a save promise
- * @param {object} grid the grid for which the promise should be processed
- * @param {GridRow} gridRow the row that has been saved
- * @returns {function} the success handling function
- */
- processSuccessPromise: function ( grid, gridRow ) {
- var self = this;
- return function() {
- delete gridRow.isSaving;
- delete gridRow.isDirty;
- delete gridRow.isError;
- delete gridRow.rowEditSaveTimer;
- delete gridRow.rowEditSavePromise;
- self.removeRow( grid.rowEdit.errorRows, gridRow );
- self.removeRow( grid.rowEdit.dirtyRows, gridRow );
- };
- },
- /**
- * @ngdoc method
- * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
- * @name processErrorPromise
- * @description Returns a function that processes the failed
- * resolution of a save promise
- * @param {object} grid the grid for which the promise should be processed
- * @param {GridRow} gridRow the row that is now in error
- * @returns {function} the error handling function
- */
- processErrorPromise: function ( grid, gridRow ) {
- return function() {
- delete gridRow.isSaving;
- delete gridRow.rowEditSaveTimer;
- delete gridRow.rowEditSavePromise;
- gridRow.isError = true;
- if (!grid.rowEdit.errorRows){
- grid.rowEdit.errorRows = [];
- }
- if (!service.isRowPresent( grid.rowEdit.errorRows, gridRow ) ){
- grid.rowEdit.errorRows.push( gridRow );
- }
- };
- },
- /**
- * @ngdoc method
- * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
- * @name removeRow
- * @description Removes a row from a cache of rows - either
- * grid.rowEdit.errorRows or grid.rowEdit.dirtyRows. If the row
- * is not present silently does nothing.
- * @param {array} rowArray the array from which to remove the row
- * @param {GridRow} gridRow the row that should be removed
- */
- removeRow: function( rowArray, removeGridRow ){
- if (typeof(rowArray) === 'undefined' || rowArray === null){
- return;
- }
- rowArray.forEach( function( gridRow, index ){
- if ( gridRow.uid === removeGridRow.uid ){
- rowArray.splice( index, 1);
- }
- });
- },
- /**
- * @ngdoc method
- * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
- * @name isRowPresent
- * @description Checks whether a row is already present
- * in the given array
- * @param {array} rowArray the array in which to look for the row
- * @param {GridRow} gridRow the row that should be looked for
- */
- isRowPresent: function( rowArray, removeGridRow ){
- var present = false;
- rowArray.forEach( function( gridRow, index ){
- if ( gridRow.uid === removeGridRow.uid ){
- present = true;
- }
- });
- return present;
- },
- /**
- * @ngdoc method
- * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
- * @name flushDirtyRows
- * @description Triggers a save event for all currently dirty rows, could
- * be used where user presses a save button or navigates away from the page
- * <pre>
- * gridApi.rowEdit.flushDirtyRows(grid)
- * </pre>
- * @param {object} grid the grid for which dirty rows should be flushed
- * @returns {promise} a promise that represents the aggregate of all
- * of the individual save promises - i.e. it will be resolved when all
- * the individual save promises have been resolved.
- *
- */
- flushDirtyRows: function(grid){
- var promises = [];
- grid.api.rowEdit.getDirtyRows().forEach( function( gridRow ){
- service.cancelTimer( grid, gridRow );
- service.saveRow( grid, gridRow )();
- promises.push( gridRow.rowEditSavePromise );
- });
- return $q.all( promises );
- },
- /**
- * @ngdoc method
- * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
- * @name endEditCell
- * @description Receives an afterCellEdit event from the edit function,
- * and sets flags as appropriate. Only the rowEntity parameter
- * is processed, although other params are available. Grid
- * is automatically provided by the gridApi.
- * @param {object} rowEntity the data entity for which the cell
- * was edited
- */
- endEditCell: function( rowEntity, colDef, newValue, previousValue ){
- var grid = this.grid;
- var gridRow = grid.getRow( rowEntity );
- if ( !gridRow ){ gridUtil.logError( 'Unable to find rowEntity in grid data, dirty flag cannot be set' ); return; }
- if ( newValue !== previousValue || gridRow.isDirty ){
- if ( !grid.rowEdit.dirtyRows ){
- grid.rowEdit.dirtyRows = [];
- }
- if ( !gridRow.isDirty ){
- gridRow.isDirty = true;
- grid.rowEdit.dirtyRows.push( gridRow );
- }
- delete gridRow.isError;
- service.considerSetTimer( grid, gridRow );
- }
- },
- /**
- * @ngdoc method
- * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
- * @name beginEditCell
- * @description Receives a beginCellEdit event from the edit function,
- * and cancels any rowEditSaveTimers if present, as the user is still editing
- * this row. Only the rowEntity parameter
- * is processed, although other params are available. Grid
- * is automatically provided by the gridApi.
- * @param {object} rowEntity the data entity for which the cell
- * editing has commenced
- */
- beginEditCell: function( rowEntity, colDef ){
- var grid = this.grid;
- var gridRow = grid.getRow( rowEntity );
- if ( !gridRow ){ gridUtil.logError( 'Unable to find rowEntity in grid data, timer cannot be cancelled' ); return; }
- service.cancelTimer( grid, gridRow );
- },
- /**
- * @ngdoc method
- * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
- * @name cancelEditCell
- * @description Receives a cancelCellEdit event from the edit function,
- * and if the row was already dirty, restarts the save timer. If the row
- * was not already dirty, then it's not dirty now either and does nothing.
- *
- * Only the rowEntity parameter
- * is processed, although other params are available. Grid
- * is automatically provided by the gridApi.
- *
- * @param {object} rowEntity the data entity for which the cell
- * editing was cancelled
- */
- cancelEditCell: function( rowEntity, colDef ){
- var grid = this.grid;
- var gridRow = grid.getRow( rowEntity );
- if ( !gridRow ){ gridUtil.logError( 'Unable to find rowEntity in grid data, timer cannot be set' ); return; }
- service.considerSetTimer( grid, gridRow );
- },
- /**
- * @ngdoc method
- * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
- * @name navigate
- * @description cellNav tells us that the selected cell has changed. If
- * the new row had a timer running, then stop it similar to in a beginCellEdit
- * call. If the old row is dirty and not the same as the new row, then
- * start a timer on it.
- * @param {object} newRowCol the row and column that were selected
- * @param {object} oldRowCol the row and column that was left
- *
- */
- navigate: function( newRowCol, oldRowCol ){
- var grid = this.grid;
- if ( newRowCol.row.rowEditSaveTimer ){
- service.cancelTimer( grid, newRowCol.row );
- }
- if ( oldRowCol && oldRowCol.row && oldRowCol.row !== newRowCol.row ){
- service.considerSetTimer( grid, oldRowCol.row );
- }
- },
- /**
- * @ngdoc property
- * @propertyOf ui.grid.rowEdit.api:GridOptions
- * @name rowEditWaitInterval
- * @description How long the grid should wait for another change on this row
- * before triggering a save (in milliseconds). If set to -1, then saves are
- * never triggered by timer (implying that the user will call flushDirtyRows()
- * manually)
- *
- * @example
- * Setting the wait interval to 4 seconds
- * <pre>
- * $scope.gridOptions = { rowEditWaitInterval: 4000 }
- * </pre>
- *
- */
- /**
- * @ngdoc method
- * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
- * @name considerSetTimer
- * @description Consider setting a timer on this row (if it is dirty). if there is a timer running
- * on the row and the row isn't currently saving, cancel it, using cancelTimer, then if the row is
- * dirty and not currently saving then set a new timer
- * @param {object} grid the grid for which we are processing
- * @param {GridRow} gridRow the row for which the timer should be adjusted
- *
- */
- considerSetTimer: function( grid, gridRow ){
- service.cancelTimer( grid, gridRow );
- if ( gridRow.isDirty && !gridRow.isSaving ){
- if ( grid.options.rowEditWaitInterval !== -1 ){
- var waitTime = grid.options.rowEditWaitInterval ? grid.options.rowEditWaitInterval : 2000;
- gridRow.rowEditSaveTimer = $interval( service.saveRow( grid, gridRow ), waitTime, 1);
- }
- }
- },
- /**
- * @ngdoc method
- * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
- * @name cancelTimer
- * @description cancel the $interval for any timer running on this row
- * then delete the timer itself
- * @param {object} grid the grid for which we are processing
- * @param {GridRow} gridRow the row for which the timer should be adjusted
- *
- */
- cancelTimer: function( grid, gridRow ){
- if ( gridRow.rowEditSaveTimer && !gridRow.isSaving ){
- $interval.cancel(gridRow.rowEditSaveTimer);
- delete gridRow.rowEditSaveTimer;
- }
- },
- /**
- * @ngdoc method
- * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
- * @name setRowsDirty
- * @description Sets each of the rows passed in dataRows
- * to be dirty. note that if you have only just inserted the
- * rows into your data you will need to wait for a $digest cycle
- * before the gridRows are present - so often you would wrap this
- * call in a $interval or $timeout
- * <pre>
- * $interval( function() {
- * gridApi.rowEdit.setRowsDirty( myDataRows);
- * }, 0, 1);
- * </pre>
- * @param {object} grid the grid for which rows should be set dirty
- * @param {array} dataRows the data entities for which the gridRows
- * should be set dirty.
- *
- */
- setRowsDirty: function( grid, myDataRows ) {
- var gridRow;
- myDataRows.forEach( function( value, index ){
- gridRow = grid.getRow( value );
- if ( gridRow ){
- if ( !grid.rowEdit.dirtyRows ){
- grid.rowEdit.dirtyRows = [];
- }
- if ( !gridRow.isDirty ){
- gridRow.isDirty = true;
- grid.rowEdit.dirtyRows.push( gridRow );
- }
- delete gridRow.isError;
- service.considerSetTimer( grid, gridRow );
- } else {
- gridUtil.logError( "requested row not found in rowEdit.setRowsDirty, row was: " + value );
- }
- });
- },
- /**
- * @ngdoc method
- * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
- * @name setRowsClean
- * @description Sets each of the rows passed in dataRows
- * to be clean, clearing the dirty flag and the error flag, and removing
- * the rows from the dirty and error caches.
- * @param {object} grid the grid for which rows should be set clean
- * @param {array} dataRows the data entities for which the gridRows
- * should be set clean.
- *
- */
- setRowsClean: function( grid, myDataRows ) {
- var gridRow;
- myDataRows.forEach( function( value, index ){
- gridRow = grid.getRow( value );
- if ( gridRow ){
- delete gridRow.isDirty;
- service.removeRow( grid.rowEdit.dirtyRows, gridRow );
- service.cancelTimer( grid, gridRow );
- delete gridRow.isError;
- service.removeRow( grid.rowEdit.errorRows, gridRow );
- } else {
- gridUtil.logError( "requested row not found in rowEdit.setRowsClean, row was: " + value );
- }
- });
- }
- };
- return service;
- }]);
- /**
- * @ngdoc directive
- * @name ui.grid.rowEdit.directive:uiGridEdit
- * @element div
- * @restrict A
- *
- * @description Adds row editing features to the ui-grid-edit directive.
- *
- */
- module.directive('uiGridRowEdit', ['gridUtil', 'uiGridRowEditService', 'uiGridEditConstants',
- function (gridUtil, uiGridRowEditService, uiGridEditConstants) {
- return {
- replace: true,
- priority: 0,
- require: '^uiGrid',
- scope: false,
- compile: function () {
- return {
- pre: function ($scope, $elm, $attrs, uiGridCtrl) {
- uiGridRowEditService.initializeGrid($scope, uiGridCtrl.grid);
- },
- post: function ($scope, $elm, $attrs, uiGridCtrl) {
- }
- };
- }
- };
- }]);
- /**
- * @ngdoc directive
- * @name ui.grid.rowEdit.directive:uiGridViewport
- * @element div
- *
- * @description Stacks on top of ui.grid.uiGridViewport to alter the attributes used
- * for the grid row to allow coloring of saving and error rows
- */
- module.directive('uiGridViewport',
- ['$compile', 'uiGridConstants', 'gridUtil', '$parse',
- function ($compile, uiGridConstants, gridUtil, $parse) {
- return {
- priority: -200, // run after default directive
- scope: false,
- compile: function ($elm, $attrs) {
- var rowRepeatDiv = angular.element($elm.children().children()[0]);
- var existingNgClass = rowRepeatDiv.attr("ng-class");
- var newNgClass = '';
- if ( existingNgClass ) {
- newNgClass = existingNgClass.slice(0, -1) + ", 'ui-grid-row-dirty': row.isDirty, 'ui-grid-row-saving': row.isSaving, 'ui-grid-row-error': row.isError}";
- } else {
- newNgClass = "{'ui-grid-row-dirty': row.isDirty, 'ui-grid-row-saving': row.isSaving, 'ui-grid-row-error': row.isError}";
- }
- rowRepeatDiv.attr("ng-class", newNgClass);
- return {
- pre: function ($scope, $elm, $attrs, controllers) {
- },
- post: function ($scope, $elm, $attrs, controllers) {
- }
- };
- }
- };
- }]);
- })();
|