Понимание шаблона VS2013 MVC 5 SPA
Я начал играть с шаблоном приложения для одной страницы для MVC 5, который поставляется с Visual Studio 2013. Я больше знаком с Knockout.js
, и хотя я не был с Sammy.js
, я читал и это не кажется сложным.
То, что я, похоже, не могу оборачивать, - это то, как MVC 5 SPA Template объединяет эти технологии или что команда Visual Studio имела в виду для шаблона в качестве примера; шаблон предоставляет, помимо прочего, файл home.viewModel.js
, который должен служить отправной точкой, но я не могу понять, как я могу добавить больше просмотров с маршрутами Sammy.js
. Если бы они предоставили второй частичный вид и модель просмотра.
Мой вопрос
Итак, короткий рассказ, мои настоящие вопросы,
- Как мне показать частичное представление, связанное с маршрутом
#users
, таким образом, чтобы имитировать предоставленный home.viewModel.js
, чтобы я мог перемещаться назад от #home
до #users
? Каким будет определение маршрута Sammy.js
в users.viewModel.js
?
- Нужно ли мне делать что-нибудь особенное, чтобы включить кнопку возврата браузеров или она будет работать, как только я правильно определил свои маршруты?
- Это он или этот шаблон выглядит наполовину испеченным примером?
Следующий код предназначен только для дополнительной ссылки/контекста, но, вероятно, это не обязательно для ответа на вопрос.
Некоторый контекст
Предположим, что я создал частичный вид _Users.cshtml
, обслуживаемый UserController
, который является контроллером MVC, а не контроллером WebAPI
, и что я хочу отобразить этот частичный вид с помощью Sammy.js
, к концу которого я создал users.viewModel.js
. Теперь...
Представленный вид Index.cshtml
выглядит следующим образом:
@section SPAViews {
@Html.Partial("_Home")
}
@section Scripts{
@Scripts.Render("~/bundles/knockout")
@Scripts.Render("~/bundles/app")
}
Я предполагаю, что это означает страницу "оболочка" приложения, где остальные частичные представления будут загружены, чтобы заменить содержимое части _Home
partial.
Проблема в том, что в home.viewModel.js
маршрут Sammy
инициализируется без передачи в селектор элемента, который будет удерживать содержимое, как этот
Sammy(function () {
this.get('#home', function () {
// more code here
}
вместо, например
Sammy("#content", function () {
this.get('#home', function () {
// more code here
}
Предполагаю ли я разместить части _Users
вместе с _Home
с самого начала так, чтобы вид Index
выглядел следующим образом:
@section SPAViews {
@Html.Partial("_Home")
@Html.Partial("_Users")
}
@section Scripts{
@Scripts.Render("~/bundles/knockout")
@Scripts.Render("~/bundles/app")
}
Это, конечно, будет отображать оба представления одновременно, что не то, что мы хотим.
Мой users.viewModel.js
выглядит следующим образом:
function UsersViewModel(app, dataModel) {
var self = this;
Sammy(function () {
this.get('#users', function () {
// the following line only makes sense if _Users is not
// called from Index.cshtml
//this.load(app.dataModel.shoppingCart).swap();
});
});
return self;
}
app.addViewModel({
name: "Users",
bindingMemberName: "users",
factory: UsersViewModel
});
Я пробовал использовать метод Sammy.js
swap
, но поскольку мое представление _Users
является частичным, а Sammy
не настроено для действия на конкретном элементе, вся страница заменяется... и кнопка возврата браузера не работает.
Извините за огромное количество текста, и если это очень тривиальный вопрос. Меня беспокоит, что я не могу понять это самостоятельно, даже после прохождения документов.
Ответы
Ответ 1
Наткнувшись на это, мне удалось применить свой собственный "взлом", чтобы исправить это.
При сравнении "старого" шаблона с новым я заметил, что Sammy.js
больше встроен в шаблон. Хотя это и хорошо, оригинальная привязка к нокауту with
для отображения ваших просмотров нарушена.
Чтобы применить исправление, в первую очередь необходимо понять привязку нокаутов with
. В представлении home
по умолчанию есть
<!-- ko with: home-->
который должен обеспечивать видимость home view
только при наличии элемента home
. В этом случае полное имя будет app.home
Если мы проверим имя этого члена, мы увидим, что это вычисляемый член, определенный как (app.viewmodel.js):
// Add binding member to AppViewModel (for example, app.home);
self[options.bindingMemberName] = ko.computed(function () {
if (!dataModel.getAccessToken()) {
//omitted for clearity
if (fragment.access_token) {
//omitted for clearity
} else {
//omitted for clearity
}
}
return self.Views[options.name];
});
Как вы можете видеть, он всегда возвращает полный инициализированный вид из коллекции Views
.
Если мы сравним это со старым шаблоном, мы можем увидеть изменение здесь:
// Add binding member to AppViewModel (for example, app.home);
self[options.bindingMemberName] = ko.computed(function () {
if (self.view() !== viewItem) {
return null;
}
return new options.factory(self, dataModel);
});
Возвращает null, если текущее представление не является таргетированным viewItem
. Это имеет решающее значение для привязки нокаута with
.
Дальнейшая проверка обоих шаблонов показывает лучшую интеграцию с Sammy.js
. Важнейшая его часть заключается в viewmodels (home.viewmodel.js):
Sammy(function () {
this.get('#home', function () {
});
this.get('/', function () { this.app.runRoute('get', '#home') });
});
Так как Sammy.js
обрабатывает навигацию, ранее упомянутый viewItem
, инкапсулированный в app.view()
, не задан. Что, опять же имеет решающее значение для привязки нокаута.
Итак, мое предложенное исправление выглядит следующим образом:
app.viewmodel.js
// Add binding member to AppViewModel (for example, app.home);
self[options.bindingMemberName] = ko.computed(function () {
if (!dataModel.getAccessToken()) {
//omitted for clearity
if (fragment.access_token) {
//omitted for clearity
} else {
//omitted for clearity
}
}
///change start here
if (self.view() !== viewItem) {
return null;
}
return self.Views[options.name];
});
и в каждой пользовательской модели просмотра:
home.viewmodel.js
Sammy(function () {
this.get('#home', function () {
app.view(self); //this line is added
});
this.get('/', function () { this.app.runRoute('get', '#home') });
});
Отказ от ответственности:
Поскольку я только что получил это и работал, у меня не было времени, чтобы проанализировать любые нежелательные побочные эффекты. Кроме того, изменение базового шаблона по умолчанию не очень хорошо, поэтому лучше принимать решения.
Ответ 2
Это, конечно, будет отображать оба представления одновременно, что не то, что мы хотим.
На самом деле, во многих случаях это именно то, что вы хотите (или, скорее, вы хотите их присутствие и контролировать их видимость.) Помимо свойства видимости на viewmodel и некоторых вспомогательных методах JS (или класса), чтобы показать/скрывать ваши представления (через ссылки на viewmodel, обычно связанные с определенным URL-адресом.)
Pseudo _Home.cshtml:
<!-- ko with: $root.home -->
<div data-bind="visible: isVisible">
<!-- view markup/etc here -->
</div>
<!-- /ko -->
Псевдо: app.viewmanager.js
MyViewManager = function () {
this.registerView = function(route, selector, viewmodel) {/**/};
this.showView = function(selector, callback) {};
this.cancelView = function(callback) {/**/};
this.showModal = function(selector, callback) {/**/};
this.closeModal = function(selector, callback) {/**/};
}
Они могли бы интегрироваться с History API
для маршрутизации/глубокой привязки и нокаутом для отображения/скрытия элементов DOM (посредством привязки IsVisible). Вышеупомянутый "registerView" заменил бы addViewModel
на стандартный эшафот, конечно. Все это, ИМО, является мусором.
Я уже несколько лет разрабатываю ООР на вершине MVC. Шаблон MVC5 SPA представляет собой интересное проявление интереса, но он имеет проблемы. Правильная глубокая привязка, инициализация и просмотр представлений - более очевидные проблемы, но с небольшим количеством смазки локтя вы можете легко кодировать то, что вам нужно.
Я также считаю раздел SPAViews
бесполезным и предпочитаю использовать RenderBody для частичной доставки, что требует некоторой модификации _Layout.cshtml
. В конце концов, для достаточно большого SPA вы завершите доставку почти всех ваших основных просмотров в одну страницу/просмотр в любом случае (редко бывает, что части Ajax в SPA, даже большой). И единственное значение SPAViews
раздел - это размещение внутри _Layout, эффективно дублируя функцию RenderBody() (поскольку тело вашего SPA всегда будет представлять собой коллекцию невидимых представлений.)
Ответ 3
Да, это определенно сбивает с толку, и, похоже, не так много документов. Я подозреваю, что количество способов делать вещи настолько велико, что им пришлось оставить его наполовину испеченным. FWIW Я сделал простую навигацию по страницам, добавив следующее в app.viewmodel
navigator = function () {
self.view(viewItem); //THIS IS ADDED
window.location.hash = options.bindingMemberName;
};
и в index.cshtml у меня есть это:
@section SPAViews {
<!-- ko if: app.view() === app.Views.Login -->
@Html.Partial("_login")
<!-- /ko -->
<!-- ko if: app.view() === app.Views.MyDashboard -->
@Html.Partial("_myDashboard")
}
Я думаю, что они, вероятно, ожидают, что вы настроитесь на то, что общее состояние представления каким-то образом связано с моделью просмотра "app" (изменение вида, видимое видимое каким-то образом перегруппировывает страницу). Помимо простого подхода выше, не уверен, как лучше всего это сделать.