1 | # data-binding
|
2 |
|
3 | Components with bindings to data objects. These components, combined with the hooks from `@olenbetong/react-data-object-connect`, JSX can be written with a similar structure to Appframe data binding.
|
4 |
|
5 | ## Getting started
|
6 |
|
7 | ### Installation
|
8 |
|
9 | The library is published to NPM. To use, install from the command line
|
10 |
|
11 | ```
|
12 | npm i @olenbetong/data-binding
|
13 | ```
|
14 |
|
15 | Or use the IIFE build from unpkg.com
|
16 |
|
17 | ```html
|
18 | <script
|
19 | crossorigin
|
20 | src="https://unpkg.com/@olenbetong/data-binding@latest/dist/iife/data-binding.min.js"
|
21 | type="text/javascript"
|
22 | ></script>
|
23 | ```
|
24 |
|
25 | ### Context
|
26 |
|
27 | Use the DataObjectProvider to provide data object context to components in the child tree.
|
28 |
|
29 | This is the equivalent of setting data-object-id on an element in Appframe data binding.
|
30 |
|
31 | ```jsx
|
32 | import {
|
33 | DataObjectProvider,
|
34 | BoundInput
|
35 | } from "/file/component/modules/esm/data-binding.min.js";
|
36 |
|
37 | function MyComponent(props) {
|
38 | return (
|
39 | <DataObjectProvider dataObject={dsMyDataObject}>
|
40 | <BoundInput type="text" field="MyField" />
|
41 | </DataObjectProvider>
|
42 | );
|
43 | }
|
44 | ```
|
45 |
|
46 | ### Components
|
47 |
|
48 | #### BoundInput
|
49 |
|
50 | Renders an input with a 2-way binding to a field in the data object passed by data object context.
|
51 |
|
52 | ```jsx
|
53 | import {
|
54 | DataObjectProvider,
|
55 | BoundInput
|
56 | } from "/file/component/modules/esm/data-binding.min.js";
|
57 |
|
58 | function MyComponent(props) {
|
59 | return (
|
60 | <DataObjectProvider value={dsMyDataObject}>
|
61 | <BoundInput type="text" field="MyField" />
|
62 | </DataObjectProvider>
|
63 | );
|
64 | }
|
65 | ```
|
66 |
|
67 | #### BoundSelect
|
68 |
|
69 | Loads data from a data object with an optional filter, and displays a select element with the data. Doesn't use the data object directly, but uses a data handler. This means the select is independent from the data object state.
|
70 |
|
71 | There are two ways to set the options from the data object:
|
72 |
|
73 | 1. Pass a function to the `option` property. This should take the record as the first argument and return a React node (<option/>)
|
74 | 2. Set the `valueField` to the data object field that represents the value. Optionally set the `descriptionField` to another field that should be shown in the dropdown.
|
75 |
|
76 | If the `dataObject` property isn't set, the select will still be bound to the current data object context, but children have to be set manually. It is also possible to add options manually in addition to data object options.
|
77 |
|
78 | Other properties:
|
79 |
|
80 | - `dataObject` - Data object used to list the options
|
81 | - `filter` - Filter passed to the data object
|
82 | - `field` - Field to bind the value to
|
83 | - `nullable` - If true, will add an empty option (default: `true`)
|
84 |
|
85 | The components in this example will create the same select using the 2 different methods above:
|
86 |
|
87 | ```jsx
|
88 | import { DataObjectProvider, BoundSelect } from "@olenbetong/data-binding";
|
89 |
|
90 | function MyComponentWithOptionFunc() {
|
91 | return (
|
92 | <BoundSelect
|
93 | dataObject={dsSomeDataObject}
|
94 | filter="IsAGoodRecord = 1"
|
95 | option={record => <option value={record.Value}>{record.Name}</option>}
|
96 | />
|
97 | );
|
98 | }
|
99 |
|
100 | function MyComponentWithFields() {
|
101 | return (
|
102 | <BoundSelect
|
103 | dataObject={dsSomeDataObject}
|
104 | filter="IsAGoodRecord = 1"
|
105 | valueField="Value"
|
106 | descriptionField="Name"
|
107 | />
|
108 | );
|
109 | }
|
110 |
|
111 | function MyComponentWithUnboundOptions() {
|
112 | return (
|
113 | <BoundSelect field="SomeEnumerableField">
|
114 | <option value="value1">Value 1</option>
|
115 | <option value="value2">Value 2</option>
|
116 | <option value="value3">Value 3</option>
|
117 | </BoundSelect>
|
118 | );
|
119 | }
|
120 |
|
121 | function MyComponentWithCombinedOptions() {
|
122 | return (
|
123 | <BoundSelect
|
124 | field="SomeEnumerableField"
|
125 | dataObject={dsSomeDataObject}
|
126 | filter="IsAGoodRecord = 1"
|
127 | valueField="Value"
|
128 | descriptionField="Name"
|
129 | >
|
130 | <option value="value1">Value 1</option>
|
131 | <option value="value2">Value 2</option>
|
132 | <option value="value3">Value 3</option>
|
133 | </BoundSelect>
|
134 | );
|
135 | }
|
136 | ```
|
137 |
|
138 | #### SaveButton
|
139 |
|
140 | Calls endEdit on the data object passed by data object context.
|
141 |
|
142 | Unlike save buttons in Appframe data binding, the button is not disabled if the record isn't dirty. This can be achieved using the useDirty hook from `@olenbetong/react-data-object-connect`. This also applies to the CancelButton.
|
143 |
|
144 | ```jsx
|
145 | import { DataObjectProvider, SaveButton } from '/file/component/modules/esm/data-binding.min.js';
|
146 |
|
147 | function MyOuterComponent(props) {
|
148 | return (
|
149 | <DataObjectProvider value={dsMyDataObject}>
|
150 | <MySaveButton />
|
151 | </DataObjectProvider>
|
152 | )
|
153 | }
|
154 |
|
155 | function MyComponent(props) {
|
156 | const dataObject = useContext(DataObjectProvider);
|
157 | const isDirty = useDirty(dataObject);
|
158 |
|
159 | return (
|
160 | <SaveButton disabled={!isDirty} className='btn btn-secondary'>
|
161 | <i className='fa fa-save mr-2'/>
|
162 | Save changes
|
163 | </SaveButton>
|
164 | )
|
165 | ```
|
166 |
|
167 | #### CancelButton
|
168 |
|
169 | Calls cancelEdit on the data object passed by data object context.
|
170 |
|
171 | ```jsx
|
172 | import { DataObjectProvider, CancelButton } from '/file/component/modules/esm/data-binding.min.js';
|
173 |
|
174 | function MyComponent(props) {
|
175 | return (
|
176 | <DataObjectProvider value={dsMyDataObject}>
|
177 | <CancelButton className='btn btn-secondary'>
|
178 | Cancel changes
|
179 | </CancelButton>
|
180 | </DataObjectProvider>
|
181 | )
|
182 | ```
|
183 |
|
184 | #### DeleteButton
|
185 |
|
186 | Deletes the current row of the data object passed by data object context. Takes a boolean confirm property to prompt the user to confirm the delete. The confirm prompt message can be customized with the prompt property.
|
187 |
|
188 | The `prompt` property can also be a function. When the button is clicked, the function will be called with the props passed to the delete button as the first argument. If the function returns `true` (not other trueish values), the record will be deleted. Alternatively, a promise can be returned. The record will be deleted if the promise resolves with `true`.
|
189 |
|
190 | The delete button also accepts an `index` property to be able to use it outside of the current row. E.g. when listing the records.
|
191 |
|
192 | ```jsx
|
193 | import {
|
194 | DataObjectProvider,
|
195 | DeleteButton
|
196 | } from "/file/component/modules/esm/data-binding.min.js";
|
197 |
|
198 | function MyComponent(props) {
|
199 | return (
|
200 | <DataObjectProvider value={dsMyDataObject}>
|
201 | <DeleteButton
|
202 | className="btn btn-danger"
|
203 | confirm
|
204 | prompt="Nooooo! Please dont delete 😿"
|
205 | >
|
206 | <i className="fa fa-trash mr-2" />
|
207 | Delete
|
208 | </DeleteButton>
|
209 | </DataObjectProvider>
|
210 | );
|
211 | }
|
212 |
|
213 | function MyListComponent(props) {
|
214 | const data = useData(dsMyDataObject);
|
215 |
|
216 | return (
|
217 | <DataObjectProvider value={dsMyDataObject}>
|
218 | {data.map((record, idx) => (
|
219 | <DeleteButton
|
220 | className="btn btn-danger"
|
221 | confirm
|
222 | prompt={props =>
|
223 | new Promise(resolve => {
|
224 | if (checkIfRecordShouldBeDeleted(props.index)) {
|
225 | resolve(true);
|
226 | } else {
|
227 | resolve(false);
|
228 | }
|
229 | })
|
230 | }
|
231 | index={idx}
|
232 | >
|
233 | <i className="fa fa-trash mr-2" />
|
234 | Delete
|
235 | </DeleteButton>
|
236 | ))}
|
237 | </DataObjectProvider>
|
238 | );
|
239 | }
|
240 | ```
|
241 |
|
242 | #### Timepicker
|
243 |
|
244 | Binds a time input to a date field. If the field doesn't have a value when the time is selected, the current date will be used.
|
245 |
|
246 | ```jsx
|
247 | import { Timepicker } from "@olenbetong/data-binding";
|
248 |
|
249 | function MyComponent(props) {
|
250 | return (
|
251 | <DataObjectProvider value={dsMyDataObject}>
|
252 | <Timepicker field="MyDateField" className="form-control" />
|
253 | </DataObjectProvider>
|
254 | );
|
255 | }
|
256 | ```
|
257 |
|
258 | #### bind
|
259 |
|
260 | For custom functionality there is a small HoC available to create a bound component.
|
261 |
|
262 | If the current value should be assigned to a property other than "value", you can pass a `valueProp` option to the HoC. If you need to edit the value before it is put into the data object, you may bass a `valueConverter` function option. The argument passed to the onChange handler will be passed to the valueConverter.
|
263 |
|
264 | ```jsx
|
265 | import { bind } from "@olenbetong/data-binding";
|
266 |
|
267 | function MyComponent(props) {
|
268 | return (
|
269 | <SomeComponent
|
270 | something={props.customValueProperty}
|
271 | onChange={props.onChange}
|
272 | />
|
273 | );
|
274 | }
|
275 |
|
276 | function addOneToValue(value) {
|
277 | return value + 1;
|
278 | }
|
279 |
|
280 | export default bind(MyComponent, {
|
281 | valueProp: "customValueProperty",
|
282 | valueConverter: addOneToValue
|
283 | });
|
284 | ```
|