2013-09-16 3 views
3

Я работаю над SPA, используя Durandal, и создал виджет для отображения конкретного компонента страницы. После Durandal Documentation виджет находится в app/widgets/my-widget и состоит из viewmodel.js и view.html.Durandal Widget с несколькими видами

Теперь я хочу добавить другое представление к тому же самому виджету - по сути, иметь «основное» представление и «расширенный» вид с флагом в ViewModel, для которого он будет использоваться.

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

Я бы подумал, что один ответ должен сделать какой-то View Composition, где имя вида, которое должно быть составлено, возвращается из ViewModel на основе флага, но я не совсем уверен, как это сделать.

У кого-нибудь есть способ сделать это? Есть ли другой способ, которым я должен приближаться к этой проблеме?


UPDATE Вот пример того, что я хочу сделать. Существует виджет workitem, который используется для отображения «Рабочие элементы». В ViewModel рабочий элемент имеет свойство status, которое может принимать значения ready, in-progress, complete, или invalid. В зависимости от статуса рабочего элемента информация, которая должна отображаться о нем (и, следовательно, его вид), совершенно другая. То, что я хочу сделать, это создать что-то вроде этого:

-widgets 
| 
|-workitem 
| |-viewmodel.js 
| |-view-ready.html 
| |-view-in-progress.html 
| |-view-complete.html 
| |-view-invalid.html 

..., а затем автоматически выбрать один из этих представлений, основанных на собственности в ViewModel.

+0

Почему виджет, а не просто нормальный вид? Кажется, есть методы для этого с представлениями, но я не уверен, что они применимы к виджетам. – CodingGorilla

+0

@CodingGorilla Это виджет по двум причинам: 1. Он имеет независимое от самой страницы состояние и поэтому имеет свой собственный ViewModel. 2. На странице может быть более одного, каждый * со своим независимым состоянием *. –

ответ

2

Виджеты не могут быть легко переключены, как обычные, с использованием area, поскольку они всегда имеют тип partial.

Для того, чтобы изменить их, вы, скорее всего, придется переписать widget.convertKindToModulePath и widget.convertKindToViewPath

Вот пример из https://github.com/BlueSpire/Durandal/issues/217

var oldConvert = widget.convertKindToModulePath; 
widget.convertKindToModulePath: function(kind) { 
    if (typeof(kind) == 'function') { 
     return widget.mapKindToModuleId(kind()); 
    } 
    return oldConvert(kind); 
} 

Update Widget может быть построена с использованием, например, наблюдаемых или вычисленных, например. на ваш взгляд:

<!-- ko widget: getWidgetSettings() --> 
<!-- /ko --> 

getWidgetSettings может быть ko.computed (зависит от статуса), что вместо {kind: 'workitem'} возвращает что-то вроде {kind: { id : 'workitem', status: 'statusId'}}.

Теперь вы должны настроить widget.convertKindToModulePath и widget.convertKindToViewPath соответственно, как OOTB Durandal ожидает вид быть типа string, но теперь что это object.

Что-то по следующему должно вам начать работу:

var oldconvertKindToModulePath = widget.convertKindToModulePath; 
widget.convertKindToModulePath = function(kind) { 
    if (typeof(kind) == 'object') { 
     return 'widgets/' + kind.id + '/viewmodel'; 
    } 
    return oldconvertKindToModulePath(kind); 
}; 

var oldconvertKindToViewPath = widget.convertKindToViewPath; 
widget.convertKindToViewPath = function(kind) { 
    if (typeof(kind) == 'object') { 
     return 'widgets/' + kind.id + '/' + statusViewMap[statusId] ; 
    } 
    return oldconvertKindToViewPath(kind); 
}; 

В качестве альтернативы реализации этого в качестве виджета вы можете рассмотреть вопрос о внедрении в качестве стандартного модуля и используя метод getView. Вот пример

http://dfiddle.github.io/dFiddle-2.0/#view-composition/getView

define(['knockout'], function(ko) { 

    var roles = ['default', 'role1', 'role2']; 
    var role = ko.observable('default'); 

    var getView = ko.computed(function(){ 
     var roleViewMap = { 
      'default': 'viewComposition/getView/index.html', 
      role1: 'viewComposition/getView/role1.html', 
      role2: 'viewComposition/getView/role2.html' 
     }; 

     this.role = (role() || 'default'); 

     return roleViewMap[this.role]; 
    }); 


    return { 
    showCodeUrl: true, 
    roles: roles, 
    role: role, 
    getView: getView, 
    propertyOne: 'This is a databound property from the root context.', 
    propertyTwo: 'This property demonstrates that binding contexts flow through composed views.' 
    }; 


}); 

