JsViews how to make data binding happen on root object as well as its nested properties?
Asked Answered
S

2

6

I am experiencing odd behavior when data linking an object to a form that led me to re-question what exactly is being data bound?

Basically I have a form that creates new Companies as well as updates them. The actual creation/update is done via ajax, which is why I am using the same form for both purposes. In the case when I have to create a company, everything works as I expect. However when I have to update a company, things don't work like how I expect them to. Please have a look at the following code.

Here is my sample Form HTML:

<div id="result"></div>

<script type="text/x-jsrender" id="CompanyFormTemplate">
    <form>
        <input type="text" data-link="Company.Name" />
    </form>
</script>

Here is my Javascript code:

var app = new CompanyFormContext();

function CompanyFormContext() { 
    this.Company = {
        Name: ''
    };

    this.setCompany = function (company) {
        if (company) {
            $.observable(this).setProperty('Company', company);
        }
    };
};

$(function () {
    initPage(); 

    ...

    if (...) {
        // we need to update company information

        app.setCompany({ Name: 'Company ABC' });
    }
});

function initPage() {
    var template = $.templates('#CompanyFormTemplate');
    template.link("#result", app);
}

Instead of the form input showing 'Company ABC', it is empty. However if I enter anything in it, then the Company.Name value does change! But while I want the input to data bind to Name property of my Company object, I also want it to be aware of any changes made to the (parent) Company object and update it's data binding to it's Name property accordingly.

So my question is how should I change the way I am writing this code so that I can achieve a data bound both on the root object as well as the property?

Signboard answered 19/5, 2015 at 7:38 Comment(0)
B
4

The issue you were having was because in your scenario, you have paths like Company.Name for which you want to data-link to changes not only of the leaf property but also to changes involving replacing objects higher up in the path (in this case the Company).

For that you need to use the syntax data-link="Company^Path".

See the section Paths: leaf changes or deep changes in this documentation topic: http://www.jsviews.com/#observe@deep.

See also the examples such as Example: JsViews with plain objects and array in this topic: http://www.jsviews.com/#explore/objectsorvm.

Here is an update of your jsfiddle, using that syntax: https://jsfiddle.net/msd5oov9/2/.

BTW, FWIW, in your fix using {^{for}} you didn't have to use a second template - you could alternatively have written:

<form class="form-horizontal">
    {^{for Company}}
        ...
        <input type="text" data-link="Name" />
    {{/for}}
</form>

To respond also to your follow-up question in your comment below, you can associate any 'block' tag with a template. Using tmpl=... on the tag means you have decided to separate what would have been the block content into a separate re-usable template. (A 'partial', if you will). The data context for that template will be the same as it would have been within the block.

So specifically, {{include}} {{if}} and {{else}} tags do not move the data context, but {{for}} and {{props}} do. With custom tags you can decide...

So in your case you could use either {^{for Company tmpl=.../}} or {{include tmpl=.../}} but in the second case your other template that you reference would use <input type="text" data-link="Company^Name" /> rather than <input type="text" data-link="Name" />.

Here are some relevant links:

Bendicty answered 19/5, 2015 at 16:26 Comment(1)
Thank you so much for sharing the links to the appropriate sections! Ironically i had gone through both sections at some point in the past but when I was writing this code it just didn't occur to me that was what I had to do. One minor question: is there any other construct that I should be using to indicate render an object using template X instead of for?Signboard
S
1

I discovered one way to achieve this. It might seem complex at first but it will make sense once you understand it properly.

(PS: I wish there was a sample like this. I might just blog about it.)

HTML Markup:

<script type="text/x-jsrender" id="CompanyFormTemplate">
    <form>
        {^{for Company tmpl="#CompanyDetailsTemplate" /}
    </form>
</script>

<script type="text/x-jsrender" id="CompanyDetailsTemplate">
    <input type="text" data-link="Name" />
</script>

Javascript: No changes needed from code above.


Okay so as I said, the solution might look complicated but it turns out all I really had to do was to set up data binding first on the Company object, and then to it's property objects. I wonder if there is a more elegant solution (i.e. one in which all of this can be achieved in a single template) however this solution ensures that data-binding is happening both on the parent object as well as its' properties.

I have posted a JsFiddle for this solution, so if anyone comes across this problem and wants to understand how this solution would work for their particular problem, they will be able to play with a working solution.

Signboard answered 19/5, 2015 at 8:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.