Прогрессивное улучшение с помощью KnockoutJS
Скажем, мы имеем следующие данные
var data = {
facets: [{
name : "some name",
values: [{
value: "some value"
}]
}]
};
Мы можем легко представить это как модель представления, связанную с шаблоном нокаута следующим образом:
<ul data-bind="foreach: facets">
<li>
<span data-bind="text: name"></span>
<ul data-bind="foreach: values">
<li data-bind="text: value"></li>
</ul>
</li>
</ul>
Вопрос в том, как мы можем добиться того же результата при использовании прогрессивного улучшения? То есть, сначала визуализируя шаблон на стороне сервера, а затем привязывая шаблон нокаута и модель представления к этому рендерингу.
Простой шаблон на стороне сервера будет выглядеть примерно так:
<ul>
<li>
<span>some name</span>
<ul>
<li>some value</li>
</ul>
</li>
</ul>
Я изучил несколько разных возможностей:
-
Один из них создает один шаблон нокаута и один шаблон на стороне сервера и динамически генерирует модель представления нокаута, анализируя DOM для шаблона на стороне сервера. Таким образом, только шаблон нокаута будет виден, когда JavaScript включен, и только шаблон на стороне сервера будет виден, если JavaScript отключен. Их можно было бы создать так, чтобы они выглядели одинаково.
-
Другим подходом является применение привязок для каждого элемента массива граней отдельно к существующему элементу DOM для этой грани. Однако это все еще только один уровень глубины и не работает для вложенных элементов.
Ни один из этих подходов не выглядит довольно чистым. Другим решением может быть создание пользовательской привязки, которая обрабатывает весь рендеринг и повторно использует существующие элементы, если это возможно.
Любые другие идеи?
Ответы
Ответ 1
Я рассмотрел несколько подходов здесь, включая создание анонимного шаблона из первого элемента, как описано здесь:
http://groups.google.com/group/knockoutjs/browse_thread/thread/3896a640583763d7
или создания отдельных привязок для каждого элемента массива посредством пользовательской привязки, например
ko.bindingHandlers.prerenderedArray = {
init: function(element, valueAccessor, allBindingsAccessor, viewModel) {
var binding = valueAccessor();
binding.firstTime = true;
$(element).children().each(function(index, node) {
var value = ko.utils.unwrapObservable(binding.value)[index];
ko.applyBindings(value, node);
});
return { 'controlsDescendantBindings': true };
},
update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var binding = valueAccessor();
if (binding.firstTime) {
binding.firstTime = false;
return;
}
$(element).children().remove();
ko.applyBindingsToNode(element, { template: { name: binding.template, foreach: binding.value }}, viewModel)
}
};
который применяет конкретную привязку к каждому элементу, а затем в первом обновлении заменяет содержимое обычным привязкой foreach. Этот подход по-прежнему означает, что вам все еще нужно иметь два шаблона. Оба они также включают в себя инициализацию состояния через JSON, предоставленный сервером.
Нынешний подход, который я решил использовать (по-прежнему подвержен изменениям), заключается в том, чтобы поместить все шаблоны Knockout в теги script, чтобы они никогда не отображались в браузерах NoJS. Шаблоны NoJS отображаются как содержимое div на стороне сервера. Как только будет вырисован шаблон нокаута, содержимое div будет заменено шаблоном нокаута в тегах script. Вы можете стилизовать их одинаковыми/похожими способами, чтобы сделать переход бесшовным, и если это невозможно, скройте шаблон noJS с помощью CSS.
В конце концов, я пришел к выводу, что Knockout.js и прогрессивное улучшение не очень хорошо работают вместе, нужно выбрать либо другое, то есть собрать некоторые части приложения, которые требуют прогрессивного улучшения, используя более традиционные методы такие прямые манипуляции с JQuery DOM.
Ответ 2
Просто добавьте различные атрибуты data-
в шаблонах на стороне сервера. Они не пострадают, если JavaScript отключен, поэтому их вообще не проблема.
Ответ 3
Я боюсь, что нет чистого способа сделать это. Лично я делаю страницу в бэкэнд, а затем передаю одни и те же контекстные данные в интерфейс (сериализуется как JSON) и настраиваю нокаут, используя его. Это означает, что существует некоторое дублирование. Возможно, переключение бэкэнд на node.js упростит ситуацию здесь.
Ответ 4
Здесь я беру его, этот базовый пример использует настраиваемую привязку для загрузки значений на стороне сервера в модель представления.
Прогрессивное расширение с бесконечным прокруткой KnockoutJS
![screenshot]()
Связывание с прогрессивным улучшением
ko.bindingHandlers.PE = {
init: function(element, valueAccessor, allBindings) {
var bindings = allBindings();
if (typeof bindings.text === 'function') {
bindings.text($(element).text());
}
}
};
Ответ 5
здесь, я предложил привязку к шаблону, которая использует сгенерированный HTML с сервера. Это использование я похоже на оригинальную привязку к шаблону, за исключением нескольких атрибутов данных, которые будут использоваться привязкой.
Ответ 6
Прогрессивное улучшение намного проще с нециклическими данными, такими как формы (как я писал здесь: http://www.tysoncadenhead.com/blog/using-knockout-for-progressive-enhancement). Лично я считаю, что переплетение нескольких элементов в DOM и их повторное рендеринг похоже на то, что это будет жесткая реализация, но трудно думать о чем-то лучше.