Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how to bind svelte dynamic components values

Let's say I have this main App:

<script>
  import Field from '../components/Field.svelte';

  const components = {};
  const fields = [
    {
      id: 'Check',
      type: 'CheckBox',
      value: false,
    },
    {
      id: 'Text',
    },
  ];

  $: console.log(components);
</script>

<form>
  {#each fields as item}
    <Field {...item} bind:bind={components[item.bind]} bind:this={components[item.id]} />
  {/each}
</form>

And I have two components, CheckBox and TextArea, both just implement HTML inputs, and the Field Component is implemented like this:

<script>
  import CheckBox from './CheckBox.svelte';
  import TextArea from './TextArea.svelte';

  export let attributes = {};
  export let type = 'TextArea';
  export let value = '';
  export let id;
  export let bind;

  const fieldComponents = {
    CheckBox: CheckBox,
    TextArea: TextArea,
  };
</script>

<svelte:component this={fieldComponents[type]} {bind} {id} {value} {attributes} />

That way I'm creating a dynamic form that has a checkbox and a textarea.

What I want is the "bind" attribute to be accessible from within the component, and to be able to bind the another component, That way i'll be able to achieve something like this:

<input type="checkbox" bind:checked={bind.value}>

which means that if the textarea will have text, the checkbox would be checked, if it's empty, the checkbox would be unchecked.

after all the components render i'm able to access them using the components object because i'm binding them like this bind:this={components[item.id]}

but before they render I can't access them, is there a way to make it so one component can dynamically bind to the other?.

I demonstrated using only 2 component, it might as well be a large set of components.

The way I want to determine the binding is using a bind property inside the fields array that matches the id of another field.

like image 513
Hagai Wild Avatar asked Jan 19 '20 14:01

Hagai Wild


People also ask

Is Svelte two way binding?

Svelte understands this keyword. Here, we specify that we wish to bind the value of the input element to the name attribute in the component. The data flows into the input when user types in the browser and with every keystroke, the name is also updated. In other words, we have two-way binding.

What is $: In Svelte?

Note: Svelte uses the $: JavaScript label statement syntax to mark reactive statements.

Does Svelte use components?

In Svelte, an application is composed from one or more components. A component is a reusable, self-contained block of code that encapsulates HTML, CSS, and JavaScript that belong together, written into a . svelte file.


1 Answers

My suggestion would be to work with the Svelte store and save your form configuration object within the store. This will allow any of your Svelte components to access the form state.

A working example can be tested and forked at https://svelte.dev/repl/3f161dd253624d4ea7a3b8b9e5763e96?version=3.21.0

The code breakdown for the example app is below.

Where App.svelte is:

<script>
/*
@abstract This app is used to demonstrate one way to track form state with Svelte.
We use the 'store' to save an object that will contain our form field configurations
and field values. A JSON string formatted configuration is used as opposed to a purely javascipt object so that we can for instance pull in our form configuration from a back-end database to dynmaically build our form (in this example we are simply hard-coding the JSON into the app, but for production you might want to pull from an server-side API).
*/
import Field from './Field.svelte'; // used to build our form fields
import Box from './Box.svelte'; // just for show
import { storeFE } from './store.js';   // store our form state
let objForm;    // @testing - used to listen for changes in our form state

// @testing - keep up to date on the form object
const unsubscribe = storeFE.subscribe(value => {
        objForm = value;
});

// opting for JSON string config (which is what we would want if we are pulling this config from say a server data API)
// the 'fIndex' value is used within our form components know which form element object to work with within our main 'storeFE' object store. the 'fType' value tells the Field.svelte component which form element to build
let objFormConfig = JSON.parse(`{
    "formElements": [
        {
                "fIndex":0,
                "fId":"cc2",
                "fType": "CheckBox",
                "fValue": "true",
                "fDisable":"ct1.fValue==''"
        },
        {
                "fIndex":1,
                "fId":"ct1",
                "fType": "TextArea",
                "fValue": "textee area",
                "fChangeEvent":"cc2 disable",
                "fDisable":"cc2 checked is false"
        }
    ]
}`);
// @testing: let us know when the form values have changed (the storeFE object has updated)
$: {
    console.log('objForm:');
    console.log(objForm);
}
$storeFE = objFormConfig;   // save the initial form configuration to the store
</script>
<form>
{#each objFormConfig.formElements as item}
    <Box>
    <Field objAttributes={item}></Field>
    </Box>
{/each}
</form>

Where Field.svelte is:

<script>
import CheckBox from './CheckBox.svelte';
import TextArea from './TextArea.svelte';

export let objAttributes = {};

const fieldComponents = {
    'CheckBox': CheckBox,
    'TextArea': TextArea
};
</script>
<div>
        <svelte:component this={fieldComponents[objAttributes.fType]} {objAttributes} />
</div>

Where CheckBox.svelte is:

<script>
/* Here we want to get the store index */
import { storeFE } from './store.js';
export let objAttributes = {};
const fIndex = objAttributes.fIndex;
const strDisable = objAttributes.fDisable;
function fDisable() {
    if (strDisable) {
        console.log('do some stuff like check: '+strDisable);
    }
}
console.log("checkbox here, showing you my field attributes:");
console.log(objAttributes);
</script>
<h2>
    My checkbox
</h2>
<input id={objAttributes.fId} type=checkbox bind:checked={$storeFE.formElements[fIndex].fValue} on:change={fDisable}>

Where TextArea.svelte is:

<script>
import { storeFE } from './store.js';
export let objAttributes = {};
const fIndex = objAttributes.fIndex;


console.log("textarea here, showing you my field attributes:");
console.log(objAttributes);
</script>
<h2>
    My text
</h2>
<textarea bind:value={$storeFE.formElements[fIndex].fValue}></textarea>

Where store.js is:

import { writable } from 'svelte/store';
export let storeFE = writable({});

Box.svelte is not necessary, but just for show (pulled from Svelte tutorials):

<style>
    .box {
        width: 300px;
        border: 1px solid #aaa;
        border-radius: 2px;
        box-shadow: 2px 2px 8px rgba(0,0,0,0.1);
        padding: 1em;
        margin: 0 0 1em 0;
    }
</style>

<div class="box">
    <slot></slot>
</div>

Another example of this code with form validation can be found in this Svelte REPL app: https://svelte.dev/repl/253ddd578806497b8b54c339490f8221?version=3.21.0

like image 135
w. Patrick Gale Avatar answered Oct 08 '22 18:10

w. Patrick Gale