@memberjunction/ng-entity-viewer 6.1.0-edge.0 → 6.1.0-edge.2

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.
@@ -10,9 +10,30 @@ export declare function buildCompositeKey(record: Record<string, unknown>, entit
10
10
  * Used as a stable row identifier for selection, tracking, and map keys.
11
11
  */
12
12
  export declare function buildPkString(record: Record<string, unknown>, entityInfo: EntityInfo): string;
13
+ /**
14
+ * Rewrite host-supplied column field names to the entity's OWN spelling.
15
+ *
16
+ * A page may reasonably write `field: 'name'` where the entity says `Name`, and the grid resolves
17
+ * host names case-insensitively when it looks up metadata — so such a column *renders*. But the
18
+ * spelling does not stay cosmetic: it becomes the AG Grid colDef's `field`, and therefore its
19
+ * `colId`, and therefore the key in captured grid state and sort settings. Meanwhile the row objects
20
+ * the grid builds are keyed by the ENTITY's spelling. Anything that addresses data or matches state
21
+ * by that name then misses: cells resolve to undefined, the captured state drops the column, a saved
22
+ * sort is discarded.
23
+ *
24
+ * Normalising once, here, is what keeps those two vocabularies from diverging — the alternative is a
25
+ * case-insensitive comparison at every one of those sites, which is the state that produced the bug.
26
+ *
27
+ * Unchanged columns are returned by REFERENCE (so callers can detect a no-op cheaply), and changed
28
+ * ones as copies — the host's own array and objects are never mutated. A name matching no field is
29
+ * left alone for the existing validation to reject.
30
+ */
31
+ export declare function canonicalizeColumnFields<T extends {
32
+ field: string;
33
+ }>(entityInfo: EntityInfo, columns: readonly T[]): T[];
13
34
  /**
14
35
  * Compute the Fields array to request from RunView based on EntityInfo and optional grid state.
15
36
  * Includes: PK fields, NameField, visible display fields, and timestamp fields.
16
37
  */
17
- export declare function computeFieldsList(entityInfo: EntityInfo, gridState?: ViewGridState | null): string[];
38
+ export declare function computeFieldsList(entityInfo: EntityInfo, gridState?: ViewGridState | null, hostColumnFields?: readonly string[] | null): string[];
18
39
  //# sourceMappingURL=record.util.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"record.util.d.ts","sourceRoot":"","sources":["../../../src/lib/utils/record.util.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAChE,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,UAAU,EAAE,UAAU,GAAG,YAAY,CAMvG;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,UAAU,EAAE,UAAU,GAAG,MAAM,CAE7F;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,UAAU,EAAE,SAAS,CAAC,EAAE,aAAa,GAAG,IAAI,GAAG,MAAM,EAAE,CAiEpG"}
1
+ {"version":3,"file":"record.util.d.ts","sourceRoot":"","sources":["../../../src/lib/utils/record.util.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAChE,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,UAAU,EAAE,UAAU,GAAG,YAAY,CAMvG;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,UAAU,EAAE,UAAU,GAAG,MAAM,CAE7F;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,wBAAwB,CAAC,CAAC,SAAS;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,EAChE,UAAU,EAAE,UAAU,EACtB,OAAO,EAAE,SAAS,CAAC,EAAE,GACtB,CAAC,EAAE,CAKL;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC7B,UAAU,EAAE,UAAU,EACtB,SAAS,CAAC,EAAE,aAAa,GAAG,IAAI,EAChC,gBAAgB,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,GAC5C,MAAM,EAAE,CA2FV"}
@@ -17,12 +17,61 @@ export function buildCompositeKey(record, entityInfo) {
17
17
  export function buildPkString(record, entityInfo) {
18
18
  return buildCompositeKey(record, entityInfo).ToConcatenatedString();
19
19
  }
20
+ /**
21
+ * Rewrite host-supplied column field names to the entity's OWN spelling.
22
+ *
23
+ * A page may reasonably write `field: 'name'` where the entity says `Name`, and the grid resolves
24
+ * host names case-insensitively when it looks up metadata — so such a column *renders*. But the
25
+ * spelling does not stay cosmetic: it becomes the AG Grid colDef's `field`, and therefore its
26
+ * `colId`, and therefore the key in captured grid state and sort settings. Meanwhile the row objects
27
+ * the grid builds are keyed by the ENTITY's spelling. Anything that addresses data or matches state
28
+ * by that name then misses: cells resolve to undefined, the captured state drops the column, a saved
29
+ * sort is discarded.
30
+ *
31
+ * Normalising once, here, is what keeps those two vocabularies from diverging — the alternative is a
32
+ * case-insensitive comparison at every one of those sites, which is the state that produced the bug.
33
+ *
34
+ * Unchanged columns are returned by REFERENCE (so callers can detect a no-op cheaply), and changed
35
+ * ones as copies — the host's own array and objects are never mutated. A name matching no field is
36
+ * left alone for the existing validation to reject.
37
+ */
38
+ export function canonicalizeColumnFields(entityInfo, columns) {
39
+ return columns.map(col => {
40
+ const field = entityInfo.Fields.find(f => f.Name.toLowerCase() === col.field.toLowerCase());
41
+ return field && field.Name !== col.field ? { ...col, field: field.Name } : col;
42
+ });
43
+ }
20
44
  /**
21
45
  * Compute the Fields array to request from RunView based on EntityInfo and optional grid state.
22
46
  * Includes: PK fields, NameField, visible display fields, and timestamp fields.
23
47
  */
24
- export function computeFieldsList(entityInfo, gridState) {
48
+ export function computeFieldsList(entityInfo, gridState, hostColumnFields) {
25
49
  const fields = new Set();
50
+ // Fields the HOST explicitly asked to display. Without these, a page that supplies `[Columns]`
51
+ // renders columns whose data was never fetched — every cell shows as empty, which reads as a
52
+ // data bug rather than a missing SELECT. They are added unconditionally (not as an alternative
53
+ // to the branches below) because a host column list says what to SHOW, while DefaultInView and
54
+ // the grid state say what to fetch by default; the host's columns must be a superset, not a
55
+ // replacement. Names are validated against the entity so a stale column can't break the query.
56
+ if (hostColumnFields?.length) {
57
+ for (const name of hostColumnFields) {
58
+ const field = entityInfo.Fields.find(f => f.Name.toLowerCase() === name.toLowerCase());
59
+ // Add the ENTITY's spelling, not the host's. A page may reasonably write `field: 'name'`
60
+ // where the entity says `Name`, and metadata lookups here are case-insensitive so the
61
+ // column is accepted — but this list goes on the wire. On the GraphQL transport it is
62
+ // interpolated straight into the selection set, and GraphQL field names are case
63
+ // SENSITIVE, so the host's spelling produces `Cannot query field "name"` and the whole
64
+ // view fails: an error and zero rows, not one odd column. (This Set is case-sensitive
65
+ // too, so the two spellings would not even collapse into one entry.)
66
+ //
67
+ // Callers that hand this function raw host names should also run them through
68
+ // `canonicalizeColumnFields` for the column definitions, or the rendered column will
69
+ // address a key the fetched rows do not have.
70
+ if (field) {
71
+ fields.add(field.Name);
72
+ }
73
+ }
74
+ }
26
75
  // Always include PK fields
27
76
  for (const pk of entityInfo.PrimaryKeys) {
28
77
  fields.add(pk.Name);
@@ -1 +1 @@
1
- {"version":3,"file":"record.util.js","sourceRoot":"","sources":["../../../src/lib/utils/record.util.ts"],"names":[],"mappings":"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","sourcesContent":["import { EntityInfo, CompositeKey } from '@memberjunction/core';\nimport { ViewGridState } from '../types';\n\n/**\n * Build a CompositeKey from a plain record using EntityInfo PK fields.\n * Works with both plain objects (from ResultType: 'simple') and BaseEntity instances.\n */\nexport function buildCompositeKey(record: Record<string, unknown>, entityInfo: EntityInfo): CompositeKey {\n const kvps = entityInfo.PrimaryKeys.map(pk => ({\n FieldName: pk.Name,\n Value: record[pk.Name]\n }));\n return new CompositeKey(kvps);\n}\n\n/**\n * Build a PK concatenated string matching CompositeKey.ToConcatenatedString() format.\n * Used as a stable row identifier for selection, tracking, and map keys.\n */\nexport function buildPkString(record: Record<string, unknown>, entityInfo: EntityInfo): string {\n return buildCompositeKey(record, entityInfo).ToConcatenatedString();\n}\n\n/**\n * Compute the Fields array to request from RunView based on EntityInfo and optional grid state.\n * Includes: PK fields, NameField, visible display fields, and timestamp fields.\n */\nexport function computeFieldsList(entityInfo: EntityInfo, gridState?: ViewGridState | null): string[] {\n const fields = new Set<string>();\n\n // Always include PK fields\n for (const pk of entityInfo.PrimaryKeys) {\n fields.add(pk.Name);\n }\n\n // Include NameField for display name resolution\n if (entityInfo.NameField) {\n fields.add(entityInfo.NameField.Name);\n }\n\n // Include visible fields from gridState or DefaultInView\n if (gridState?.columnSettings?.length) {\n for (const col of gridState.columnSettings) {\n if (!col.hidden) {\n fields.add(col.Name);\n }\n }\n } else {\n // First try to use DefaultInView fields\n const defaultInViewFields = entityInfo.Fields.filter(f => f.DefaultInView);\n\n if (defaultInViewFields.length > 0) {\n for (const f of defaultInViewFields) {\n fields.add(f.Name);\n }\n } else {\n // Fallback: when no DefaultInView fields are defined, include the first 10\n // non-system fields. This matches the fallback in generateAgColumnDefs()\n // which uses getDefaultFieldsFallback() to show columns even when\n // DefaultInView isn't configured.\n const fallbackFields = entityInfo.Fields\n .filter(f =>\n !f.Name.startsWith('__mj_') &&\n !(f.IsPrimaryKey && f.SQLFullType?.toLowerCase() === 'uniqueidentifier') &&\n (f.Length <= 500 || f.Length < 0) // Exclude very long text unless nvarchar(max)\n )\n .slice(0, 10);\n\n for (const f of fallbackFields) {\n fields.add(f.Name);\n }\n }\n }\n\n // Include __mj timestamp fields (commonly used for sort and display)\n fields.add('__mj_CreatedAt');\n fields.add('__mj_UpdatedAt');\n\n // Lat/lng only — never address-tagged fields. The map uses pre-geocoded\n // coords, never text-based location guessing.\n for (const f of entityInfo.Fields) {\n if (f.ExtendedType === 'GeoLatitude' || f.ExtendedType === 'GeoLongitude') {\n fields.add(f.Name);\n }\n }\n // Gated on SupportsGeoCoding — BoundaryGeoJSON is nvarchar(max), wasteful\n // to pull for grid views that won't render a map.\n if (entityInfo.SupportsGeoCoding && entityInfo.Fields.some(f => f.Name === 'BoundaryGeoJSON')) {\n fields.add('BoundaryGeoJSON');\n }\n\n return Array.from(fields);\n}\n"]}
1
+ {"version":3,"file":"record.util.js","sourceRoot":"","sources":["../../../src/lib/utils/record.util.ts"],"names":[],"mappings":"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","sourcesContent":["import { EntityInfo, CompositeKey } from '@memberjunction/core';\nimport { ViewGridState } from '../types';\n\n/**\n * Build a CompositeKey from a plain record using EntityInfo PK fields.\n * Works with both plain objects (from ResultType: 'simple') and BaseEntity instances.\n */\nexport function buildCompositeKey(record: Record<string, unknown>, entityInfo: EntityInfo): CompositeKey {\n const kvps = entityInfo.PrimaryKeys.map(pk => ({\n FieldName: pk.Name,\n Value: record[pk.Name]\n }));\n return new CompositeKey(kvps);\n}\n\n/**\n * Build a PK concatenated string matching CompositeKey.ToConcatenatedString() format.\n * Used as a stable row identifier for selection, tracking, and map keys.\n */\nexport function buildPkString(record: Record<string, unknown>, entityInfo: EntityInfo): string {\n return buildCompositeKey(record, entityInfo).ToConcatenatedString();\n}\n\n/**\n * Rewrite host-supplied column field names to the entity's OWN spelling.\n *\n * A page may reasonably write `field: 'name'` where the entity says `Name`, and the grid resolves\n * host names case-insensitively when it looks up metadata — so such a column *renders*. But the\n * spelling does not stay cosmetic: it becomes the AG Grid colDef's `field`, and therefore its\n * `colId`, and therefore the key in captured grid state and sort settings. Meanwhile the row objects\n * the grid builds are keyed by the ENTITY's spelling. Anything that addresses data or matches state\n * by that name then misses: cells resolve to undefined, the captured state drops the column, a saved\n * sort is discarded.\n *\n * Normalising once, here, is what keeps those two vocabularies from diverging — the alternative is a\n * case-insensitive comparison at every one of those sites, which is the state that produced the bug.\n *\n * Unchanged columns are returned by REFERENCE (so callers can detect a no-op cheaply), and changed\n * ones as copies — the host's own array and objects are never mutated. A name matching no field is\n * left alone for the existing validation to reject.\n */\nexport function canonicalizeColumnFields<T extends { field: string }>(\n entityInfo: EntityInfo,\n columns: readonly T[],\n): T[] {\n return columns.map(col => {\n const field = entityInfo.Fields.find(f => f.Name.toLowerCase() === col.field.toLowerCase());\n return field && field.Name !== col.field ? { ...col, field: field.Name } : col;\n });\n}\n\n/**\n * Compute the Fields array to request from RunView based on EntityInfo and optional grid state.\n * Includes: PK fields, NameField, visible display fields, and timestamp fields.\n */\nexport function computeFieldsList(\n entityInfo: EntityInfo,\n gridState?: ViewGridState | null,\n hostColumnFields?: readonly string[] | null,\n): string[] {\n const fields = new Set<string>();\n\n // Fields the HOST explicitly asked to display. Without these, a page that supplies `[Columns]`\n // renders columns whose data was never fetched — every cell shows as empty, which reads as a\n // data bug rather than a missing SELECT. They are added unconditionally (not as an alternative\n // to the branches below) because a host column list says what to SHOW, while DefaultInView and\n // the grid state say what to fetch by default; the host's columns must be a superset, not a\n // replacement. Names are validated against the entity so a stale column can't break the query.\n if (hostColumnFields?.length) {\n for (const name of hostColumnFields) {\n const field = entityInfo.Fields.find(f => f.Name.toLowerCase() === name.toLowerCase());\n // Add the ENTITY's spelling, not the host's. A page may reasonably write `field: 'name'`\n // where the entity says `Name`, and metadata lookups here are case-insensitive so the\n // column is accepted — but this list goes on the wire. On the GraphQL transport it is\n // interpolated straight into the selection set, and GraphQL field names are case\n // SENSITIVE, so the host's spelling produces `Cannot query field \"name\"` and the whole\n // view fails: an error and zero rows, not one odd column. (This Set is case-sensitive\n // too, so the two spellings would not even collapse into one entry.)\n //\n // Callers that hand this function raw host names should also run them through\n // `canonicalizeColumnFields` for the column definitions, or the rendered column will\n // address a key the fetched rows do not have.\n if (field) {\n fields.add(field.Name);\n }\n }\n }\n\n // Always include PK fields\n for (const pk of entityInfo.PrimaryKeys) {\n fields.add(pk.Name);\n }\n\n // Include NameField for display name resolution\n if (entityInfo.NameField) {\n fields.add(entityInfo.NameField.Name);\n }\n\n // Include visible fields from gridState or DefaultInView\n if (gridState?.columnSettings?.length) {\n for (const col of gridState.columnSettings) {\n if (!col.hidden) {\n fields.add(col.Name);\n }\n }\n } else {\n // First try to use DefaultInView fields\n const defaultInViewFields = entityInfo.Fields.filter(f => f.DefaultInView);\n\n if (defaultInViewFields.length > 0) {\n for (const f of defaultInViewFields) {\n fields.add(f.Name);\n }\n } else {\n // Fallback: when no DefaultInView fields are defined, include the first 10\n // non-system fields. This matches the fallback in generateAgColumnDefs()\n // which uses getDefaultFieldsFallback() to show columns even when\n // DefaultInView isn't configured.\n const fallbackFields = entityInfo.Fields\n .filter(f =>\n !f.Name.startsWith('__mj_') &&\n !(f.IsPrimaryKey && f.SQLFullType?.toLowerCase() === 'uniqueidentifier') &&\n (f.Length <= 500 || f.Length < 0) // Exclude very long text unless nvarchar(max)\n )\n .slice(0, 10);\n\n for (const f of fallbackFields) {\n fields.add(f.Name);\n }\n }\n }\n\n // Include __mj timestamp fields (commonly used for sort and display)\n fields.add('__mj_CreatedAt');\n fields.add('__mj_UpdatedAt');\n\n // Lat/lng only — never address-tagged fields. The map uses pre-geocoded\n // coords, never text-based location guessing.\n for (const f of entityInfo.Fields) {\n if (f.ExtendedType === 'GeoLatitude' || f.ExtendedType === 'GeoLongitude') {\n fields.add(f.Name);\n }\n }\n // Gated on SupportsGeoCoding — BoundaryGeoJSON is nvarchar(max), wasteful\n // to pull for grid views that won't render a map.\n if (entityInfo.SupportsGeoCoding && entityInfo.Fields.some(f => f.Name === 'BoundaryGeoJSON')) {\n fields.add('BoundaryGeoJSON');\n }\n\n return Array.from(fields);\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memberjunction/ng-entity-viewer",
3
- "version": "6.1.0-edge.0",
3
+ "version": "6.1.0-edge.2",
4
4
  "description": "MemberJunction: Angular components for viewing entity data in multiple formats (grid, cards) with filtering, selection, and shared data management",
5
5
  "main": "./dist/public-api.js",
6
6
  "typings": "./dist/public-api.d.ts",
@@ -21,35 +21,35 @@
21
21
  "devDependencies": {
22
22
  "@angular/compiler": "21.1.3",
23
23
  "@angular/compiler-cli": "21.1.3",
24
- "@memberjunction/ng-test-utils": "6.1.0-edge.0",
24
+ "@memberjunction/ng-test-utils": "6.1.0-edge.2",
25
25
  "vitest": "^4.0.18"
26
26
  },
27
27
  "peerDependencies": {
28
- "@angular/animations": "21.1.3",
29
- "@angular/common": "21.1.3",
30
- "@angular/core": "21.1.3",
31
- "@angular/forms": "21.1.3",
28
+ "@angular/animations": "^21.1.3",
29
+ "@angular/common": "^21.1.3",
30
+ "@angular/core": "^21.1.3",
31
+ "@angular/forms": "^21.1.3",
32
32
  "ag-grid-angular": "^35.0.1",
33
33
  "ag-grid-community": "^35.0.1"
34
34
  },
35
35
  "dependencies": {
36
- "@memberjunction/actions-base": "6.1.0-edge.0",
37
- "@memberjunction/core": "6.1.0-edge.0",
38
- "@memberjunction/core-entities": "6.1.0-edge.0",
39
- "@memberjunction/export-engine": "6.1.0-edge.0",
40
- "@memberjunction/global": "6.1.0-edge.0",
41
- "@memberjunction/ng-base-types": "6.1.0-edge.0",
42
- "@memberjunction/ng-entity-action-ux": "6.1.0-edge.0",
43
- "@memberjunction/ng-export-service": "6.1.0-edge.0",
44
- "@memberjunction/ng-filter-builder": "6.1.0-edge.0",
45
- "@memberjunction/ng-list-management": "6.1.0-edge.0",
46
- "@memberjunction/ng-map-view": "6.1.0-edge.0",
47
- "@memberjunction/ng-notifications": "6.1.0-edge.0",
48
- "@memberjunction/ng-pagination": "6.1.0-edge.0",
49
- "@memberjunction/ng-record-changes": "6.1.0-edge.0",
50
- "@memberjunction/ng-shared-generic": "6.1.0-edge.0",
51
- "@memberjunction/ng-timeline": "6.1.0-edge.0",
52
- "@memberjunction/ng-ui-components": "6.1.0-edge.0",
36
+ "@memberjunction/actions-base": "6.1.0-edge.2",
37
+ "@memberjunction/core": "6.1.0-edge.2",
38
+ "@memberjunction/core-entities": "6.1.0-edge.2",
39
+ "@memberjunction/export-engine": "6.1.0-edge.2",
40
+ "@memberjunction/global": "6.1.0-edge.2",
41
+ "@memberjunction/ng-base-types": "6.1.0-edge.2",
42
+ "@memberjunction/ng-entity-action-ux": "6.1.0-edge.2",
43
+ "@memberjunction/ng-export-service": "6.1.0-edge.2",
44
+ "@memberjunction/ng-filter-builder": "6.1.0-edge.2",
45
+ "@memberjunction/ng-list-management": "6.1.0-edge.2",
46
+ "@memberjunction/ng-map-view": "6.1.0-edge.2",
47
+ "@memberjunction/ng-notifications": "6.1.0-edge.2",
48
+ "@memberjunction/ng-pagination": "6.1.0-edge.2",
49
+ "@memberjunction/ng-record-changes": "6.1.0-edge.2",
50
+ "@memberjunction/ng-shared-generic": "6.1.0-edge.2",
51
+ "@memberjunction/ng-timeline": "6.1.0-edge.2",
52
+ "@memberjunction/ng-ui-components": "6.1.0-edge.2",
53
53
  "rxjs": "^7.8.2",
54
54
  "tslib": "^2.8.1"
55
55
  },