Search code examples
javascriptjsonaurelia

Schema form using Aurelia


I am using Aurelia to build a dynamic form based on a json. The form is generating from a json like the following:

Schema = [{
    'key': 'Name',
    'display': 'Name',
    'type': 'text',
    'placeholder': 'Name',
    'required': true
},
{
    'key': 'IsSubscribed',
    'display': 'Subscribed to newsletter?',
    'type': 'checkbox',
    'placeholder': null,
    'required': false
}];

The model to fill the form is available via a Web API service. So, far I succeeded using the following template.

    <template>

    <section class="au-animate">
    <h2>Edit Form</h2>
    <form class="form-group">
        <div repeat.for="item of Schema" class="form-group">
            <label if.bind="item.type === 'text' || item.type === 'checkbox'" class="control-label" for.bind="item.key">${item.display}
                <input class="form-control" id.bind="item.key" placeholder.bind="item.placeholder" type.bind="item.type" value.bind="Model[item.key]" />    
            </label>
            <label if.bind="item.type === 'textarea'">${item.display}
                <textarea placeholder.bind="item.placeholder" value.bind="Model[item.key]></textarea>
            </label>
            ...
        </div>
    </form>
    </section>

    </template>

Now I am facing difficulties, when the Model contains another object as property. E.g., for the property Address I would like a input box for City.

Hence, item.key = "Address.City".

I can bind with (1) Model.Address.City or (2) Model['Address']['City'] which are not possible as the form is generating at runtime. I would like to use something like (3) Model['Address.City'], so that I can use Model[item.key] for the binding. Is there any easy syntax to achieve this?

Example of similar application in Angular Js is Angular Schema Form

Thanks in advance.


Solution

  • This can be accomplished with a binding behavior that understands what to do with the keys. The end-result is the binding will function like any other binding expression.

    Here's an example: https://gist.run?id=720d20b2db5adba92f62f7e665cf3b96

    app.html

    <template>
      <require from="./dynamic-expression-binding-behavior"></require>
    
      <label>
        Address 1:
        <input value.bind="model & dynamicExpression:'address.address1'">
      </label>
      <label>
        Address 2:
        <input value.bind="model & dynamicExpression:'address.address2'">
      </label>
      <label>
        City:
        <input value.bind="model & dynamicExpression:key">
      </label>
      <label>
        State:
        <input value.bind="model & dynamicExpression:'address.state'">
      </label>
      <label>
        Zip:
        <input value.bind="model & dynamicExpression:'address.zip'">
      </label>
    </template>
    

    app.js

    export class App {
      model = {
        address: {
          address1: '1 Main Street',
          address2: '',
          city: 'Burlington',
          state: 'VT',
          zip: '05401'
        }
      };
    
      key = 'address.city';
    }
    

    dynamic-expression-binding-behavior.js

    import {inject} from 'aurelia-dependency-injection';
    import {Parser} from 'aurelia-binding';
    import {rebaseExpression} from './expression-rebaser';
    
    @inject(Parser)
    export class DynamicExpressionBindingBehavior {
      constructor(parser) {
        this.parser = parser;
      }
    
      bind(binding, source, rawExpression) {
        // Parse the expression that was passed as a string argument to
        // the binding behavior.
        let expression = this.parser.parse(rawExpression);
    
        // Rebase the expression
        expression = rebaseExpression(expression, binding.sourceExpression);
    
        // Squirrel away the binding's original expression so we can restore
        // the binding to it's initial state later.
        binding.originalSourceExpression = binding.sourceExpression;
    
        // Replace the binding's expression.
        binding.sourceExpression = expression;
      }
    
      unbind(binding, source) {
        // Restore the binding to it's initial state.
        binding.sourceExpression = binding.originalSourceExpression;
        binding.originalSourceExpression = null;
      }
    }