UNPKG

10 kBPlain TextView Raw
1import {GridOptionsWrapper} from "../gridOptionsWrapper";
2import {ExpressionService} from "./expressionService";
3import {ColumnController} from "../columnController/columnController";
4import {NewValueParams, ValueGetterParams} from "../entities/colDef";
5import {Autowired, Bean, PostConstruct} from "../context/context";
6import {RowNode} from "../entities/rowNode";
7import {Column} from "../entities/column";
8import {_} from "../utils";
9import {CellValueChangedEvent, Events} from "../events";
10import {EventService} from "../eventService";
11import {ValueCache} from "./valueCache";
12
13@Bean('valueService')
14export class ValueService {
15
16 @Autowired('gridOptionsWrapper') private gridOptionsWrapper: GridOptionsWrapper;
17 @Autowired('expressionService') private expressionService: ExpressionService;
18 @Autowired('columnController') private columnController: ColumnController;
19 @Autowired('eventService') private eventService: EventService;
20 @Autowired('valueCache') private valueCache: ValueCache;
21
22 private cellExpressions: boolean;
23
24 private initialised = false;
25
26 @PostConstruct
27 public init(): void {
28 this.cellExpressions = this.gridOptionsWrapper.isEnableCellExpressions();
29 this.initialised = true;
30 }
31
32 public getValue(column: Column,
33 rowNode: RowNode,
34 forFilter = false,
35 ignoreAggData = false): any {
36
37 // console.log(`turnActive = ${this.turnActive}`);
38
39 // hack - the grid is getting refreshed before this bean gets initialised, race condition.
40 // really should have a way so they get initialised in the right order???
41 if (!this.initialised) { this.init(); }
42
43 // pull these out to make code below easier to read
44 let colDef = column.getColDef();
45 let field = colDef.field;
46 let colId = column.getId();
47 let data = rowNode.data;
48
49 let result: any;
50
51 // if there is a value getter, this gets precedence over a field
52 let groupDataExists = rowNode.groupData && rowNode.groupData[colId] !== undefined;
53 let aggDataExists = !ignoreAggData && rowNode.aggData && rowNode.aggData[colId] !== undefined;
54 if (forFilter && colDef.filterValueGetter) {
55 result = this.executeValueGetter(colDef.filterValueGetter, data, column, rowNode);
56 } else if (groupDataExists) {
57 result = rowNode.groupData[colId];
58 } else if (aggDataExists) {
59 result = rowNode.aggData[colId];
60 } else if (colDef.valueGetter) {
61 result = this.executeValueGetter(colDef.valueGetter, data, column, rowNode);
62 } else if (field && data) {
63 result = _.getValueUsingField(data, field, column.isFieldContainsDots());
64 } else {
65 result = undefined;
66 }
67
68 // the result could be an expression itself, if we are allowing cell values to be expressions
69 if (this.cellExpressions && (typeof result === 'string') && result.indexOf('=') === 0) {
70 let cellValueGetter = result.substring(1);
71 result = this.executeValueGetter(cellValueGetter, data, column, rowNode);
72 }
73
74 return result;
75 }
76
77 public setValue(rowNode: RowNode, colKey: string|Column, newValue: any): void {
78 let column = this.columnController.getPrimaryColumn(colKey);
79
80 if (!rowNode || !column) {
81 return;
82 }
83 // this will only happen if user is trying to paste into a group row, which doesn't make sense
84 // the user should not be trying to paste into group rows
85 let data = rowNode.data;
86 if (_.missing(data)) {
87 rowNode.data = {};
88 }
89
90 // for backwards compatibility we are also retrieving the newValueHandler as well as the valueSetter
91 let {field, newValueHandler, valueSetter} = column.getColDef();
92
93 // need either a field or a newValueHandler for this to work
94 if (_.missing(field) && _.missing(newValueHandler) && _.missing(valueSetter)) {
95 // we don't tell user about newValueHandler, as that is deprecated
96 console.warn(`ag-Grid: you need either field or valueSetter set on colDef for editing to work`);
97 return;
98 }
99
100 let params: NewValueParams = {
101 node: rowNode,
102 data: rowNode.data,
103 oldValue: this.getValue(column, rowNode),
104 newValue: newValue,
105 colDef: column.getColDef(),
106 column: column,
107 api: this.gridOptionsWrapper.getApi(),
108 columnApi: this.gridOptionsWrapper.getColumnApi(),
109 context: this.gridOptionsWrapper.getContext()
110 };
111
112 params.newValue = newValue;
113
114 let valueWasDifferent: boolean;
115 if (_.exists(newValueHandler)) {
116 valueWasDifferent = newValueHandler(params);
117 } else if (_.exists(valueSetter)) {
118 valueWasDifferent = this.expressionService.evaluate(valueSetter, params);
119 } else {
120 valueWasDifferent = this.setValueUsingField(data, field, newValue, column.isFieldContainsDots());
121 }
122
123 // in case user forgot to return something (possible if they are not using TypeScript
124 // and just forgot, or using an old newValueHandler we didn't always expect a return
125 // value here), we default the return value to true, so we always refresh.
126 if (valueWasDifferent === undefined) {
127 valueWasDifferent = true;
128 }
129
130 // if no change to the value, then no need to do the updating, or notifying via events.
131 // otherwise the user could be tabbing around the grid, and cellValueChange would get called
132 // all the time.
133 if (!valueWasDifferent) { return; }
134
135 // reset quick filter on this row
136 rowNode.resetQuickFilterAggregateText();
137
138 this.valueCache.onDataChanged();
139
140 params.newValue = this.getValue(column, rowNode);
141
142 if (typeof column.getColDef().onCellValueChanged === 'function') {
143 // to make callback async, do in a timeout
144 setTimeout( ()=> column.getColDef().onCellValueChanged(params), 0);
145 }
146
147 let event: CellValueChangedEvent = {
148 type: Events.EVENT_CELL_VALUE_CHANGED,
149 event: null,
150 rowIndex: rowNode.rowIndex,
151 rowPinned: rowNode.rowPinned,
152 column: params.column,
153 api: params.api,
154 colDef: params.colDef,
155 columnApi: params.columnApi,
156 context: params.context,
157 data: rowNode.data,
158 node: rowNode,
159 oldValue: params.oldValue,
160 newValue: params.newValue,
161 value: params.newValue
162 };
163
164 this.eventService.dispatchEvent(event);
165 }
166
167 private setValueUsingField(data: any, field: string, newValue: any, isFieldContainsDots: boolean): boolean {
168 // if no '.', then it's not a deep value
169 let valuesAreSame: boolean;
170 if (!isFieldContainsDots) {
171 data[field] = newValue;
172 } else {
173 // otherwise it is a deep value, so need to dig for it
174 let fieldPieces = field.split('.');
175 let currentObject = data;
176 while (fieldPieces.length > 0 && currentObject) {
177 let fieldPiece = fieldPieces.shift();
178 if (fieldPieces.length === 0) {
179 currentObject[fieldPiece] = newValue;
180 } else {
181 currentObject = currentObject[fieldPiece];
182 }
183 }
184 }
185 return !valuesAreSame;
186 }
187
188 private executeValueGetter(filterValueGetter: string | Function, data: any, column: Column, rowNode: RowNode): any {
189
190 let colId = column.getId();
191
192 // if inside the same turn, just return back the value we got last time
193 let valueFromCache = this.valueCache.getValue(rowNode, colId);
194
195 if (valueFromCache!==undefined) {
196 return valueFromCache;
197 }
198
199 let params: ValueGetterParams = {
200 data: data,
201 node: rowNode,
202 column: column,
203 colDef: column.getColDef(),
204 api: this.gridOptionsWrapper.getApi(),
205 columnApi: this.gridOptionsWrapper.getColumnApi(),
206 context: this.gridOptionsWrapper.getContext(),
207 getValue: this.getValueCallback.bind(this, rowNode)
208 };
209
210 let result = this.expressionService.evaluate(filterValueGetter, params);
211
212 // if a turn is active, store the value in case the grid asks for it again
213 this.valueCache.setValue(rowNode, colId, result);
214
215 return result;
216 }
217
218 private getValueCallback(node: RowNode, field: string): any {
219 let otherColumn = this.columnController.getPrimaryColumn(field);
220 if (otherColumn) {
221 return this.getValue(otherColumn, node);
222 } else {
223 return null;
224 }
225 }
226
227 // used by row grouping and pivot, to get key for a row. col can be a pivot col or a row grouping col
228 public getKeyForNode(col: Column, rowNode: RowNode): any {
229 let value = this.getValue(col, rowNode);
230 let result: any;
231 let keyCreator = col.getColDef().keyCreator;
232
233 if (keyCreator) {
234 result = keyCreator({value: value});
235 } else {
236 result = value;
237 }
238
239 // if already a string, or missing, just return it
240 if (typeof result === 'string' || result===null || result===undefined) { return result; }
241
242 result = String(result);
243
244 if (result==='[object Object]') {
245 _.doOnce( ()=> {
246 console.warn('ag-Grid: a column you are grouping or pivoting by has objects as values. If you want to group by complex objects then either a) use a colDef.keyCreator (se ag-Grid docs) or b) to toString() on the object to return a key');
247 }, 'getKeyForNode - warn about [object,object]');
248 }
249
250 return result;
251 }
252
253}