Allow using JSON field arrays as a data provider source and add data bindings for nested JSON fields
This commit is contained in:
parent
b362068d47
commit
f898b8c94d
|
@ -5,7 +5,7 @@ import {
|
||||||
findComponent,
|
findComponent,
|
||||||
findComponentPath,
|
findComponentPath,
|
||||||
getComponentSettings,
|
getComponentSettings,
|
||||||
} from "./storeUtils"
|
} from "./componentUtils"
|
||||||
import { store } from "builderStore"
|
import { store } from "builderStore"
|
||||||
import { queries as queriesStores, tables as tablesStore } from "stores/backend"
|
import { queries as queriesStores, tables as tablesStore } from "stores/backend"
|
||||||
import {
|
import {
|
||||||
|
@ -15,6 +15,7 @@ import {
|
||||||
encodeJSBinding,
|
encodeJSBinding,
|
||||||
} from "@budibase/string-templates"
|
} from "@budibase/string-templates"
|
||||||
import { TableNames } from "../constants"
|
import { TableNames } from "../constants"
|
||||||
|
import { convertJSONSchemaToTableSchema } from "./jsonUtils"
|
||||||
|
|
||||||
// Regex to match all instances of template strings
|
// Regex to match all instances of template strings
|
||||||
const CAPTURE_VAR_INSIDE_TEMPLATE = /{{([^}]+)}}/g
|
const CAPTURE_VAR_INSIDE_TEMPLATE = /{{([^}]+)}}/g
|
||||||
|
@ -186,6 +187,7 @@ const getProviderContextBindings = (asset, dataProviders) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
let schema
|
let schema
|
||||||
|
let table
|
||||||
let readablePrefix
|
let readablePrefix
|
||||||
let runtimeSuffix = context.suffix
|
let runtimeSuffix = context.suffix
|
||||||
|
|
||||||
|
@ -209,7 +211,16 @@ const getProviderContextBindings = (asset, dataProviders) => {
|
||||||
}
|
}
|
||||||
const info = getSchemaForDatasource(asset, datasource)
|
const info = getSchemaForDatasource(asset, datasource)
|
||||||
schema = info.schema
|
schema = info.schema
|
||||||
readablePrefix = info.table?.name
|
table = info.table
|
||||||
|
|
||||||
|
// For JSON arrays, use the array name as the readable prefix.
|
||||||
|
// Otherwise use the table name
|
||||||
|
if (datasource.type === "jsonarray") {
|
||||||
|
const split = datasource.label.split(".")
|
||||||
|
readablePrefix = split[split.length - 1]
|
||||||
|
} else {
|
||||||
|
readablePrefix = info.table?.name
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!schema) {
|
if (!schema) {
|
||||||
return
|
return
|
||||||
|
@ -229,7 +240,8 @@ const getProviderContextBindings = (asset, dataProviders) => {
|
||||||
const fieldSchema = schema[key]
|
const fieldSchema = schema[key]
|
||||||
|
|
||||||
// Make safe runtime binding
|
// Make safe runtime binding
|
||||||
const runtimeBinding = `${safeComponentId}.${makePropSafe(key)}`
|
const safeKey = key.split(".").map(makePropSafe).join(".")
|
||||||
|
const runtimeBinding = `${safeComponentId}.${safeKey}`
|
||||||
|
|
||||||
// Optionally use a prefix with readable bindings
|
// Optionally use a prefix with readable bindings
|
||||||
let readableBinding = component._instanceName
|
let readableBinding = component._instanceName
|
||||||
|
@ -247,6 +259,8 @@ const getProviderContextBindings = (asset, dataProviders) => {
|
||||||
// datasource options, based on bindable properties
|
// datasource options, based on bindable properties
|
||||||
fieldSchema,
|
fieldSchema,
|
||||||
providerId,
|
providerId,
|
||||||
|
// Table ID is used by JSON fields to know what table the field is in
|
||||||
|
tableId: table?._id,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -347,16 +361,26 @@ export const getSchemaForDatasource = (asset, datasource, isForm = false) => {
|
||||||
|
|
||||||
if (datasource) {
|
if (datasource) {
|
||||||
const { type } = datasource
|
const { type } = datasource
|
||||||
|
const tables = get(tablesStore).list
|
||||||
|
|
||||||
// Determine the source table from the datasource type
|
// Determine the entity which backs this datasource.
|
||||||
|
// "provider" datasources are those targeting another data provider
|
||||||
if (type === "provider") {
|
if (type === "provider") {
|
||||||
const component = findComponent(asset.props, datasource.providerId)
|
const component = findComponent(asset.props, datasource.providerId)
|
||||||
const source = getDatasourceForProvider(asset, component)
|
const source = getDatasourceForProvider(asset, component)
|
||||||
return getSchemaForDatasource(asset, source, isForm)
|
return getSchemaForDatasource(asset, source, isForm)
|
||||||
} else if (type === "query") {
|
}
|
||||||
|
|
||||||
|
// "query" datasources are those targeting non-plus datasources or
|
||||||
|
// custom queries
|
||||||
|
else if (type === "query") {
|
||||||
const queries = get(queriesStores).list
|
const queries = get(queriesStores).list
|
||||||
table = queries.find(query => query._id === datasource._id)
|
table = queries.find(query => query._id === datasource._id)
|
||||||
} else if (type === "field") {
|
}
|
||||||
|
|
||||||
|
// "field" datasources are array-like fields of rows, such as attachments
|
||||||
|
// or multi-select fields
|
||||||
|
else if (type === "field") {
|
||||||
table = { name: datasource.fieldName }
|
table = { name: datasource.fieldName }
|
||||||
const { fieldType } = datasource
|
const { fieldType } = datasource
|
||||||
if (fieldType === "attachment") {
|
if (fieldType === "attachment") {
|
||||||
|
@ -375,12 +399,26 @@ export const getSchemaForDatasource = (asset, datasource, isForm = false) => {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
const tables = get(tablesStore).list
|
|
||||||
|
// "jsonarray" datasources are arrays inside JSON fields
|
||||||
|
else if (type === "jsonarray") {
|
||||||
|
table = tables.find(table => table._id === datasource.tableId)
|
||||||
|
const keysToSchema = datasource.label.split(".").slice(2)
|
||||||
|
let jsonSchema = table?.schema
|
||||||
|
for (let i = 0; i < keysToSchema.length; i++) {
|
||||||
|
jsonSchema = jsonSchema[keysToSchema[i]].schema
|
||||||
|
}
|
||||||
|
schema = convertJSONSchemaToTableSchema(jsonSchema, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise we assume we're targeting an internal table or a plus
|
||||||
|
// datasource, and we can treat it as a table with a schema
|
||||||
|
else {
|
||||||
table = tables.find(table => table._id === datasource.tableId)
|
table = tables.find(table => table._id === datasource.tableId)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine the schema from the table if not already determined
|
// Determine the schema from the backing entity if not already determined
|
||||||
if (table && !schema) {
|
if (table && !schema) {
|
||||||
if (type === "view") {
|
if (type === "view") {
|
||||||
schema = cloneDeep(table.views?.[datasource.name]?.schema)
|
schema = cloneDeep(table.views?.[datasource.name]?.schema)
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { getHostingStore } from "./store/hosting"
|
||||||
import { getThemeStore } from "./store/theme"
|
import { getThemeStore } from "./store/theme"
|
||||||
import { derived, writable } from "svelte/store"
|
import { derived, writable } from "svelte/store"
|
||||||
import { FrontendTypes, LAYOUT_NAMES } from "../constants"
|
import { FrontendTypes, LAYOUT_NAMES } from "../constants"
|
||||||
import { findComponent } from "./storeUtils"
|
import { findComponent } from "./componentUtils"
|
||||||
|
|
||||||
export const store = getFrontendStore()
|
export const store = getFrontendStore()
|
||||||
export const automationStore = getAutomationStore()
|
export const automationStore = getAutomationStore()
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
export const convertJSONSchemaToTableSchema = jsonSchema => {
|
||||||
|
if (!jsonSchema) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if (jsonSchema.schema) {
|
||||||
|
jsonSchema = jsonSchema.schema
|
||||||
|
}
|
||||||
|
const keys = extractJSONSchemaKeys(jsonSchema)
|
||||||
|
let schema = {}
|
||||||
|
keys.forEach(({ key, type }) => {
|
||||||
|
schema[key] = { type, name: key }
|
||||||
|
})
|
||||||
|
return schema
|
||||||
|
}
|
||||||
|
|
||||||
|
const extractJSONSchemaKeys = (jsonSchema, squashObjects = false) => {
|
||||||
|
if (!jsonSchema || !Object.keys(jsonSchema).length) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
let keys = []
|
||||||
|
Object.keys(jsonSchema).forEach(key => {
|
||||||
|
const type = jsonSchema[key].type
|
||||||
|
if (type === "json" && squashObjects) {
|
||||||
|
const childKeys = extractJSONSchemaKeys(jsonSchema[key].schema)
|
||||||
|
keys = keys.concat(
|
||||||
|
childKeys.map(childKey => ({
|
||||||
|
key: `${key}.${childKey.key}`,
|
||||||
|
type: childKey.type,
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
} else if (type !== "array") {
|
||||||
|
keys.push({ key, type })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return keys
|
||||||
|
}
|
|
@ -26,7 +26,7 @@ import {
|
||||||
findAllMatchingComponents,
|
findAllMatchingComponents,
|
||||||
findComponent,
|
findComponent,
|
||||||
getComponentSettings,
|
getComponentSettings,
|
||||||
} from "../storeUtils"
|
} from "../componentUtils"
|
||||||
import { uuid } from "../uuid"
|
import { uuid } from "../uuid"
|
||||||
import { removeBindings } from "../dataBinding"
|
import { removeBindings } from "../dataBinding"
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
notifications,
|
notifications,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import ErrorSVG from "assets/error.svg?raw"
|
import ErrorSVG from "assets/error.svg?raw"
|
||||||
import { findComponent, findComponentPath } from "builderStore/storeUtils"
|
import { findComponent, findComponentPath } from "builderStore/componentUtils"
|
||||||
|
|
||||||
let iframe
|
let iframe
|
||||||
let layout
|
let layout
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import { get } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
import { store, currentAsset } from "builderStore"
|
import { store, currentAsset } from "builderStore"
|
||||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||||
import { findComponentParent } from "builderStore/storeUtils"
|
import { findComponentParent } from "builderStore/componentUtils"
|
||||||
import { ActionMenu, MenuItem, Icon, notifications } from "@budibase/bbui"
|
import { ActionMenu, MenuItem, Icon, notifications } from "@budibase/bbui"
|
||||||
|
|
||||||
export let component
|
export let component
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { writable, get } from "svelte/store"
|
import { writable, get } from "svelte/store"
|
||||||
import { store as frontendStore } from "builderStore"
|
import { store as frontendStore } from "builderStore"
|
||||||
import { findComponentPath } from "builderStore/storeUtils"
|
import { findComponentPath } from "builderStore/componentUtils"
|
||||||
|
|
||||||
export const DropEffect = {
|
export const DropEffect = {
|
||||||
MOVE: "move",
|
MOVE: "move",
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
import { selectedComponent } from "builderStore"
|
import { selectedComponent } from "builderStore"
|
||||||
import { getComponentForSettingType } from "./componentSettings"
|
import { getComponentForSettingType } from "./componentSettings"
|
||||||
import PropertyControl from "./PropertyControl.svelte"
|
import PropertyControl from "./PropertyControl.svelte"
|
||||||
import { getComponentSettings } from "builderStore/storeUtils"
|
import { getComponentSettings } from "builderStore/componentUtils"
|
||||||
|
|
||||||
export let conditions = []
|
export let conditions = []
|
||||||
export let bindings = []
|
export let bindings = []
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import { Select } from "@budibase/bbui"
|
import { Select } from "@budibase/bbui"
|
||||||
import { makePropSafe } from "@budibase/string-templates"
|
import { makePropSafe } from "@budibase/string-templates"
|
||||||
import { currentAsset, store } from "builderStore"
|
import { currentAsset, store } from "builderStore"
|
||||||
import { findComponentPath } from "builderStore/storeUtils"
|
import { findComponentPath } from "builderStore/componentUtils"
|
||||||
import { createEventDispatcher, onMount } from "svelte"
|
import { createEventDispatcher, onMount } from "svelte"
|
||||||
|
|
||||||
export let value
|
export let value
|
||||||
|
|
|
@ -20,7 +20,10 @@
|
||||||
import { notifications } from "@budibase/bbui"
|
import { notifications } from "@budibase/bbui"
|
||||||
import ParameterBuilder from "components/integration/QueryParameterBuilder.svelte"
|
import ParameterBuilder from "components/integration/QueryParameterBuilder.svelte"
|
||||||
import IntegrationQueryEditor from "components/integration/index.svelte"
|
import IntegrationQueryEditor from "components/integration/index.svelte"
|
||||||
import { makePropSafe as safe } from "@budibase/string-templates"
|
import {
|
||||||
|
makePropSafe,
|
||||||
|
makePropSafe as safe,
|
||||||
|
} from "@budibase/string-templates"
|
||||||
|
|
||||||
export let value = {}
|
export let value = {}
|
||||||
export let otherSources
|
export let otherSources
|
||||||
|
@ -48,9 +51,7 @@
|
||||||
return [...acc, ...viewsArr]
|
return [...acc, ...viewsArr]
|
||||||
}, [])
|
}, [])
|
||||||
$: queries = $queriesStore.list
|
$: queries = $queriesStore.list
|
||||||
.filter(
|
.filter(q => showAllQueries || q.queryVerb === "read" || q.readable)
|
||||||
query => showAllQueries || query.queryVerb === "read" || query.readable
|
|
||||||
)
|
|
||||||
.map(query => ({
|
.map(query => ({
|
||||||
label: query.name,
|
label: query.name,
|
||||||
name: query.name,
|
name: query.name,
|
||||||
|
@ -104,13 +105,60 @@
|
||||||
value: `{{ literal ${runtimeBinding} }}`,
|
value: `{{ literal ${runtimeBinding} }}`,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
$: jsonArrays = findJSONArrays(bindings)
|
||||||
|
|
||||||
function handleSelected(selected) {
|
const findJSONArrays = bindings => {
|
||||||
|
let arrays = []
|
||||||
|
const jsonBindings = bindings.filter(x => x.fieldSchema?.type === "json")
|
||||||
|
jsonBindings.forEach(binding => {
|
||||||
|
const {
|
||||||
|
providerId,
|
||||||
|
readableBinding,
|
||||||
|
runtimeBinding,
|
||||||
|
fieldSchema,
|
||||||
|
tableId,
|
||||||
|
} = binding
|
||||||
|
const { name, type } = fieldSchema
|
||||||
|
const schemaArrays = findArraysInSchema(fieldSchema).map(path => {
|
||||||
|
const safePath = path.split(".").map(makePropSafe).join(".")
|
||||||
|
return {
|
||||||
|
providerId,
|
||||||
|
label: `${readableBinding}.${path}`,
|
||||||
|
fieldName: name,
|
||||||
|
fieldType: type,
|
||||||
|
tableId,
|
||||||
|
type: "jsonarray",
|
||||||
|
value: `{{ literal ${runtimeBinding}.${safePath} }}`,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
arrays = arrays.concat(schemaArrays)
|
||||||
|
})
|
||||||
|
|
||||||
|
return arrays
|
||||||
|
}
|
||||||
|
|
||||||
|
const findArraysInSchema = (schema, path) => {
|
||||||
|
if (!schema?.schema || !Object.keys(schema.schema).length) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
if (schema.type === "array") {
|
||||||
|
return [path]
|
||||||
|
}
|
||||||
|
let arrays = []
|
||||||
|
Object.keys(schema.schema).forEach(key => {
|
||||||
|
const newPath = `${path ? `${path}.` : ""}${key}`
|
||||||
|
const childArrays = findArraysInSchema(schema.schema[key], newPath)
|
||||||
|
arrays = arrays.concat(childArrays)
|
||||||
|
})
|
||||||
|
return arrays
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSelected = selected => {
|
||||||
dispatch("change", selected)
|
dispatch("change", selected)
|
||||||
dropdownRight.hide()
|
dropdownRight.hide()
|
||||||
}
|
}
|
||||||
|
|
||||||
function fetchQueryDefinition(query) {
|
const fetchQueryDefinition = query => {
|
||||||
const source = $datasources.list.find(
|
const source = $datasources.list.find(
|
||||||
ds => ds._id === query.datasourceId
|
ds => ds._id === query.datasourceId
|
||||||
).source
|
).source
|
||||||
|
@ -227,6 +275,17 @@
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if jsonArrays?.length}
|
||||||
|
<Divider size="S" />
|
||||||
|
<div class="title">
|
||||||
|
<Heading size="XS">Key/Value Arrays</Heading>
|
||||||
|
</div>
|
||||||
|
<ul>
|
||||||
|
{#each jsonArrays as field}
|
||||||
|
<li on:click={() => handleSelected(field)}>{field.label}</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
{/if}
|
||||||
{#if dataProviders?.length}
|
{#if dataProviders?.length}
|
||||||
<Divider size="S" />
|
<Divider size="S" />
|
||||||
<div class="title">
|
<div class="title">
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
getSchemaForDatasource,
|
getSchemaForDatasource,
|
||||||
} from "builderStore/dataBinding"
|
} from "builderStore/dataBinding"
|
||||||
import { currentAsset } from "builderStore"
|
import { currentAsset } from "builderStore"
|
||||||
import { findClosestMatchingComponent } from "builderStore/storeUtils"
|
import { findClosestMatchingComponent } from "builderStore/componentUtils"
|
||||||
|
|
||||||
export let componentInstance
|
export let componentInstance
|
||||||
export let value
|
export let value
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { ActionButton } from "@budibase/bbui"
|
import { ActionButton } from "@budibase/bbui"
|
||||||
import { currentAsset, store } from "builderStore"
|
import { currentAsset, store } from "builderStore"
|
||||||
import { findClosestMatchingComponent } from "builderStore/storeUtils"
|
import { findClosestMatchingComponent } from "builderStore/componentUtils"
|
||||||
import { makeDatasourceFormComponents } from "builderStore/store/screenTemplates/utils/commonComponents"
|
import { makeDatasourceFormComponents } from "builderStore/store/screenTemplates/utils/commonComponents"
|
||||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
DatePicker,
|
DatePicker,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import { currentAsset, selectedComponent } from "builderStore"
|
import { currentAsset, selectedComponent } from "builderStore"
|
||||||
import { findClosestMatchingComponent } from "builderStore/storeUtils"
|
import { findClosestMatchingComponent } from "builderStore/componentUtils"
|
||||||
import { getSchemaForDatasource } from "builderStore/dataBinding"
|
import { getSchemaForDatasource } from "builderStore/dataBinding"
|
||||||
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
|
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
|
||||||
import { generate } from "shortid"
|
import { generate } from "shortid"
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
import FrontendNavigatePane from "components/design/NavigationPanel/FrontendNavigatePane.svelte"
|
import FrontendNavigatePane from "components/design/NavigationPanel/FrontendNavigatePane.svelte"
|
||||||
import { goto, leftover, params } from "@roxi/routify"
|
import { goto, leftover, params } from "@roxi/routify"
|
||||||
import { FrontendTypes } from "constants"
|
import { FrontendTypes } from "constants"
|
||||||
import { findComponent, findComponentPath } from "builderStore/storeUtils"
|
import { findComponent, findComponentPath } from "builderStore/componentUtils"
|
||||||
import { get } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
import AppThemeSelect from "components/design/AppPreview/AppThemeSelect.svelte"
|
import AppThemeSelect from "components/design/AppPreview/AppThemeSelect.svelte"
|
||||||
import ThemeEditor from "components/design/AppPreview/ThemeEditor.svelte"
|
import ThemeEditor from "components/design/AppPreview/ThemeEditor.svelte"
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { fetchViewData } from "./views"
|
||||||
import { fetchRelationshipData } from "./relationships"
|
import { fetchRelationshipData } from "./relationships"
|
||||||
import { FieldTypes } from "../constants"
|
import { FieldTypes } from "../constants"
|
||||||
import { executeQuery, fetchQueryDefinition } from "./queries"
|
import { executeQuery, fetchQueryDefinition } from "./queries"
|
||||||
|
import { convertJSONSchemaToTableSchema } from "builder/src/builderStore/jsonUtils"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches all rows for a particular Budibase data source.
|
* Fetches all rows for a particular Budibase data source.
|
||||||
|
@ -75,6 +76,18 @@ export const fetchDatasourceSchema = async dataSource => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// JSON arrays need their table definitions fetched.
|
||||||
|
// We can then extract their schema as a subset of the table schema.
|
||||||
|
if (type === "jsonarray") {
|
||||||
|
const table = await fetchTableDefinition(dataSource.tableId)
|
||||||
|
const keysToSchema = dataSource.label.split(".").slice(2)
|
||||||
|
let schema = table?.schema
|
||||||
|
for (let i = 0; i < keysToSchema.length; i++) {
|
||||||
|
schema = schema[keysToSchema[i]].schema
|
||||||
|
}
|
||||||
|
return convertJSONSchemaToTableSchema(schema)
|
||||||
|
}
|
||||||
|
|
||||||
// Tables, views and links can be fetched by table ID
|
// Tables, views and links can be fetched by table ID
|
||||||
if (
|
if (
|
||||||
(type === "table" || type === "view" || type === "link") &&
|
(type === "table" || type === "view" || type === "link") &&
|
||||||
|
|
|
@ -203,6 +203,9 @@
|
||||||
} else {
|
} else {
|
||||||
allRows = data
|
allRows = data
|
||||||
}
|
}
|
||||||
|
} else if (dataSource?.type === "jsonarray") {
|
||||||
|
// JSON array sources will be available from context
|
||||||
|
allRows = dataSource?.value || []
|
||||||
} else {
|
} else {
|
||||||
// For other data sources like queries or views, fetch all rows from the
|
// For other data sources like queries or views, fetch all rows from the
|
||||||
// server
|
// server
|
||||||
|
|
Loading…
Reference in New Issue