Как вы используете собственные шаблоны нокаута с внешним шаблонизатором KO

#javascript #jquery #knockout.js #knockout-templating

#javascript #jquery #knockout.js #нокаут-создание шаблонов

Вопрос:

Я использую внешний шаблонизатор KO, чтобы разбить мое одностраничное веб-приложение на несколько файлов, но загружаемые мной шаблоны содержат только разметку KO.

Несмотря на тщательное изучение темы, я не могу понять, как надежно применять привязки KO к загружаемым шаблонам.

Ключевые моменты заключаются в том, что:

  • Элементы DOM, определенные шаблоном, изначально не существуют
  • Мои привязки шаблонов являются динамическими, поэтому я не думаю, что KO будет запрашивать шаблоны при применении привязок ко всему документу
  • Я не хочу, чтобы загрузка шаблона была синхронной
  • pb становится еще более сложным из-за того, что infuser, что может происходить некоторое кэширование шаблонов, из-за чего KO жалуется, что я применяю привязки дважды.

Это код, который у меня есть, у которого есть как минимум две проблемы:

  • Нет никакой гарантии, что шаблон завершил загрузку, поскольку я вызываю applyBindings
  • KO жалуется, что я пытаюсь повторно применить привязки при перемещении вперед и назад

Есть предложения по чистому и надежному методу применения привязок один и только один раз к внешним узлам шаблона, когда они добавляются в DOM?

  • В index.html:
     <div id="templateDiv" data-bind="template: { name: currentView() }"></div>
     
  • В main.js:
     function AdminViewModel() {
         var self = this;
         self.currentView = ko.observable('adminHome');
     }
    
    var viewModel = new AdminViewModel();
    
    var SammyApp = $.sammy('#admin_content', function() {
      //...
      this.get('#/editMembers', function(context) {
          viewModel.currentView('editMembers');
          ko.applyBindings(viewModel, $('.ko-template').get(0));
      });
    };
    
    ko.applyBindings(viewModel);
     

Ответ №1:

если я вас понял, вы можете сделать это

загрузите шаблон в один базовый файл в js, используя get и присвоите тегу script, как показано ниже

 var script   = document.createElement("script");
               script.id  = "YourTemplateName";
               script.type  = "text/html";
               script.text  = result.Value; //template data
               document.body.appendChild(script);
 

и в вашем индексном / базовом html-файле назначьте шаблон

 <div id="OtherTemplateDiv" data-bind="template: { name: 'YourTemplateName' }">
            </div>
 

Комментарии:

1. Что ж, я разобрался с этой частью. Проблема связана с внешним шаблоном, который загружается сразу после первоначального вызова applyBindings (скажем, после 15 миллионов навигации по приложению пользователь внезапно переходит к представлению, которое загружает новый шаблон).

2. попробуйте что-то вроде этого ko.applyBindings(objectLateLoaded, $(«#DivIdInWhichTemplateAdded»)[0]);

Ответ №2:

Это не решение о том, как использовать шаблоны, но это то, как я разбиваю файлы с помощью привязки with .

Предположим, у вас есть страница, подобная этой:

 <div data-bind="with: block1">
    <input data-bind="value: yourFirstInput" />
    <!-- more markeup -->
</div>
<div data-bind="with: block2">
    <select data-bind="options: dropDownlist2options"></select>
    <!-- more markeup -->
</div>
 

Вы можете поместить block1 в файл (который я использую ascx ), block2 в другой файл.
Затем в вашей viewmodel у вас есть что-то вроде:

 var viewmodel = function () {
    var self = this;
    this.block1 = ko.observable();
    this.block2 = ko.observable();
}
var vm = new viewmodel();
ko.applyBindings(vm);
 

Таким образом, вы применяете привязки ко всей странице. Он with binding обработает проверку на наличие объектов null / undefined и отобразит блоки только при создании экземпляров объектов.

Затем вы можете сделать что-то подобное, когда захотите отобразить block1 :

 vm.block1({ yourFirstInput: ko.observable('aa')});
 

На самом деле я делаю это с помощью плагина mapping, это всего лишь пример.

Живая демонстрация (обратите внимание, как блок1 появляется после истечения времени установки)

Комментарии:

1. Хм, проблема в том, что у меня может быть более 50 внешних шаблонов, поэтому я не хочу загружать их все перед вызовом applyBindings() . Ваша скрипка работает, потому что все узлы DOM (основной документ и все шаблоны) создаются до вызова applyBindings, не так ли?

2. На самом деле важно определить блоки перед вызовом applyBindings для связанных элементов DOM. Вы можете загрузить разметку после, но вам придется вызывать applyBindings для нее (ko. Applybindings(vm, DOMElement))

Ответ №3:

Я создал шаблонизатор для KO

https://github.com/AndersMalmgren/Knockout .Bootstrap.TemplateStore /wiki

Для этого требуется веб-сервер с поддержкой Owin, после настройки он понимает, что имя ViewModel FooViewModel должно быть подключено к вызываемому представлению FooView

Установите с помощью nuget (для ASP.NET )

 Install-Package Knockout.Bootstrap.TemplateStore.SystemWeb
 

Он также разработан, чтобы его было легко использовать в SPA

Демонстрация https://github.com/AndersMalmgren/Knockout .Bootstrap.Demo

Ответ №4:

Я придумал кое-что, что работает с использованием обратного вызова afterRender… хотя, ИМХО, это немного запутанно, улучшения приветствуются.

По какой-то причине обратный вызов afterRender вызывается дважды, во второй раз с пустым объектом, отсюда и тест на hasOwnProperty(‘nodeType’).

isBound() проверяет, были ли привязки уже применены к элементу — попытка добавить пользовательский маркерный CSS-класс (‘ko-applied’) к элементу после применения привязок не сработала надежно.

Не уверен, что копирование массива koElements действительно необходимо, но без этого у меня в цикле оказались неопределенные элементы [i], поэтому загрузчик шаблонов может асинхронно обновлять массив во время работы afterRender.

 <div id="templateDiv" data-bind="template: { name: currentView(), afterRender: applyTemplateBindings }"></div>

self.applyTemplateBindings = function(koElements) {
            var elements = koElements.slice();
            for (var i = 0, len = elements.length; i < len; i  ) {
                var element = elements[i];
                if (element.hasOwnProperty('nodeType') amp;amp; ! $(element).hasClass('infuser-loading') amp;amp;
                    ! isBound(element)) {
                    ko.applyBindings(self, element);
                }
            }
        };

        var isBound = function(node) {
            return !!ko.dataFor(node);
        };