0

I'd like to have an input represent a list of primitive (string, number) values so you can enter them as comma-separated values but update the model as an array:

"Transforms": [
        {
            "Fn": "TheFunctionName",
            "Args": [ "arg1", 2, "arg3" ]
        },
        {
            "Fn": "AnotherMethod",
            "Args": [ 4.678 ]
        },
    ]

Would be administered with:

{{#each Transforms:i}}
   <li>
       <input value="{{Fn}}" placeholder="Function Name" />
       <input value="{{implode(Args)}}" placeholder="Function Arguments" />
   </li>
{{/each}}

and would render something like:

* [ TheFunctionName ] [ "arg1", 2, "arg3" ] * [ AnotherMethod ] [ 4.678 ]

Mainly so that I don't need to figure out a good UI to dynamically add/remove argument inputs (like bind on certain keypress, use buttons to add/remove fields, etc).

I could use a "placeholder" property for databinding, and probably observe that to update the actual property, but then I'd need to filter it out when 'serializing' the underlying model. It seems like I could use computed properties, which have a getter and a setter, but it's not clear from the docs how it works with nested properties in a list (i.e. there are many entries in data with the Transforms list).

drzaus
  • 24,171
  • 16
  • 142
  • 201

1 Answers1

0

The trick here is to not use two-way binding, but to use more traditional event handling techniques. This example listens for change events on the second input (not input events, which happen with each keystroke, since that would cause the cursor to jump about while the user is still typing) in each <li>, and tries to evaluate its contents. Meanwhile, a formatter takes the arguments and turns them back into a string:

var ractive = new Ractive({
    el: 'main',
    template: '#template',
    data: {
        transforms: [
            {
                fn: 'TheFunctionName',
                args: [ 'arg1', 2, 'arg3' ]
            },
            {
                fn: 'AnotherMethod',
                args: [ 4.678 ]
            }
        ],
        format: function ( args ) {
            return args.map( JSON.stringify ).join( ', ' );
        }    
    },
    updateArgs: function ( index, str ) {
        var keypath = 'transforms[' + index + '].args',
            args = this.get( keypath );
        
        try {
            // or use JSON.parse, if you don't want to eval
            this.set( keypath, eval( '([' + str + '])' ) );
        } catch ( err ) {
            // reset
            this.set( keypath, null );
            this.set( keypath, args );
        }
    }
});
<script src="http://cdn.ractivejs.org/edge/ractive.js"></script>

<main></main>

<script id='template' type='text/ractive'>
    <h2>input</h2>
    <ul>
        {{#each transforms:i}}
            <li>
                <input value='{{fn}}' placeholder='function name'/>
                <input
                    twoway='false'
                    value='{{format(args)}}'
                    placeholder='function arguments'
                    on-change='updateArgs(i,event.node.value)'
                />
            </li>
        {{/each}}
    </ul>
    
    <h2>output</h2>
    <ul>
        {{#each transforms}}
            <li>
                <p>function name: <strong>{{fn}}</strong></p>
                <p>args:</p>
                <ul>
                    {{#each args}}
                        <p>{{JSON.stringify(this)}} ({{typeof this}})</p>
                    {{/each}}
                </ul>
            </li>
        {{/each}}
    </ul>
</script>
Rich Harris
  • 28,091
  • 3
  • 84
  • 99
  • I had just about come to [the same conclusion](https://github.com/ractivejs/ractive/issues/689#issuecomment-67568203), but I wasn't sure of the right way to declare it. I was also concerned by the console warning `Two-way binding does not work with expressions...` -- is this just a friendly reminder, and can be ignored? – drzaus Dec 19 '14 at 07:13
  • Yes, it's just a debugging message, in case anyone tried to do `` and was surprised that entering 10 didn't cause `number` to become 5. It won't cause any problems with your app, and the message (should?) be squelched in the next version if the element has `twoway='false'`. – Rich Harris Dec 19 '14 at 13:41
  • ah...I think my problems stem from the fact that I'm using CDN "latest" rather than "edge" -- I was getting an error `Uncaught TypeError: Cannot read property 'split' of undefined` on every keypress – drzaus Dec 19 '14 at 17:01
  • So this may be a dumb question, but if you're passing `event` as an argument, why not reference the `event.context` directly for updates as opposed to get/set on keypath; or for that matter use `event.keypath` rather than reconstructing it? – drzaus Dec 19 '14 at 19:34
  • I've managed to reproduced my latest issue -- [this](http://jsfiddle.net/drzaus/34aec2v9/2/) vs [that](http://jsfiddle.net/drzaus/34aec2v9/3/), where the second every so often throws `Uncaught TypeError: Cannot read property 'str' of undefined` and the next click dumps the "missing" rows – drzaus Dec 19 '14 at 20:28