Виджеты Durandal, динамические шаблонные части

Я создаю виджет мастера с Durandal, и я хотел бы использовать его так:

        <div data-bind="wizard: options">
            <!-- Step 1 -->
            <span data-part="step-header-1">
                Step 1
            </span>
            <div data-part="step-content-1">
                step content here
            </div>

            <!-- Step 2 -->
            <span data-part="step-header-2">
                Step 2
            </span>
            <div data-part="step-content-2">
                step content here
            </div>
        </div>

Это фактический виджет (урезанный для краткости):

<div class="wizard-container">

    <ul class="steps" data-bind="foreach: steps">
        <li>
            <span data-bind="html: heading"></span>
        </li>
    </ul>

    <!-- ko foreach: steps -->
    <div class="wizard-step" data-bind="css: { active: isActive }">
        <div data-bind="html: content">

        </div>
    </div>
    <!-- /ko -->

</div>

Я как бы заставил его работать, используя jQuery, чтобы захватить части данных, назначить внутренний HTML части данных свойству моей пошаговой модели, а затем использовать привязку html для привязки содержимого к каждому шагу. Это работает на стороне DOM, но это означает, что содержимое моего шага не будет привязано к данным. Я почти уверен, что это потому, что я использую привязку html, которая не привязывает содержимое.

Есть ли способ сделать это с виджетами Durandal, не разделяя каждый шаг на новое представление?


person Jeff    schedule 18.09.2013    source источник
comment
Вы хотите, чтобы все они отображались одновременно или менялись в зависимости от того, на каком этапе вы находитесь?   -  person PW Kad    schedule 18.09.2013
comment
@PWKad все это уже работает, как я уже сказал, я сократил код для краткости - единственная проблема заключается в том, что любая привязка данных в моем пошаговом содержимом не будет привязана, поскольку весь контент загружается с использованием HTML Knockout. -binding, который не применяет привязки к HTML, отображается с использованием этой привязки.   -  person Jeff    schedule 18.09.2013
comment
Но дело в том, все ли шаги отображаются одновременно (т. е. вы используете отдельные объекты для просмотра каждого внутри виджета) или виджет представляет каждый шаг на этом пути? Я спрашиваю, почему это зависит от того, как вы хотите отображать свой контент (используете ли вы синглтон или что?)   -  person PW Kad    schedule 18.09.2013
comment
@PWKad Я не уверен, что понимаю ваш вопрос, но я все равно постараюсь ответить: шаги определяются разметкой части данных внутри контейнера виджета. Сам виджет должен обеспечивать только взаимодействие с пользовательским интерфейсом мастера, что бы ни происходило внутри реальных шагов, это не его дело. Виджет предоставляет только пользовательский интерфейс, а также механизм навигации (я также использую Knockout, чтобы облегчить себе жизнь). Иногда я хотел бы добавить некоторые хуки проверки шага, но это позже. :)   -  person Jeff    schedule 18.09.2013
comment
@PWKad Таким образом, каждое использование мастера будет содержать начальный набор шагов (указанных как часть данных, как показано в вопросе), но было бы здорово также иметь возможность динамически добавлять шаги.   -  person Jeff    schedule 18.09.2013
comment
Используя привязку foreach:, я предполагаю, что все шаги показаны независимо от логики. Если бы я был на вашем месте и хотел связать отдельные шаги мастера с разными наборами элементов управления, я бы не делал этого внутри виджета, я бы использовал виджет для отображения своего шага и вызывал его из представления. То, что вы в основном объясняете, заставило бы меня подумать, что либо вам нужно создавать одноэлементные представления для каждого шага, а затем визуализировать, либо создавать один виджет, чтобы заботиться о каждом шаге на этом пути, и загружать разные данные, но это должно вызываться из вид мастера какой-то   -  person PW Kad    schedule 18.09.2013
comment
При инициализации виджета я беру все части данных с помощью селектора: [data-part^='step-content'], это дает мне все части содержимого шага, определенные пользователем виджета. Затем я перебираю их, создавая объект Step для каждого шага, и назначаю step.content = $thisStepContent.html() — как только я настроил все шаги, Durandal визуализирует представление, используя привязку foreach для создания заголовков и содержимого моих шагов. У меня есть /widgets/wizard/view.html и viewmodel.js, которые заботятся о рендеринге моего мастера — сами шаги определяются при каждом использовании виджета мастера.   -  person Jeff    schedule 18.09.2013
comment
@PWKad Итак, скажем, у меня есть представление «Настройка учетной записи», оно будет использовать мой виджет мастера, определять его шаги — скажем, 1: Основная информация, 2: Какой-то другой шаг, 3: Готово — тогда мне не придется беспокоиться о написании моего всю разметку мастера для каждого мастера, который я хочу создать, я просто определяю шаги внутри виджета. Если это не имеет смысла, не могли бы вы создать быструю скрипку, показывающую предлагаемое вами решение?   -  person Jeff    schedule 18.09.2013
comment
Нет, теперь это имеет смысл, ваш виджет-мастер не является синглтоном, который проходит через один процесс, это виджет, который можно повторно использовать для нескольких типов виджетов. Теперь я читаю вас громко и ясно, дайте мне немного времени, чтобы опубликовать решение, которое вы можете использовать.   -  person PW Kad    schedule 18.09.2013
comment
@PWKad да, спасибо. :)   -  person Jeff    schedule 18.09.2013
comment
Извините, я был очень занят последние 2 часа, вы смотрели @ это - durandaljs.com/documentation /Creating-A-Widget — в частности, где говорится о переопределении HTML со связанными наблюдаемыми объектами? Виджет с переопределением шаблонной части... ‹div› ‹h1›Widgets Sample‹/h1› ‹div data-bind=expander:{items:projects}› ‹h2 data-part=header›Project: ‹span data- bind=текст: имя›‹/span›‹/h2› ‹/div› ‹/div›   -  person PW Kad    schedule 19.09.2013
comment
@PWKad да, именно с этого я и начал.   -  person Jeff    schedule 19.09.2013


