Skip to content

Commit

Permalink
Create separate pages for data views
Browse files Browse the repository at this point in the history
  • Loading branch information
nikolatechie committed Jan 12, 2024
1 parent ba5be58 commit 7a33287
Show file tree
Hide file tree
Showing 28 changed files with 409 additions and 594 deletions.
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,10 @@ CxJS uses React for DOM manipulation and offers many high-level features on top
- [controllers](https://cxjs.io/docs/concepts/controllers)
- [computed values](https://cxjs.io/docs/concepts/controllers#computed-values)
- [triggers](https://cxjs.io/docs/concepts/controllers#triggers)
- [data views](https://cxjs.io/docs/concepts/data-views)
- [repeater](https://cxjs.io/docs/concepts/repeater)
- [rescope](https://cxjs.io/docs/concepts/rescope)
- [sandbox](https://cxjs.io/docs/concepts/sandbox)
- [data proxy](https://cxjs.io/docs/concepts/data-proxy)

### Layout

Expand Down
5 changes: 4 additions & 1 deletion docs/app/DocsNav.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,10 @@ export const docsNavTree = [
{ text: "Store", url: "~/concepts/store" },
{ text: "Widgets", url: "~/concepts/widgets" },
{ text: "Data Binding", url: "~/concepts/data-binding" },
{ text: "Data Views", url: "~/concepts/data-views" },
{ text: "Repeater", url: "~/concepts/repeater" },
{ text: "Rescope", url: "~/concepts/rescope" },
{ text: "Sandbox", url: "~/concepts/sandbox" },
{ text: "Data Proxy", url: "~/concepts/data-proxy" },
{ text: "Controllers", url: "~/concepts/controllers" },
{ text: "Inner Layouts", url: "~/concepts/inner-layouts" },
{ text: "CSS", url: "~/concepts/css" },
Expand Down
14 changes: 7 additions & 7 deletions docs/content/concepts/DataBinding.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export const DataBinding = <cx>
</div>

<Content name="code">
<Tab value-bind="$page.code1.tab" mod="code" tab="code" text="Code" default />
<Tab value-bind="$page.code1.tab" mod="code" tab="code" text="Code" default />
<CodeSnippet visible-expr="{$page.code1.tab}=='code'" fiddle="csRQr9CA">{`
<Checkbox value-bind='intro.core.checked'>Checkbox</Checkbox>
`}</CodeSnippet>
Expand All @@ -55,7 +55,7 @@ export const DataBinding = <cx>
</div>
<Content name="code">
<Tab value-bind="$page.code2.tab" mod="code" tab="code" text="Code" default />
<Tab value-bind="$page.code2.tab" mod="code" tab="code" text="Code" default />
<CodeSnippet visible-expr="{$page.code2.tab}=='code'" fiddle="csRQr9CA">{`
<TextField
value-bind='intro.core.text'
Expand Down Expand Up @@ -94,7 +94,7 @@ export const DataBinding = <cx>
</div>
<Content name="code">
<Tab value-bind="$page.code3.tab" mod="code" tab="code" text="Code" default />
<Tab value-bind="$page.code3.tab" mod="code" tab="code" text="Code" default />
<CodeSnippet visible-expr="{$page.code3.tab}=='code'" fiddle="M60J0cyg">{`
<div layout={LabelsLeftLayout}>
<TextField value-bind='intro.core.firstName' label="First Name" />
Expand Down Expand Up @@ -126,7 +126,7 @@ export const DataBinding = <cx>
</div>
<Content name="code">
<Tab value-bind="$page.code4.tab" mod="code" tab="code" text="Code" default />
<Tab value-bind="$page.code4.tab" mod="code" tab="code" text="Code" default />
<CodeSnippet visible-expr="{$page.code4.tab}=='code'" fiddle="dfT9CWn4">{`
store.set('intro.core.letterCount', '');
...
Expand Down Expand Up @@ -169,7 +169,7 @@ export const DataBinding = <cx>
</div>

<Content name="code">
<Tab value-bind="$page.code5.tab" mod="code" tab="code" text="Code" default />
<Tab value-bind="$page.code5.tab" mod="code" tab="code" text="Code" default />
<CodeSnippet visible-expr="{$page.code5.tab}=='code'" fiddle="9CxYWdfS">{`
<div preserveWhitespace>
<NumberField value-bind='intro.core.a' placeholder="A" />
Expand Down Expand Up @@ -204,7 +204,7 @@ export const DataBinding = <cx>
</div>

<Content name="code">
<Tab value-bind="$page.code6.tab" mod="code" tab="code" text="Code" default />
<Tab value-bind="$page.code6.tab" mod="code" tab="code" text="Code" default />
<CodeSnippet visible-expr="{$page.code6.tab}=='code'" fiddle="9CxYWdfS">{`
<div preserveWhitespace>
A + 2 = <NumberField style="width:50px"
Expand Down Expand Up @@ -249,7 +249,7 @@ export const DataBinding = <cx>
</div>

<Content name="code">
<Tab value-bind="$page.code7.tab" mod="code" tab="code" text="Code" default />
<Tab value-bind="$page.code7.tab" mod="code" tab="code" text="Code" default />
<CodeSnippet visible-expr="{$page.code7.tab}=='code'" fiddle="RAVD9CLT">{`
<div>
<Section class="well" title="Direct">
Expand Down
143 changes: 143 additions & 0 deletions docs/content/concepts/DataProxy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import { Content, Slider, Tab } from 'cx/widgets';
import { Rescope, DataProxy, computable, LabelsTopLayout } from 'cx/ui';
import { Md } from '../../components/Md';
import { CodeSplit } from '../../components/CodeSplit';
import { CodeSnippet } from '../../components/CodeSnippet';
import { ImportPath } from '../../components/ImportPath';
import { ConfigTable } from '../../components/ConfigTable';
import dataProxyConfig from './configs/DataProxy';

import { enableFatArrowExpansion } from "cx/data";
enableFatArrowExpansion();

export const DataProxyPage = <cx>
<Md>
<Rescope bind="$page">
<CodeSplit>
# DataProxy
<ImportPath path="import { DataProxy } from 'cx/ui';" />

The simplest use case for `DataProxy` is when we want to create an alias for a certain store binding.
In the example below, `level` binding is also made available as `$level`.
This creates a simple two-way mapping between the two store values. Moving one slider will affect the other.

<div class="widgets flex-row">
<LabelsTopLayout>
<Slider value-bind="level" label="Level" />
</LabelsTopLayout>
<DataProxy
value-bind="level"
alias="$level"
>
<LabelsTopLayout>
<Slider value-bind="$level" label="Level alias" />
</LabelsTopLayout>
</DataProxy>
</div>
<Content name="code">
<Tab value-bind="$page.code1.tab" mod="code" tab="code" text="DataProxy" default />
<CodeSnippet fiddle="YwPbwL8u">{`
<div class="widgets flex-row">
<LabelsTopLayout>
<Slider value-bind="level" label="Level" />
</LabelsTopLayout>
<DataProxy
value-bind="level"
alias="$level"
>
<LabelsTopLayout>
<Slider value-bind="$level" label="Level alias" />
</LabelsTopLayout>
</DataProxy>
</div>
`}</CodeSnippet>
</Content>
</CodeSplit>
</Rescope>
### Defining multiple aliases
<Rescope>
<CodeSplit>
`data` property is used to define multiple mappings. `data` is an object whose property names serve as aliases,
and their values are objects with `expr` and `set` properties that define custom getter and setter logic:
- `expr` defines a getter logic and can be a Cx computable or an expression,
- `set` is a function that receives the alias value and the `instance` object as parameters. The `store` can be accessed directly
with destructuring assignment syntax. Note that the setter function needs to call the `store.set` method explicitly
in order to set the `level` value, as opposed to just returning the calculated value.
This is because we can use any number of store values to calculate the alias,
and it's up to us to define the setter logic correctly.

Omitting the `set` property will make the alias itself a read-only. Attempting to change its value will log
an error to the console, so the UI should not allow it.

<div class="widgets flex-row flex-start">
<LabelsTopLayout>
<Slider value-bind="level" label="Level" />
</LabelsTopLayout>
<DataProxy
data={{
$invertedLevel: {
expr: computable("level", v => 100 - v),
set: (value, {store}) => {
store.set("level", 100 - value);
}
},
// read-only
$level: {
expr: "{level}"
}
}}
>
<div class="flex-column">
<Slider value-bind="$invertedLevel" label="Inverted level" />
<Slider value-bind="$level" label="Level (read-only)" readOnly />
</div>
</DataProxy>
</div>
<Content name="code">
<Tab value-bind="$page.code2.tab" mod="code" tab="code" text="DataProxy" default />
<CodeSnippet fiddle="2HxXBe43">{`
<div class="widgets flex-row flex-start">
<LabelsTopLayout>
<Slider value-bind="level" label="Level" />
</LabelsTopLayout>
<DataProxy
data={{
$invertedLevel: {
expr: computable("level", v => 100 - v),
set: (value, {store}) => {
store.set("level", 100 - value);
}
},
// read-only
$level: {
expr: "{level}"
}
}}
>
<div class="flex-column">
<Slider value-bind="$invertedLevel" label="Inverted level" />
<Slider value-bind="$level" label="Level (read-only)" />
</div>
</DataProxy>
</div>
`}</CodeSnippet>
</Content>

If mapping is done in both directions (both getter and setter are used), it is important that both operations are reversible, without any data loss.
This means, for any alias value, we should be able to get back all of the store values that were used to calculate it.
Failing to do so will cause bugs that are hard to detect.

**Note**: It is good practice to prefix the alias name with a `$` sign in order to avoid unintentional name shadowing
which will cause an infinite get-set loop and a `Maximum call stack exceeded` error.

### Advanced example

[Exposing currently selected Grid record](~/examples/grid/form-edit) for real time editing is a common use case for `DataProxy`.

</CodeSplit>
</Rescope>

### Configuration
<ConfigTable props={dataProxyConfig} />
</Md>
</cx>
Loading

0 comments on commit 7a33287

Please sign in to comment.