最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Knockout.js multiple external templates & multiple VM switching fails - Stack Overflow

programmeradmin2浏览0评论

I'm using the following:

  • knockout-2.1.0.js
  • koExternalTemplateEngine_all.js

What I'm trying to achieve is the following:

  • Template container loads external HTML and loads a specific VM for that HTML (works).
  • Template container loads/switches to another external HTML, alongside with the other specific VM for that HTML (works).
  • Template container switches back to the first template/VM, alongside with their VM (doesn't work!).

I'm guessing the reason it doesn't work, is because the Template is loaded before the VM (it does give me binding errors).

The structure of my site is as like this (excl. the libraries stated above):

  • index.html (Holds the template container)
  • js/script.js (Holds the main ViewModel)
  • js/firstvm.js (Holds the first ViewModel)
  • js/secondvm.js (Holds the second ViewModel)
  • tmpl/firstvm.html (Template for the first VM)
  • tmpl/secondvm.html (Template for the second VM)

Or simply download the source and view the problem.

Most important parts:

  • index.html

    <button data-bind="click: loadFirstPage">Load first page + ViewModel</button>
    <button data-bind="click: loadSecondPage">Load second page + ViewModel</button>
    < hr />
    <div data-bind="template: { name: function() { return currentTemplate(); }, data: currentData }"></div>
    
  • script.js

    function IndexViewModel() {
    
        var vm = this;
    
        this.currentTemplate = ko.observable();
        this.currentData = ko.observable();
    
        this.loadFirstPage = function() {
            vm.currentTemplate("firstvm");
            vm.currentData(new FirstViewModel());
        };
    
        this.loadSecondPage = function() {
            vm.currentTemplate("secondvm");
            vm.currentData(new SecondViewModel());
        };
    
        this.loadFirstPage();
    };
    ko.applyBindings(new IndexViewModel());
    
  • firstvm.html

    <p data-bind="text: displayValue"></p>
    
  • secondvm.html

    <p data-bind="text: displayValue2"></p>
    
  • firstvm.js

    function FirstViewModel() {
        this.displayValue = ko.observable("Text from firstvm.js");
    };
    
  • secondvm.js

    function SecondViewModel() {
        this.displayValue2 = ko.observable("Text from secondvm.js");
    };
    

I hope somebody can help me out with this one. Thanks in advance!

Ps. Forgot to mention: When the "First page" button is pressed twice, it does seem to work (probably since the correct VM is loaded).

I'm using the following:

  • knockout-2.1.0.js
  • koExternalTemplateEngine_all.js

What I'm trying to achieve is the following:

  • Template container loads external HTML and loads a specific VM for that HTML (works).
  • Template container loads/switches to another external HTML, alongside with the other specific VM for that HTML (works).
  • Template container switches back to the first template/VM, alongside with their VM (doesn't work!).

I'm guessing the reason it doesn't work, is because the Template is loaded before the VM (it does give me binding errors).

The structure of my site is as like this (excl. the libraries stated above):

  • index.html (Holds the template container)
  • js/script.js (Holds the main ViewModel)
  • js/firstvm.js (Holds the first ViewModel)
  • js/secondvm.js (Holds the second ViewModel)
  • tmpl/firstvm.html (Template for the first VM)
  • tmpl/secondvm.html (Template for the second VM)

Or simply download the source and view the problem.

Most important parts:

  • index.html

    <button data-bind="click: loadFirstPage">Load first page + ViewModel</button>
    <button data-bind="click: loadSecondPage">Load second page + ViewModel</button>
    < hr />
    <div data-bind="template: { name: function() { return currentTemplate(); }, data: currentData }"></div>
    
  • script.js

    function IndexViewModel() {
    
        var vm = this;
    
        this.currentTemplate = ko.observable();
        this.currentData = ko.observable();
    
        this.loadFirstPage = function() {
            vm.currentTemplate("firstvm");
            vm.currentData(new FirstViewModel());
        };
    
        this.loadSecondPage = function() {
            vm.currentTemplate("secondvm");
            vm.currentData(new SecondViewModel());
        };
    
        this.loadFirstPage();
    };
    ko.applyBindings(new IndexViewModel());
    
  • firstvm.html

    <p data-bind="text: displayValue"></p>
    
  • secondvm.html

    <p data-bind="text: displayValue2"></p>
    
  • firstvm.js

    function FirstViewModel() {
        this.displayValue = ko.observable("Text from firstvm.js");
    };
    
  • secondvm.js

    function SecondViewModel() {
        this.displayValue2 = ko.observable("Text from secondvm.js");
    };
    

I hope somebody can help me out with this one. Thanks in advance!

Ps. Forgot to mention: When the "First page" button is pressed twice, it does seem to work (probably since the correct VM is loaded).

Share Improve this question asked Jul 31, 2012 at 13:31 MarcoKMarcoK 6,1102 gold badges29 silver badges40 bronze badges 3
  • Have you tried changing your template: {name: } binding to bind directly to a puted or observable rather than a function? ko should auto eval the changes in that case. – Nick Nieslanik Commented Jul 31, 2012 at 14:15
  • @Nick Yup. Sadly, that doesn't work.. – MarcoK Commented Jul 31, 2012 at 14:24
  • data-bind="template: { name: currentTemplate, data: currentData }" Works for me. – Kyeotic Commented Jul 31, 2012 at 16:18
Add a ment  | 

2 Answers 2

Reset to default 8

So it looks like the issue is that the name and data need to change at the same time, so that the template isn't binding to a viewmodel that isn't there yet. There are a few ways you could solve this. One would be loading the templates and keeping them, but you could continue reloading them like this:

Template Binding:

<div data-bind="template: {name: currentTemplate().name(), 
                data: currentTemplate().data() }"></div>

ViewModel:

function TemplateViewModel(name, data) {
        this.name = ko.observable(name);
        this.data = ko.observable(data);
    };

    function IndexViewModel() {

        var vm = this;

        this.currentTemplate = ko.observable();

        this.loadFirstPage = function() {           
            vm.currentTemplate(new TemplateViewModel("firstvm", new FirstViewModel()));
        };

        this.loadSecondPage = function() {
            vm.currentTemplate(new TemplateViewModel("secondvm", new SecondViewModel()));
        };

        this.loadFirstPage();
    };
    ko.applyBindings(new IndexViewModel());

I tested this, it works. You may want to tweak it a bit more, but you get the idea.

This is already answered, however i wanted to share my solution used with standard knockout templates. I created custom binding, and wrapped template rendering call into setTimeout to put rendering to end of queue and it works fine. Here is the code:

ko.bindingHandlers.widget = {
    'init': function(element, valueAccessor) {
        return { 'controlsDescendantBindings': true };
    },
    'update': function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
        var templateName = valueAccessor().widgetTemplate();
        var dataValue = valueAccessor()
        var innerBindingContext = bindingContext['createChildContext'](dataValue);

        // This puts rendering of template to end of queue. 
        // This is to avoid binding errors while new template is assigned to old widget data
        setTimeout(function(){
            ko.renderTemplate(templateName, innerBindingContext, {}, element);
        }, 0);

    }
}

NOTE: Since knockout 2.3 name of template binding also accept observable

发布评论

评论列表(0)

  1. 暂无评论