Ответы (2)


Вот реализация, в которой используется традиционный подход Durandal master/detail в сочетании с виджетом Tab. Виджет вкладки реализует только функциональность табуляции, в то время как мастер контролирует то, что в него помещается, а деталь управляет своим поведением/макетом.

Владелец

Модель просмотра

define(['./tab', 'plugins/widget', 'knockout'], function (Tab, widget, ko) {

    return {
        tabs: ko.observableArray([
            new Tab('Durandal', 'A ...', true),
            new Tab('UnityDatabinding', 'A ...'),
            new Tab('Caliburn.Micro', 'C ...')
        ]),
        addNewTab: function() {
            this.tabs.push(new Tab('New Tab ', 'A test tab.'));
        }
    };
});

Вид

<div>
    <h1>Tabs sample</h1>
    <!-- ko widget : {kind: 'tabs', items : tabs} -->
    <!-- /ko -->

    <button class="btn" data-bind="click: addNewTab">Add</button>
</div>

Деталь

Модель просмотра

define(['durandal/events', 'knockout'], function(events, ko) {
    return function(name, content, isActive) {
        this.isActive = ko.observable(isActive || false);
        this.name = name;
        this.content = content;
    };
});

Посмотреть

<div>
    <div data-bind="html: description"></div>
</div>

Виджет вкладки

Модель просмотра

define(['durandal/composition', 'jquery'], function(composition, $) {

    var ctor = function() { };

    ctor.prototype.activate = function(settings) {
        this.settings = settings;
    };

    ctor.prototype.detached = function() {
        console.log('bootstrap/widget/viewmodel: detached', arguments, this);
    };

    ctor.prototype.toggle = function(model, event){
        this.deactivateAll();
        model.isActive(true);

    };

    ctor.prototype.deactivateAll = function(){

        $.each(this.settings.items(), function(idx, tab){
            tab.isActive(false);
        });
    };



    return ctor;
});

Вид

<div class="tabs">
    <ul class="nav nav-tabs" data-bind="foreach: { data: settings.items }">
        <li data-bind="css: {active: isActive}">
            <a data-bind="text: name, click: $parent.toggle.bind($parent)"></a>
        </li>
    </ul>

    <div class="tab-content" data-bind="foreach: { data: settings.items}">
        <div class="tab-pane"  data-bind="html: content, css: {active: isActive}"></div>
    </div>
</div>

Живая версия доступна по адресу: http://dfiddle.github.io/dFiddle-2.0/#extras/default. Не стесняйтесь раскошелиться.

person RainerAtSpirit    schedule 21.09.2013
comment
Кому ты рассказываешь. Раскошельтесь/попробуйте. Если это не работает, как вы ожидаете, вернитесь с новым вопросом. Кстати: если я правильно понимаю вопрос, я бы начал с попытки перезаписать вид виджета вкладки <div class="tab-pane" data-bind="html: content, css: {active: isActive}"></div> на <div class="tab-pane" data-bind="compose: $data, css: {active: isActive}"></div>, чтобы подробный вид контролировал то, что отображается. - person RainerAtSpirit; 23.09.2013
comment
если я использую композицию, я вынужден разделить каждый шаг на новый вид. Я не хочу этого. :) - person Jeff; 23.09.2013
comment
Ну вот... стили программирования разные. Если вы найдете время, создайте небольшую демонстрацию и отправьте мне запрос на включение. Я был бы рад показать более одного подхода. - person RainerAtSpirit; 23.09.2013
comment
Демонстрация будет в значительной степени тем, что я разместил. :) - person Jeff; 23.09.2013

Как я и подозревал, проблема с неприменением моих привязок была связана с тем, что я использовал привязку html для установки содержимого шага. Когда Knockout устанавливает HTML, он не применяет к нему привязки.

Я написал свой собственный обработчик привязки HTML, который оборачивает HTML и вставляет его как DOM-узел — Knockout с радостью применит к этому привязки.

(function(window, $, ko) {
    var setHtml = function (element, valueAccessor) {
        var $elem = $(element);
        var unwrapped = ko.utils.unwrapObservable(valueAccessor());
        var $content = $(unwrapped);
        $elem.children().remove().end().append($content);
    };
    ko.bindingHandlers.htmlAsDom = {
        init: setHtml,
        update: setHtml
    };
}(window, jQuery, ko));

Обратите внимание, что это работает только тогда, когда значение привязки упаковано как узел, например, внутри тега div. Если нет, он не будет отображать его.

person Jeff    schedule 19.09.2013
comment
Рассматривали ли вы вместо этого использование традиционного основного/детального дизайна? Эта концепция обсуждалась на странице stackoverflow.com/questions/18850223/, и я создал пример по адресу dfiddle.github.io/dFiddle-2.0/#master-detail/wizard2 - person RainerAtSpirit; 20.09.2013
comment
@RainerAtSpirit Нет, однако у меня это работает так, как я хочу, так что я счастлив. :) - person Jeff; 20.09.2013
comment
Зачем позволять системе работать на вас, если вы можете сделать это самостоятельно ;-). - person RainerAtSpirit; 20.09.2013
comment
@RainerAtSpirit Ха-ха, я вас слышу :) - позволит ли использование основной детали, как вы предложили, использовать мой виджет, как указано в моем вопросе? - person Jeff; 20.09.2013
comment
Хороший вопрос, который заслуживает более подробного ответа. Попробуйте и дайте мне знать. - person RainerAtSpirit; 21.09.2013