@bexis2/bexis2-core-ui 0.4.95 → 0.4.96

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,4 +1,11 @@
1
1
  # bexis-core-ui
2
+
3
+ ## 0.4.96
4
+ - ServerSideTable
5
+ - Add support for boolean
6
+ - Fix missing value handling
7
+
8
+
2
9
  ## 0.4.95
3
10
  - Input
4
11
  - Add focus in/out for element
@@ -3,6 +3,11 @@ export let config;
3
3
  let fetched = false;
4
4
  const data = config.data;
5
5
  $: if ($data.length > 0) fetched = true;
6
+ if (typeof BigInt !== "undefined" && !BigInt.prototype.toJSON) {
7
+ BigInt.prototype.toJSON = function() {
8
+ return this.toString();
9
+ };
10
+ }
6
11
  </script>
7
12
 
8
13
  {#key fetched}
@@ -16,7 +16,7 @@ let type = "string";
16
16
  let isDate = false;
17
17
  let dropdowns = [];
18
18
  $values.forEach((item) => {
19
- if (item) {
19
+ if (item !== null && item !== void 0) {
20
20
  type = typeof (toFilterableValueFn ? toFilterableValueFn(item) : item);
21
21
  if (type === "object") {
22
22
  if (toFilterableValueFn && toFilterableValueFn(item) instanceof Date) {
@@ -105,6 +105,16 @@ const options = {
105
105
  value: FilterOptionsEnum.no,
106
106
  label: "Is not on"
107
107
  }
108
+ ],
109
+ boolean: [
110
+ {
111
+ value: FilterOptionsEnum.e,
112
+ label: "Is equal to"
113
+ },
114
+ {
115
+ value: FilterOptionsEnum.ne,
116
+ label: "Is not equal to"
117
+ }
108
118
  ]
109
119
  };
110
120
  const popupId = `${tableId}-${id}`;
@@ -129,14 +139,10 @@ const getMissingValue = (value) => {
129
139
  };
130
140
  const optionChangeHandler = (e, index) => {
131
141
  delete $filters[id][dropdowns[index].option];
142
+ const val = dropdowns[index].value;
132
143
  $filters[id] = {
133
144
  ...$filters[id],
134
- [e.target.value]: (
135
- // type === 'number'
136
- // ?
137
- getMissingValue(dropdowns[index].value)
138
- )
139
- // : dropdowns[index].value
145
+ [e.target.value]: type === "boolean" ? val === "" || val === void 0 ? void 0 : getMissingValue(val) === "true" || getMissingValue(val) === true : getMissingValue(val)
140
146
  };
141
147
  $filters = $filters;
142
148
  dropdowns[index] = {
@@ -154,11 +160,7 @@ const valueChangeHandler = (e, index) => {
154
160
  ...$filters,
155
161
  [id]: {
156
162
  ...$filters[id],
157
- [dropdowns[index].option]: (
158
- // type === 'number' ?
159
- getMissingValue(e.target.value)
160
- )
161
- // : dropdowns[index].value
163
+ [dropdowns[index].option]: type === "boolean" ? e.target.value === "" ? void 0 : getMissingValue(e.target.value) === "true" || getMissingValue(e.target.value) === true : getMissingValue(e.target.value)
162
164
  }
163
165
  };
164
166
  };
@@ -265,6 +267,17 @@ onMount(() => {
265
267
  bind:value={dropdown.value}
266
268
  aria-label="Filter value"
267
269
  />
270
+ {:else if type === 'boolean'}
271
+ <select
272
+ class="select border border-primary-500 text-sm p-1"
273
+ on:change={(e) => valueChangeHandler(e, index)}
274
+ bind:value={dropdown.value}
275
+ aria-label="Filter value"
276
+ >
277
+ <option value="" disabled selected>Select value...</option>
278
+ <option value="true">True</option>
279
+ <option value="false">False</option>
280
+ </select>
268
281
  {:else}
269
282
  <input
270
283
  type="date"
@@ -16,8 +16,9 @@ let type = "string";
16
16
  let isDate = false;
17
17
  let dropdowns = [];
18
18
  $values.forEach((item) => {
19
- if (item) {
20
- type = typeof (toFilterableValueFn ? toFilterableValueFn(item) : item);
19
+ if (item !== null && item !== void 0) {
20
+ const rawType = typeof (toFilterableValueFn ? toFilterableValueFn(item) : item);
21
+ type = rawType === "bigint" ? "number" : rawType;
21
22
  if (type === "object") {
22
23
  if ((toFilterableValueFn ? toFilterableValueFn(item) : item) instanceof Date) {
23
24
  isDate = true;
@@ -104,6 +105,16 @@ const options = {
104
105
  // value: FilterOptionsEnum.no,
105
106
  // label: 'Is not on'
106
107
  // }
108
+ ],
109
+ boolean: [
110
+ {
111
+ value: FilterOptionsEnum.e,
112
+ label: "Is equal to"
113
+ },
114
+ {
115
+ value: FilterOptionsEnum.ne,
116
+ label: "Is not equal to"
117
+ }
107
118
  ]
108
119
  };
109
120
  const popupId = `${tableId}-${id}`;
@@ -114,7 +125,8 @@ const popupFeatured = {
114
125
  };
115
126
  const stringValues = $values.map((item) => toStringFn ? toStringFn(item) : item);
116
127
  const missingValues = stringValues.reduce((acc, item, index) => {
117
- acc[typeof item === "string" ? item.toLowerCase() : item] = $values[index];
128
+ const strRepresentation = String(item).toLowerCase();
129
+ acc[strRepresentation] = $values[index];
118
130
  return acc;
119
131
  }, {});
120
132
  const getMissingValue = (value) => {
@@ -122,9 +134,10 @@ const getMissingValue = (value) => {
122
134
  };
123
135
  const optionChangeHandler = (e, index) => {
124
136
  delete $filters[id][dropdowns[index].option];
137
+ const val = dropdowns[index].value;
125
138
  $filters[id] = {
126
139
  ...$filters[id],
127
- [e.target.value]: getMissingValue(dropdowns[index].value)
140
+ [e.target.value]: type === "boolean" ? val === "" || val === void 0 ? void 0 : getMissingValue(val) === "true" || getMissingValue(val) === true : getMissingValue(dropdowns[index].value)
128
141
  };
129
142
  $filters = $filters;
130
143
  dropdowns[index] = {
@@ -142,7 +155,28 @@ const valueChangeHandler = (e, index) => {
142
155
  ...$filters,
143
156
  [id]: {
144
157
  ...$filters[id],
145
- [dropdowns[index].option]: type === "number" ? parseFloat(getMissingValue(e.target.value)) || void 0 : getMissingValue(e.target.value)
158
+ [dropdowns[index].option]: (() => {
159
+ const rawVal = getMissingValue(e.target.value);
160
+ if (rawVal === "" || rawVal === void 0 || rawVal === null) {
161
+ return void 0;
162
+ }
163
+ if (type === "number") {
164
+ const cleanStr = String(rawVal).trim();
165
+ const isBigInt = /^\d+$/.test(cleanStr) && cleanStr.length >= 16;
166
+ if (isBigInt) {
167
+ try {
168
+ return BigInt(cleanStr);
169
+ } catch {
170
+ return cleanStr;
171
+ }
172
+ }
173
+ return parseFloat(cleanStr) || void 0;
174
+ }
175
+ if (type === "boolean") {
176
+ return rawVal === "true" || rawVal === true;
177
+ }
178
+ return rawVal;
179
+ })()
146
180
  }
147
181
  };
148
182
  };
@@ -241,7 +275,7 @@ $: console.log($filters);
241
275
  on:click|preventDefault={() => removeFilter(dropdown.option)}
242
276
  on:keydown|preventDefault={() => removeFilter(dropdown.option)}
243
277
  aria-label="Remove filter"
244
- >
278
+ >
245
279
  <Fa icon={faXmark} />
246
280
  </div>
247
281
  {/if}
@@ -262,6 +296,17 @@ $: console.log($filters);
262
296
  bind:value={dropdown.value}
263
297
  aria-label="Filter value"
264
298
  />
299
+ {:else if type === 'boolean'}
300
+ <select
301
+ class="select border border-primary-500 text-sm p-1"
302
+ on:change={(e) => valueChangeHandler(e, index)}
303
+ bind:value={dropdown.value}
304
+ aria-label="Filter value"
305
+ >
306
+ <option value="" disabled selected>Select value...</option>
307
+ <option value="true">True</option>
308
+ <option value="false">False</option>
309
+ </select>
265
310
  {:else}
266
311
  <input
267
312
  type="date"
@@ -67,20 +67,35 @@ const applyFilter = (filterValue, value, filterFn) => {
67
67
  value: filterValue[key]
68
68
  }));
69
69
  filters.forEach((filter) => {
70
- result = result && (filter.value ? filterFn(filter.option, filter.value, value) : true);
70
+ result = result && (filter.value !== undefined && filter.value !== '' && filter.value !== null ? filterFn(filter.option, filter.value, value) : true);
71
71
  });
72
72
  return result;
73
73
  };
74
+ const booleanFilter = (filterOption, filterValue, value) => {
75
+ const filterBool = typeof filterValue === 'string' ? filterValue === 'true' : !!filterValue;
76
+ const valBool = !!value;
77
+ switch (filterOption) {
78
+ case FilterOptionsEnum.e:
79
+ return valBool === filterBool;
80
+ case FilterOptionsEnum.ne:
81
+ return valBool !== filterBool;
82
+ default:
83
+ return false;
84
+ }
85
+ };
74
86
  export const columnFilter = ({ filterValue, value }) => {
75
87
  if (typeof value === 'object' && value instanceof Date) {
76
88
  return applyFilter(filterValue, value, dateFilter);
77
89
  }
78
- else if (typeof value === 'number') {
90
+ else if (typeof value === 'number' || typeof value === 'bigint') {
79
91
  return applyFilter(filterValue, value, numberFilter);
80
92
  }
81
93
  else if (typeof value === 'string') {
82
94
  return applyFilter(filterValue, value, textFilter);
83
95
  }
96
+ else if (typeof value === 'boolean') {
97
+ return applyFilter(filterValue, value, booleanFilter);
98
+ }
84
99
  return false;
85
100
  };
86
101
  export const searchFilter = ({ filterValue, value }) => {
@@ -176,13 +176,27 @@ export const updateTable = async (pageSize, pageIndex, server, filters, data, se
176
176
  if (!fetchData.ok) {
177
177
  throw new Error('Failed to fetch data');
178
178
  }
179
- const response = await fetchData.json();
179
+ // 1. Get the raw text of the response instead of parsing it automatically
180
+ const rawText = await fetchData.text();
181
+ // 2. Parse it with a reviver function that targets large integers
182
+ const response = JSON.parse(rawText, (key, value, context) => {
183
+ // If the parser thinks it's a number, but we have the raw source text
184
+ if (context && context.source && typeof value === 'number') {
185
+ // Check if the number exceeds JavaScript's safe integer limit
186
+ if (!Number.isSafeInteger(value)) {
187
+ // Return it natively as a JavaScript BigInt (e.g., 9223372036854775806n)
188
+ return BigInt(context.source);
189
+ }
190
+ }
191
+ return value;
192
+ });
193
+ // console.log('Server response', response);
180
194
  // Format server columns to the client columns
181
195
  if (response.columns !== undefined) {
182
- console.log('Server columns', response.columns);
196
+ //console.log('Server columns', response.columns);
183
197
  columns = convertServerColumns(response.columns, columns);
184
198
  const clientCols = response.columns.reduce((acc, col) => {
185
- console.log(col.key, col.column);
199
+ // console.log(col.key, col.column);
186
200
  // replace the . with empty string
187
201
  //const key = col.key.replaceAll('.' ,'');
188
202
  ///console.log(key, col.column);
@@ -204,11 +218,11 @@ export const updateTable = async (pageSize, pageIndex, server, filters, data, se
204
218
  });
205
219
  dispatch('fetch', columns);
206
220
  data.set(tmpArr);
207
- console.log('Server data', tmpArr);
221
+ // console.log('Server data', tmpArr);
208
222
  return data;
209
223
  }
210
224
  serverItems?.set(response.count);
211
- console.log('Server data updated');
225
+ // console.log('Server data updated');
212
226
  // log the columns object
213
227
  console.log(response);
214
228
  return response;
@@ -264,7 +278,7 @@ export const convertServerColumns = (serverColumns, columns) => {
264
278
  };
265
279
  }
266
280
  });
267
- console.log('Columns config', columnsConfig);
281
+ // console.log('Columns config', columnsConfig);
268
282
  return columnsConfig;
269
283
  };
270
284
  // Calculates the maximum height of the cells in each row
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bexis2/bexis2-core-ui",
3
- "version": "0.4.95",
3
+ "version": "0.4.96",
4
4
  "private": false,
5
5
  "description": "Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte).",
6
6
  "keywords": [
@@ -8,6 +8,12 @@
8
8
  const data = config.data;
9
9
 
10
10
  $: if ($data.length > 0) fetched = true;
11
+
12
+ if (typeof BigInt !== "undefined" && !BigInt.prototype.toJSON) {
13
+ BigInt.prototype.toJSON = function () {
14
+ return this.toString();
15
+ };
16
+ }
11
17
  </script>
12
18
 
13
19
  {#key fetched}
@@ -28,7 +28,7 @@
28
28
 
29
29
  // Check the type of the column
30
30
  $values.forEach((item) => {
31
- if (item) {
31
+ if (item !== null && item !== undefined) {
32
32
  type = typeof (toFilterableValueFn ? toFilterableValueFn(item) : item);
33
33
 
34
34
  if (type === 'object') {
@@ -119,6 +119,16 @@
119
119
  value: FilterOptionsEnum.no,
120
120
  label: 'Is not on'
121
121
  }
122
+ ],
123
+ boolean: [
124
+ {
125
+ value: FilterOptionsEnum.e,
126
+ label: 'Is equal to'
127
+ },
128
+ {
129
+ value: FilterOptionsEnum.ne,
130
+ label: 'Is not equal to'
131
+ }
122
132
  ]
123
133
  };
124
134
 
@@ -156,13 +166,13 @@
156
166
 
157
167
  const optionChangeHandler = (e, index) => {
158
168
  delete $filters[id][dropdowns[index].option];
169
+ const val = dropdowns[index].value;
159
170
  $filters[id] = {
160
171
  ...$filters[id],
161
172
  [e.target.value]:
162
- // type === 'number'
163
- // ?
164
- getMissingValue(dropdowns[index].value as string)
165
- // : dropdowns[index].value
173
+ type === 'boolean'
174
+ ? (val === '' || val === undefined ? undefined : getMissingValue(val as string) === 'true' || getMissingValue(val as string) === true)
175
+ : getMissingValue(val as string)
166
176
  };
167
177
  $filters = $filters;
168
178
 
@@ -184,9 +194,9 @@
184
194
  [id]: {
185
195
  ...$filters[id],
186
196
  [dropdowns[index].option]:
187
- // type === 'number' ?
188
- getMissingValue(e.target.value)
189
- // : dropdowns[index].value
197
+ type === 'boolean'
198
+ ? (e.target.value === '' ? undefined : getMissingValue(e.target.value) === 'true' || getMissingValue(e.target.value) === true)
199
+ : getMissingValue(e.target.value)
190
200
  }
191
201
  };
192
202
  };
@@ -304,6 +314,17 @@
304
314
  bind:value={dropdown.value}
305
315
  aria-label="Filter value"
306
316
  />
317
+ {:else if type === 'boolean'}
318
+ <select
319
+ class="select border border-primary-500 text-sm p-1"
320
+ on:change={(e) => valueChangeHandler(e, index)}
321
+ bind:value={dropdown.value}
322
+ aria-label="Filter value"
323
+ >
324
+ <option value="" disabled selected>Select value...</option>
325
+ <option value="true">True</option>
326
+ <option value="false">False</option>
327
+ </select>
307
328
  {:else}
308
329
  <input
309
330
  type="date"
@@ -28,8 +28,11 @@
28
28
 
29
29
  // Check the type of the column
30
30
  $values.forEach((item) => {
31
- if (item) {
32
- type = typeof (toFilterableValueFn ? toFilterableValueFn(item) : item);
31
+ if (item !== null && item !== undefined) {
32
+ const rawType = typeof (toFilterableValueFn ? toFilterableValueFn(item) : item);
33
+
34
+ // Treat bigint exactly as 'number' for layout options
35
+ type = rawType === 'bigint' ? 'number' : rawType;
33
36
 
34
37
  if (type === 'object') {
35
38
  if ((toFilterableValueFn ? toFilterableValueFn(item) : item) instanceof Date) {
@@ -119,6 +122,16 @@
119
122
  // value: FilterOptionsEnum.no,
120
123
  // label: 'Is not on'
121
124
  // }
125
+ ],
126
+ boolean: [
127
+ {
128
+ value: FilterOptionsEnum.e,
129
+ label: 'Is equal to'
130
+ },
131
+ {
132
+ value: FilterOptionsEnum.ne,
133
+ label: 'Is not equal to'
134
+ }
122
135
  ]
123
136
  };
124
137
 
@@ -134,7 +147,9 @@
134
147
  const stringValues = $values.map((item) => (toStringFn ? toStringFn(item) : item));
135
148
 
136
149
  const missingValues = stringValues.reduce((acc, item, index) => {
137
- acc[typeof item === 'string' ? item.toLowerCase() : item] = $values[index];
150
+ // Convert everything (including BigInt values) to string first
151
+ const strRepresentation = String(item).toLowerCase();
152
+ acc[strRepresentation] = $values[index];
138
153
  return acc;
139
154
  }, {});
140
155
 
@@ -146,9 +161,15 @@
146
161
 
147
162
  const optionChangeHandler = (e, index) => {
148
163
  delete $filters[id][dropdowns[index].option];
164
+ const val = dropdowns[index].value;
149
165
  $filters[id] = {
150
166
  ...$filters[id],
151
- [e.target.value]: getMissingValue(dropdowns[index].value as string)
167
+ [e.target.value]:
168
+ type === 'boolean'
169
+ ? val === '' || val === undefined
170
+ ? undefined
171
+ : getMissingValue(val as string) === 'true' || getMissingValue(val as string) === true
172
+ : getMissingValue(dropdowns[index].value as string)
152
173
  };
153
174
  $filters = $filters;
154
175
 
@@ -169,10 +190,34 @@
169
190
  ...$filters,
170
191
  [id]: {
171
192
  ...$filters[id],
172
- [dropdowns[index].option]:
173
- type === 'number'
174
- ? parseFloat(getMissingValue(e.target.value)) || undefined
175
- : getMissingValue(e.target.value)
193
+ [dropdowns[index].option]: (() => {
194
+ const rawVal = getMissingValue(e.target.value);
195
+
196
+ if (rawVal === '' || rawVal === undefined || rawVal === null) {
197
+ return undefined;
198
+ }
199
+
200
+ if (type === 'number') {
201
+ const cleanStr = String(rawVal).trim();
202
+ // If it's a 16+ digit integer, parse as BigInt to keep precision
203
+ const isBigInt = /^\d+$/.test(cleanStr) && cleanStr.length >= 16;
204
+
205
+ if (isBigInt) {
206
+ try {
207
+ return BigInt(cleanStr);
208
+ } catch {
209
+ return cleanStr;
210
+ }
211
+ }
212
+ return parseFloat(cleanStr) || undefined;
213
+ }
214
+
215
+ if (type === 'boolean') {
216
+ return rawVal === 'true' || rawVal === true;
217
+ }
218
+
219
+ return rawVal;
220
+ })()
176
221
  }
177
222
  };
178
223
  };
@@ -284,7 +329,7 @@
284
329
  on:click|preventDefault={() => removeFilter(dropdown.option)}
285
330
  on:keydown|preventDefault={() => removeFilter(dropdown.option)}
286
331
  aria-label="Remove filter"
287
- >
332
+ >
288
333
  <Fa icon={faXmark} />
289
334
  </div>
290
335
  {/if}
@@ -305,6 +350,17 @@
305
350
  bind:value={dropdown.value}
306
351
  aria-label="Filter value"
307
352
  />
353
+ {:else if type === 'boolean'}
354
+ <select
355
+ class="select border border-primary-500 text-sm p-1"
356
+ on:change={(e) => valueChangeHandler(e, index)}
357
+ bind:value={dropdown.value}
358
+ aria-label="Filter value"
359
+ >
360
+ <option value="" disabled selected>Select value...</option>
361
+ <option value="true">True</option>
362
+ <option value="false">False</option>
363
+ </select>
308
364
  {:else}
309
365
  <input
310
366
  type="date"
@@ -78,19 +78,35 @@ const applyFilter = (filterValue, value, filterFn) => {
78
78
  }));
79
79
 
80
80
  filters.forEach((filter) => {
81
- result = result && (filter.value ? filterFn(filter.option, filter.value, value) : true);
81
+ result = result && (filter.value !== undefined && filter.value !== '' && filter.value !== null ? filterFn(filter.option, filter.value, value) : true);
82
82
  });
83
83
 
84
84
  return result;
85
85
  };
86
86
 
87
+ const booleanFilter = (filterOption, filterValue, value) => {
88
+ const filterBool = typeof filterValue === 'string' ? filterValue === 'true' : !!filterValue;
89
+ const valBool = !!value;
90
+
91
+ switch (filterOption) {
92
+ case FilterOptionsEnum.e:
93
+ return valBool === filterBool;
94
+ case FilterOptionsEnum.ne:
95
+ return valBool !== filterBool;
96
+ default:
97
+ return false;
98
+ }
99
+ };
100
+
87
101
  export const columnFilter: ColumnFilterFn = ({ filterValue, value }) => {
88
102
  if (typeof value === 'object' && value instanceof Date) {
89
103
  return applyFilter(filterValue, value, dateFilter);
90
- } else if (typeof value === 'number') {
104
+ } else if (typeof value === 'number' || typeof value === 'bigint') {
91
105
  return applyFilter(filterValue, value, numberFilter);
92
106
  } else if (typeof value === 'string') {
93
107
  return applyFilter(filterValue, value, textFilter);
108
+ } else if (typeof value === 'boolean') {
109
+ return applyFilter(filterValue, value, booleanFilter);
94
110
  }
95
111
 
96
112
  return false;
@@ -227,15 +227,30 @@ export const updateTable = async (
227
227
  throw new Error('Failed to fetch data');
228
228
  }
229
229
 
230
- const response: Receive = await fetchData.json();
230
+ // 1. Get the raw text of the response instead of parsing it automatically
231
+ const rawText = await fetchData.text();
232
+
233
+ // 2. Parse it with a reviver function that targets large integers
234
+ const response: Receive = JSON.parse(rawText, (key, value, context) => {
235
+ // If the parser thinks it's a number, but we have the raw source text
236
+ if (context && context.source && typeof value === 'number') {
237
+ // Check if the number exceeds JavaScript's safe integer limit
238
+ if (!Number.isSafeInteger(value)) {
239
+ // Return it natively as a JavaScript BigInt (e.g., 9223372036854775806n)
240
+ return BigInt(context.source);
241
+ }
242
+ }
243
+ return value;
244
+ });
245
+ // console.log('Server response', response);
231
246
 
232
247
  // Format server columns to the client columns
233
248
  if (response.columns !== undefined) {
234
- console.log('Server columns', response.columns);
249
+ //console.log('Server columns', response.columns);
235
250
  columns = convertServerColumns(response.columns, columns);
236
251
 
237
252
  const clientCols = response.columns.reduce((acc, col) => {
238
- console.log(col.key, col.column);
253
+ // console.log(col.key, col.column);
239
254
  // replace the . with empty string
240
255
  //const key = col.key.replaceAll('.' ,'');
241
256
  ///console.log(key, col.column);
@@ -259,12 +274,12 @@ export const updateTable = async (
259
274
  });
260
275
  dispatch('fetch', columns);
261
276
  data.set(tmpArr);
262
- console.log('Server data', tmpArr);
277
+ // console.log('Server data', tmpArr);
263
278
  return data;
264
279
  }
265
280
 
266
281
  serverItems?.set(response.count);
267
- console.log('Server data updated');
282
+ // console.log('Server data updated');
268
283
 
269
284
 
270
285
  // log the columns object
@@ -326,7 +341,7 @@ export const convertServerColumns = (
326
341
  };
327
342
  }
328
343
  });
329
- console.log('Columns config', columnsConfig);
344
+ // console.log('Columns config', columnsConfig);
330
345
  return columnsConfig;
331
346
  };
332
347
  // Calculates the maximum height of the cells in each row