diff --git a/packages/bbui/src/Form/Core/Checkbox.svelte b/packages/bbui/src/Form/Core/Checkbox.svelte
index a5b366c262..8384c8ca09 100644
--- a/packages/bbui/src/Form/Core/Checkbox.svelte
+++ b/packages/bbui/src/Form/Core/Checkbox.svelte
@@ -47,7 +47,9 @@
- {text || ""}
+ {#if text}
+ {text}
+ {/if}
diff --git a/packages/bbui/src/Table/Table.svelte b/packages/bbui/src/Table/Table.svelte
index e89b4e849a..c9d7f12339 100644
--- a/packages/bbui/src/Table/Table.svelte
+++ b/packages/bbui/src/Table/Table.svelte
@@ -5,6 +5,7 @@
import SelectEditRenderer from "./SelectEditRenderer.svelte"
import { cloneDeep, deepGet } from "../helpers"
import ProgressCircle from "../ProgressCircle/ProgressCircle.svelte"
+ import Checkbox from "../Form/Checkbox.svelte"
/**
* The expected schema is our normal couch schemas for our tables.
@@ -31,7 +32,6 @@
export let allowEditRows = true
export let allowEditColumns = true
export let selectedRows = []
- export let editColumnTitle = "Edit"
export let customRenderers = []
export let disableSorting = false
export let autoSortColumns = true
@@ -50,6 +50,8 @@
// Table state
let height = 0
let loaded = false
+ let checkboxStatus = false
+
$: schema = fixSchema(schema)
$: if (!loading) loaded = true
$: fields = getFields(schema, showAutoColumns, autoSortColumns)
@@ -67,6 +69,16 @@
$: showEditColumn = allowEditRows || allowSelectRows
$: cellStyles = computeCellStyles(schema)
+ // Deselect the "select all" checkbox when the user navigates to a new page
+ $: {
+ let checkRowCount = rows.filter(o1 =>
+ selectedRows.some(o2 => o1._id === o2._id)
+ )
+ if (checkRowCount.length === 0) {
+ checkboxStatus = false
+ }
+ }
+
const fixSchema = schema => {
let fixedSchema = {}
Object.entries(schema || {}).forEach(([fieldName, fieldSchema]) => {
@@ -197,13 +209,32 @@
if (!allowSelectRows) {
return
}
- if (selectedRows.includes(row)) {
- selectedRows = selectedRows.filter(selectedRow => selectedRow !== row)
+ if (selectedRows.some(selectedRow => selectedRow._id === row._id)) {
+ selectedRows = selectedRows.filter(
+ selectedRow => selectedRow._id !== row._id
+ )
} else {
selectedRows = [...selectedRows, row]
}
}
+ const toggleSelectAll = e => {
+ const select = !!e.detail
+ if (select) {
+ // Add any rows which are not already in selected rows
+ rows.forEach(row => {
+ if (selectedRows.findIndex(x => x._id === row._id) === -1) {
+ selectedRows.push(row)
+ }
+ })
+ } else {
+ // Remove any rows from selected rows that are in the current data set
+ selectedRows = selectedRows.filter(el =>
+ rows.every(f => f._id !== el._id)
+ )
+ }
+ }
+
const computeCellStyles = schema => {
let styles = {}
Object.keys(schema || {}).forEach(field => {
@@ -244,7 +275,14 @@
- {editColumnTitle || ""}
+ {#if allowSelectRows}
+
+ {:else}
+ Edit
+ {/if}
{/if}
{#each fields as field}
@@ -302,11 +340,16 @@
{#if showEditColumn}
{
+ toggleSelectRow(row)
+ e.stopPropagation()
+ }}
>
toggleSelectRow(row)}
+ selected={selectedRows.findIndex(
+ selectedRow => selectedRow._id === row._id
+ ) !== -1}
onEdit={e => editRow(e, row)}
{allowSelectRows}
{allowEditRows}
diff --git a/packages/bbui/yarn.lock b/packages/bbui/yarn.lock
index 28c009b331..33c3c391be 100644
--- a/packages/bbui/yarn.lock
+++ b/packages/bbui/yarn.lock
@@ -53,10 +53,10 @@
to-gfm-code-block "^0.1.1"
year "^0.2.1"
-"@budibase/string-templates@^1.0.66-alpha.0":
- version "1.0.72"
- resolved "https://registry.yarnpkg.com/@budibase/string-templates/-/string-templates-1.0.72.tgz#acc154e402cce98ea30eedde9c6124183ee9b37c"
- integrity sha512-w715TjgO6NUHkZNqoOEo8lAKJ/PQ4b00ATWSX5VB523SAu7y/uOiqKqV1E3fgwxq1o8L+Ff7rn9FTkiYtjkV/g==
+"@budibase/string-templates@^1.0.72-alpha.0":
+ version "1.0.75"
+ resolved "https://registry.yarnpkg.com/@budibase/string-templates/-/string-templates-1.0.75.tgz#5b4061f1a626160ec092f32f036541376298100c"
+ integrity sha512-hPgr6n5cpSCGFEha5DS/P+rtRXOLc72M6y4J/scl59JvUi/ZUJkjRgJdpQPdBLu04CNKp89V59+rAqAuDjOC0g==
dependencies:
"@budibase/handlebars-helpers" "^0.11.7"
dayjs "^1.10.4"
diff --git a/packages/builder/cypress/integration/createTable.spec.js b/packages/builder/cypress/integration/createTable.spec.js
index bac6806bcd..81b7c2f045 100644
--- a/packages/builder/cypress/integration/createTable.spec.js
+++ b/packages/builder/cypress/integration/createTable.spec.js
@@ -27,10 +27,13 @@ filterTests(["smoke", "all"], () => {
it("updates a column on the table", () => {
cy.get(".title").click()
cy.get(".spectrum-Table-editIcon > use").click()
- cy.get("input").eq(1).type("updated", { force: true })
+ cy.get(".modal-inner-wrapper").within(() => {
+
+ cy.get("input").eq(0).type("updated", { force: true })
// Unset table display column
cy.get(".spectrum-Switch-input").eq(1).click()
cy.contains("Save Column").click()
+ })
cy.contains("nameupdated ").should("contain", "nameupdated")
})
diff --git a/packages/builder/cypress/support/commands.js b/packages/builder/cypress/support/commands.js
index 8e205f7d67..c05e6c8191 100644
--- a/packages/builder/cypress/support/commands.js
+++ b/packages/builder/cypress/support/commands.js
@@ -172,17 +172,19 @@ Cypress.Commands.add("addRow", values => {
Cypress.Commands.add("addRowMultiValue", values => {
cy.contains("Create row").click()
- cy.get(".spectrum-Form-itemField")
- .click()
- .then(() => {
- cy.get(".spectrum-Popover").within(() => {
- for (let i = 0; i < values.length; i++) {
- cy.get(".spectrum-Menu-item").eq(i).click()
- }
+ cy.get(".spectrum-Modal").within(() => {
+ cy.get(".spectrum-Form-itemField")
+ .click()
+ .then(() => {
+ cy.get(".spectrum-Popover").within(() => {
+ for (let i = 0; i < values.length; i++) {
+ cy.get(".spectrum-Menu-item").eq(i).click()
+ }
+ })
+ cy.get(".spectrum-Dialog-grid").click("top")
+ cy.get(".spectrum-ButtonGroup").contains("Create").click()
})
- cy.get(".spectrum-Dialog-grid").click("top")
- cy.get(".spectrum-ButtonGroup").contains("Create").click()
- })
+ })
})
Cypress.Commands.add("createUser", email => {
diff --git a/packages/builder/src/builderStore/dataBinding.js b/packages/builder/src/builderStore/dataBinding.js
index edb12c7e74..60cce2b1fd 100644
--- a/packages/builder/src/builderStore/dataBinding.js
+++ b/packages/builder/src/builderStore/dataBinding.js
@@ -32,12 +32,14 @@ export const getBindableProperties = (asset, componentId) => {
const urlBindings = getUrlBindings(asset)
const deviceBindings = getDeviceBindings()
const stateBindings = getStateBindings()
+ const selectedRowsBindings = getSelectedRowsBindings(asset)
return [
...contextBindings,
...urlBindings,
...stateBindings,
...userBindings,
...deviceBindings,
+ ...selectedRowsBindings,
]
}
@@ -315,6 +317,40 @@ const getDeviceBindings = () => {
return bindings
}
+/**
+ * Gets all selected rows bindings for tables in the current asset.
+ */
+const getSelectedRowsBindings = asset => {
+ let bindings = []
+ if (get(store).clientFeatures?.rowSelection) {
+ // Add bindings for table components
+ let tables = findAllMatchingComponents(asset?.props, component =>
+ component._component.endsWith("table")
+ )
+ const safeState = makePropSafe("rowSelection")
+ bindings = bindings.concat(
+ tables.map(table => ({
+ type: "context",
+ runtimeBinding: `${safeState}.${makePropSafe(table._id)}`,
+ readableBinding: `${table._instanceName}.Selected rows`,
+ }))
+ )
+
+ // Add bindings for table blocks
+ let tableBlocks = findAllMatchingComponents(asset?.props, component =>
+ component._component.endsWith("tableblock")
+ )
+ bindings = bindings.concat(
+ tableBlocks.map(block => ({
+ type: "context",
+ runtimeBinding: `${safeState}.${makePropSafe(block._id + "-table")}`,
+ readableBinding: `${block._instanceName}.Selected rows`,
+ }))
+ )
+ }
+ return bindings
+}
+
/**
* Gets all state bindings that are globally available.
*/
@@ -597,14 +633,9 @@ const buildFormSchema = component => {
* in the app.
*/
export const getAllStateVariables = () => {
- // Get all component containing assets
- let allAssets = []
- allAssets = allAssets.concat(get(store).layouts || [])
- allAssets = allAssets.concat(get(store).screens || [])
-
// Find all button action settings in all components
let eventSettings = []
- allAssets.forEach(asset => {
+ getAllAssets().forEach(asset => {
findAllMatchingComponents(asset.props, component => {
const settings = getComponentSettings(component._component)
settings
@@ -635,6 +666,15 @@ export const getAllStateVariables = () => {
return Array.from(bindingSet)
}
+export const getAllAssets = () => {
+ // Get all component containing assets
+ let allAssets = []
+ allAssets = allAssets.concat(get(store).layouts || [])
+ allAssets = allAssets.concat(get(store).screens || [])
+
+ return allAssets
+}
+
/**
* Recurses the input object to remove any instances of bindings.
*/
diff --git a/packages/builder/src/builderStore/store/frontend.js b/packages/builder/src/builderStore/store/frontend.js
index 9ce66db3c0..d8118c9c60 100644
--- a/packages/builder/src/builderStore/store/frontend.js
+++ b/packages/builder/src/builderStore/store/frontend.js
@@ -41,6 +41,7 @@ const INITIAL_FRONTEND_STATE = {
intelligentLoading: false,
deviceAwareness: false,
state: false,
+ rowSelection: false,
customThemes: false,
devicePreview: false,
messagePassing: false,
diff --git a/packages/client/manifest.json b/packages/client/manifest.json
index 639efe5b79..d8e589588f 100644
--- a/packages/client/manifest.json
+++ b/packages/client/manifest.json
@@ -6,7 +6,8 @@
"state": true,
"customThemes": true,
"devicePreview": true,
- "messagePassing": true
+ "messagePassing": true,
+ "rowSelection": true
},
"layout": {
"name": "Layout",
@@ -2714,6 +2715,13 @@
"key": "showAutoColumns",
"defaultValue": false
},
+ {
+ "type": "boolean",
+ "label": "Allow row selection",
+ "key": "allowSelectRows",
+ "defaultValue": false
+ },
+
{
"type": "boolean",
"label": "Link table rows",
@@ -2973,6 +2981,11 @@
"label": "Show auto columns",
"key": "showAutoColumns"
},
+ {
+ "type": "boolean",
+ "label": "Allow row selection",
+ "key": "allowSelectRows"
+ },
{
"type": "boolean",
"label": "Link table rows",
diff --git a/packages/client/src/components/ClientApp.svelte b/packages/client/src/components/ClientApp.svelte
index 06b240f3ee..e8ed7e9538 100644
--- a/packages/client/src/components/ClientApp.svelte
+++ b/packages/client/src/components/ClientApp.svelte
@@ -21,6 +21,7 @@
import UserBindingsProvider from "components/context/UserBindingsProvider.svelte"
import DeviceBindingsProvider from "components/context/DeviceBindingsProvider.svelte"
import StateBindingsProvider from "components/context/StateBindingsProvider.svelte"
+ import RowSelectionProvider from "components/context/RowSelectionProvider.svelte"
import SettingsBar from "components/preview/SettingsBar.svelte"
import SelectionIndicator from "components/preview/SelectionIndicator.svelte"
import HoverIndicator from "components/preview/HoverIndicator.svelte"
@@ -90,59 +91,61 @@
-
-
- {#key $builderStore.selectedComponentId}
- {#if $builderStore.inBuilder}
-
- {/if}
- {/key}
+
+
+
+ {#key $builderStore.selectedComponentId}
+ {#if $builderStore.inBuilder}
+
+ {/if}
+ {/key}
-
-
-
-
-
- {#key `${$screenStore.activeLayout._id}-${$builderStore.previewType}`}
-
- {/key}
+
+
+
+
+
+ {#key `${$screenStore.activeLayout._id}-${$builderStore.previewType}`}
+
+ {/key}
-
-
+
-
-
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
+
- {#if $builderStore.inBuilder}
-
-
-
- {/if}
-
+ {#if $builderStore.inBuilder}
+
+
+
+ {/if}
+
+
diff --git a/packages/client/src/components/app/blocks/TableBlock.svelte b/packages/client/src/components/app/blocks/TableBlock.svelte
index 39d9be9a41..70980669b6 100644
--- a/packages/client/src/components/app/blocks/TableBlock.svelte
+++ b/packages/client/src/components/app/blocks/TableBlock.svelte
@@ -18,6 +18,7 @@
export let quiet
export let compact
export let size
+ export let allowSelectRows
export let linkRows
export let linkURL
export let linkColumn
@@ -157,6 +158,7 @@
>
row._id)
+ )
+ }
const getFields = (schema, customColumns, showAutoColumns) => {
// Check for an invalid column selection
@@ -117,6 +126,10 @@
const split = linkURL.split("/:")
routeStore.actions.navigate(`${split[0]}/${id}`, linkPeek)
}
+
+ onDestroy(() => {
+ rowSelectionStore.actions.updateSelection($component.id, [])
+ })
@@ -128,7 +141,8 @@
{quiet}
{compact}
{customRenderers}
- allowSelectRows={false}
+ allowSelectRows={!!allowSelectRows}
+ bind:selectedRows
allowEditRows={false}
allowEditColumns={false}
showAutoColumns={true}
@@ -139,10 +153,19 @@
>
+ {#if allowSelectRows && selectedRows.length}
+
+ {selectedRows.length} row{selectedRows.length === 1 ? "" : "s"} selected
+
+ {/if}
diff --git a/packages/client/src/components/context/RowSelectionProvider.svelte b/packages/client/src/components/context/RowSelectionProvider.svelte
new file mode 100644
index 0000000000..2c87a5fa00
--- /dev/null
+++ b/packages/client/src/components/context/RowSelectionProvider.svelte
@@ -0,0 +1,8 @@
+
+
+
+
+
diff --git a/packages/client/src/sdk.js b/packages/client/src/sdk.js
index 4851b2cc02..50ec07ba98 100644
--- a/packages/client/src/sdk.js
+++ b/packages/client/src/sdk.js
@@ -6,6 +6,7 @@ import {
screenStore,
builderStore,
uploadStore,
+ rowSelectionStore,
} from "stores"
import { styleable } from "utils/styleable"
import { linkable } from "utils/linkable"
@@ -19,6 +20,7 @@ export default {
authStore,
notificationStore,
routeStore,
+ rowSelectionStore,
screenStore,
builderStore,
uploadStore,
diff --git a/packages/client/src/stores/index.js b/packages/client/src/stores/index.js
index 9f6e5f6f50..ddd052fb4e 100644
--- a/packages/client/src/stores/index.js
+++ b/packages/client/src/stores/index.js
@@ -10,7 +10,7 @@ export { peekStore } from "./peek"
export { stateStore } from "./state"
export { themeStore } from "./theme"
export { uploadStore } from "./uploads.js"
-
+export { rowSelectionStore } from "./rowSelection.js"
// Context stores are layered and duplicated, so it is not a singleton
export { createContextStore } from "./context"
diff --git a/packages/client/src/stores/rowSelection.js b/packages/client/src/stores/rowSelection.js
new file mode 100644
index 0000000000..3d1f2038aa
--- /dev/null
+++ b/packages/client/src/stores/rowSelection.js
@@ -0,0 +1,22 @@
+import { writable } from "svelte/store"
+
+const createRowSelectionStore = () => {
+ const store = writable({})
+
+ function updateSelection(componentId, selectedRows) {
+ store.update(state => {
+ state[componentId] = [...selectedRows]
+ return state
+ })
+ }
+
+ return {
+ subscribe: store.subscribe,
+ set: store.set,
+ actions: {
+ updateSelection,
+ },
+ }
+}
+
+export const rowSelectionStore = createRowSelectionStore()