BorisMoore / jsrender

A lightweight, powerful and highly extensible templating engine. In the browser or on Node.js, with or without jQuery.
http://www.jsviews.com
MIT License
2.67k stars 339 forks source link

view.index contains "Error: #index in nested view: use #getIndex()" #234

Closed CatsMuvva closed 10 years ago

CatsMuvva commented 10 years ago

If I have the following in a template:

<fieldset><legend>Interfaces</legend>
{^{for interfaces}}
    <fieldset><legend>{{:name}}</legend>
        <fieldset>
            <legend>
                Addresses
                <img class="action" src="/images/add-button.png" title="Add new address" id="{{:~subs("address_add", #getIndex())}}">
            </legend>
    {^{for addresses ~interface_index=#index}}
            <p>
                <input type="text" id="{{:~subs("address", ~interface_index, #getIndex())}}" size="32" data-link="address" autocomplete="no" spellcheck="false" placeholder="IPv4 or IPv6 address">
                <label for="{{:~subs("address", ~interface_index, #getIndex())}}" class="errormsg"></label>
                <label for="{{:~subs("network_prefix_length", ~interface_index, #getIndex())}}">/ </label>
                <input type="number" id="{{:~subs("network_prefix_length", ~interface_index, #getIndex())}}" size="2" data-link="prefix_length" autocomplete="no" spellcheck="false" placeholder="prefix">
                <label for="{{:~subs("network_prefix_length", ~interface_index, #getIndex())}}" class="errormsg"></label>
                <label for="{{:~subs("network_name", ~interface_index, #getIndex())}}">Network Name</label>
                <input type="text" id="{{:~subs("network_name", ~interface_index, #getIndex())}}" size="6" data-link="network_name" autocomplete="no" spellcheck="false" placeholder="network name">
                <label for="{{:~subs("network_name", ~interface_index, #getIndex())}}" class="errormsg"></label>
                <img class="action" src="/images/remove-button.png" title="Delete address" id="{{:~subs("address_delete", ~interface_index, #getIndex())}}">
            </p>
    {{/for}}
</fieldset>

There is a helper function ~subs, that merely returns the concatenation of all its arguments, separated by underscore.

The handler for id=addressadd... is

    $.observable(data).insert(data.length, new_item);
    return false;

The handler for id=addressdelete... is

    var view = $.view(this);
    $.observable(view.parent.parent.data).remove(view.index);
    return false;

The difficulty is that view.index contains "Error: #index in nested view: use #getIndex()"

I've plainly misunderstood something. Could you help?

CatsMuvva commented 10 years ago

I'm shooting in the dark ere. I updated the above template by replacing all the references to "#index# with "getIndex()". The error message remains the same. I'm baffled. The message talks of using #index where there is no such reference.

CatsMuvva commented 10 years ago

Whoops. Wrong button. This is not closed.

BorisMoore commented 10 years ago

I reopened. (You wanted it to stay open, right?) I'll look into this during this weekend...

CatsMuvva commented 10 years ago

Yes. Thank you. Don't kill yourself. Work/life balance is important.

Warmests

Nicole

On 15/02/14 17:29, Boris Moore wrote:

I reopened. (You wanted it to stay open, right?) I'll look into this during this weekend...

— Reply to this email directly or view it on GitHub https://github.com/BorisMoore/jsrender/issues/234#issuecomment-35161896.

BorisMoore commented 10 years ago

The index property on the view will have a numeric value on any view that is an indexed view (type "item"), e.g. the views that correspond to the iterated content of a {{for someArray}} block. But a view that corresponding to the content of an {{if}} block, for example, will have the standard warning string as value. That is so that if you write {{:#index}} within that view, it will output the warning telling you to instead write {{:#getIndex()}} - which will actually step up through parent views and find the first view of type "item" and output that index.

So the question is what is the element to which you attached your delete handler. (The 'this' within the handler). It it has non numeric index then it is in a nested view, not in an item view. But you can do view.getIndex() to find what the index of the nearest parent item view is.

BTW using non-self-closing markup for <img> or <input> can lead to errors. You need <img.../> and <input.../>

The following returns a view with and index as expected:

<script id="myTmpl" type="text/x-jsrender">
    <fieldset><legend>Interfaces</legend>
        {^{for interfaces}}
            <fieldset><legend>{{:name}}</legend>
                <fieldset>
                    <legend>
                        Addresses
                    </legend>
                    {^{for addresses ~interface_index=#index}}
                        <p>
                            <input type="text" spellcheck="false" placeholder="network name" />
                            <img class="action-delete" src="/images/remove-button.png" title="Delete address" />
                        </p>
                    {{/for}}
                </fieldset>
            </fieldset>
        {{/for}}
    </fieldset>
</script>

<div id="page"></div>

<script>
var myTmpl = $.templates("#myTmpl"),
    model = {
        interfaces: [
            {
                addresses: [
                    {}
                ]
            }
        ]
    };

myTmpl.link("#page", model);

$(".action-delete").on("click", function () {
    var view = $.view(this);
    //...
});
CatsMuvva commented 10 years ago

Thank you, Boris, for working on this over your weekend.

As I understand it what your saying could be summarised as follows:

  1. For my purposes we say that a block starts with a template directive {{something}} or one of its derivatives such as {{^something}}. The block is terminated with a {{/something}}.
  2. For each block a new context is created describing the environment of the script at that moment such as the "for" index and the data on which the block is operating.
  3. Each context is chained to that of the containing block through its parent member.
  4. If there is no index for a given script block, the corresponding context member contains the text "Error: #index in nested view: use #getIndex()".
  5. The getIndex function walks the chain of contexts from the current one, upwards through their parent members, until it reaches one that has a numeric index member. It returns this value.

Is that a fair summary?

CatsMuvva commented 10 years ago

My apologies to those who, like me, care about grammar. I used "your" in the above post when it should have been "you're".

BorisMoore commented 10 years ago

Yes, that is fair. To add a couple of details/clarifications, the context you are referring to is called a view, and is an instance of the rendered content of a block tag. {{for}} tags against array iterate against the array, and create a view for each item. Those views are 'item' views and have index properties.

BorisMoore commented 10 years ago

Closing - since this was a question, now answered, not an issue.