Пример использует синглтон, который не может выполнить требование наличия нескольких независимых WorkItems, так что вам придется переписать его с помощью шаблона конструктора.

Не стесняйтесь развиваться, и я беру запросы на тягу :).

+0

Я добавил пример в вопрос. Эти функции будут делать то, что я хочу? Например, если я использовал эти функции для определения представления, а затем изменилось свойство (наблюдаемое) 'status', переключилось бы это представление? –

+0

Обновленный ответ. – RainerAtSpirit

2

Как вы уже сказали, вы также можете сделать это, используя композицию и вернув соответствующее представление. Может быть что-то вроде следующего (с использованием view location strategy):

localViewStrategy - что вы можете придумать лучшее название для этого :)

define(['durandal/system', 'durandal/viewLocator'], function (system, viewLocator) { 
    return function(settings) { 
      var moduleId = system.getModuleId(settings.model); 
      var viewName = settings.viewName; 
      var path = moduleId.substring(0, moduleId.lastIndexOf('/') + 1) + viewName + '.html'; 

     return viewLocator.locateView(path, settings.area , settings.parent); 
    }; 
}); 

Widget View Model

define(function(){ 
    var ctor = function() { 
     //assuming this is your default view? 
     this.view = ko.observable('view-ready'); 
    }; 

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

     if(settings.view !== null){ 
      this.view(settings.view); 
     } 
    } 
    return ctor; 
}); 

view.html

<span data-bind="compose: { model: $data, strategy: 'path/to/strategy/localViewStrategy', 'viewName' : view, activate: false, preserveContext: true, cacheViews : false }"> 

</span> 

Не уверен, что это лучший способ сделать это!

3

Подход, который мы применяем к виджетам с несколькими видами, состоит в том, чтобы просто использовать привязку if или visible и привязать ее к наблюдаемой на модели, содержащей тип вида (например, «компактный», «расширенный», «сгруппированный» ', и т.д.).

Для нашего Datepicker виджета, на наш взгляд высокого уровня это (показывает только два из представлений):

</div> 
    <div data-bind="visible: viewMode() === 'months'"> 
     <div data-bind="compose: {model: 'widgets/datepicker/datepickerMonths', view: 'widgets/datepicker/datepickerMonths', activate: true, activationData: messageChannel }"> </div> 
    </div> 
    <div data-bind="visible: viewMode() === 'years'"> 
     <div data-bind="compose: {model: 'widgets/datepicker/datepickerYears', view: 'widgets/datepicker/datepickerYears', activate: true, activationData: messageChannel }"> </div> 
    </div> 
</div> 

viewMode наблюдаемыми позволяет переключаться между видами.

Мы используем visible связывания в этом случае по двум причинам:

  1. Производительность: Макеты оставаться загруженным в DOM, просто скрыты;
  2. Плагин clickoutside jQuery путается привязкой if (привязка if удаляет DOM из-под указателя мыши пользователя, так сказать, и ведет плагин clickoutside, чтобы полагать, что пользователь нажал на него).

С помощью подхода, приведенного выше, единственный файл view.html вашего виджета может ссылаться на несколько динамически настраиваемых представлений.

Я предоставил ссылку на мою общедоступную папку SkyDrive (теперь OneDrive) нашего дампикера, написанную целиком, чтобы использовать состав и системы виджетов Дурандала, а также KnockoutJS: video of datepicker as I switch from one view to another. Переключение представлений происходит с использованием техники, описанной выше.

Характеристики DatePicker:

  • DatePicker является мульти-виджет
  • он составляет поле ввода и наш поповер виджет (другими словами, виджет внутри виджета)
  • высокоуровневое представление динамически объединяет три разных вида, переключаясь между ними при взаимодействии пользователя.
  • он полагается на postal.js в качестве клиентской шины сообщений для маршрутизации выборов между представлениями, для координации с родительским представлением и для обновления управление вводом (Вы можете использовать Дюрандаль встроенный в паб/к югу, если вам нравится)
  • использует jwerty.js для управления клавиатурой
Смежные вопросы