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()