Это означает, что когда вы нажимаете на некой странице на кнопку или ссылку, и состояние (внешний вид) страницы меняется (неважно, был ли это postback или callback) — то у этой страницы должен измениться и адрес (т.е. текст в строке браузера):У каждого состояния страницы должен быть уникальный адрес
Если каждый раз адрес менять не получается (например, в случае карт), то нужно предусмотреть кнопку «Получить ссылку» или что-то в этом духе.
Естественно, что если другой пользователь, или этот же, но позже — переходит по ранее запомненному уникальному адресу, то состояние страницы должно восстановиться:
Надеюсь, задача понятна, а теперь давайте рассмотрим реализацию, и некоторые тонкости при реализации этого паттерна в SharePoint.
На самом деле, как это всегда и бывает, способов реализации довольно много. Например, вы можете написать всё с нуля, или подсмотреть код на каком-нибудь сайте в интернете, или использовать одну из множества библиотек для реализации этого паттерна.
Я стараюсь при возможности ничего не подключать дополнительного и писать как можно меньше кастомного кода (потому что его потом еще поддерживать), в общем стараюсь по максимуму использовать то, что уже есть в SharePoint.
В частности, в SharePoint «из коробки» уже подключена библиотека ASP.Net Ajax версии 3.5, и оказывается, в этой библиотеке есть встроенные средства для реализации якорной навигации, что нам вполне подходит. На MSDN есть статья Managing Browser History Using Client Script, которая объясняет, как работать с якорной навигацией с помощью ASP.Net Ajax.
Если вкратце, потребуется всего два метода:
- При изменении состояния страницы необходимо менять текущий адрес и добавлять соответствующий переход в историю браузера. Это делает метод Sys.Application.addHistoryPoint.
- При изначальном заходе на страницу, нам нужно проанализировать текущий адрес и восстановить сохраненное в анкоре состояние страницы. Для этого необходимо подписаться на событие Sys.Application.navigate.
function OnSelectionChange(value) { var today = new Date(); var oneYear = new Date(today.getTime() + 365 * 24 * 60 * 60 * 1000); var url = window.location.href; document.cookie = "lcid=" + value + ";path=/;expires=" + oneYear.toGMTString(); window.location.href = url; }
Здесь присваивание window.location.href = url; должно по идее вызывать перезагрузку страницы, но т.к. на конце url при использовании анкорной навигации идет символ решетка (“#”) и параметры, идентифицирующие текущее состояние страницы (например, “http://site/page.aspx#state=10”), то перезагрузки страницы не происходит, т.к. браузер думает, что мы хотим перейти на анкор внутри текущей страницы.
Довольно забавный баг, но неприятный. Вышеозначенная функция “OnSelectionChange” генерируется прямо в код страницы, непосредственно перед кодом риббона. Редиску, которая генерирует эту функцию, я вычислять не стал. Вместо этого, просто исправил в ней ошибку и теперь переопределяю её после начальной загрузки страницы.
В конечном итоге, класс, ответственный за реализацию Unique URLs в моем проекте, выглядит следующим образом:
/// <reference path="GridController.js" /> DW.TasksDashboard._UniqueURLs = function () { DW.TasksDashboard.DataLoadManager.add_dataLoaded(onDataLoaded); this.FixOnFlyLocalization = function () { window.OnSelectionChange = function (value) { var today = new Date(); var oneYear = new Date(today.getTime() + 365 * 24 * 60 * 60 * 1000); var url = window.location.href.replace(/#[^#]*$/, ''); document.cookie = 'lcid=' + value + ';path=/;expires=' + oneYear.toGMTString(); window.location.href = url; } } Sys.Application.add_navigate(function (sender, e) { var state = e.get_state(); // Валидируем значения, т.к. они получены из Query String и могут быть потенциально небезопасны var view = parseInt(state.view); var filter = parseInt(state.filter); var prefs = parseInt(state.prefs); if (isNaN(view) || isNaN(filter) || isNaN(prefs)) return; DW.TasksDashboard.ViewManager.RestorePreviouslySavedView(state); }); function onDataLoaded() { Sys.Application.addHistoryPoint({ view: DW.TasksDashboard.ViewManager.get_CurrentViewFlags(), filter: DW.TasksDashboard.ViewManager.get_CurrentFilterFlags(), prefs: DW.TasksDashboard.ViewManager.get_CurrentAdditionalFlags() }); } } DW.TasksDashboard.UniqueURLs = new DW.TasksDashboard._UniqueURLs(); _spBodyOnLoadFunctionNames.push("DW.TasksDashboard.UniqueURLs.FixOnFlyLocalization"); Он зависит от некоторых других классов моего проекта, но я надеюсь, он очень хорошо демонстрирует концепцию паттерна UniqueURLs, и заодно, этот пример должен вам помочь правильно изолировать логику UniqueURLs от всего остального решения. Мне кажется, здесь эта логика изолирована очень удачно.
В итоге, адресная строка моего дашбоарда выглядит примерно так:
, т.е. параметры в строке состояния полностью соответствуют положению переключателей в трех разных группах на контекстной ленте (Views, Filters и Preferences). И если мы начинаем переключать кнопки на ленте, эти переключения тут же отражаются в адресной строке.
Мне кажется, это очень классно и очень удобно. Решается как минимум одна бизнес-задача: например, если клиент вдруг захочет, чтобы для всех пользователей дашбоард открывался по умолчанию на фильтре “Задачи, назначенные на меня моим руководителем”, или на какой-то другой особой комбинации настроек/фильтров/представлений, то он сможет просто отредактировать ссылку в главном меню – и вуаля, проблема решена.
Подводя итог: очень рекомендую обратить внимание на Ajax-паттерны и использовать их, если они подходят вашему решению – часто эти паттерны требуют совершенно пустяковых затрат в реализации, но зато могут значительно повысить удобство ваших интерфейсов, особенно если вы реализуете не один, а хотя бы 2-3 паттерна. Список Ajax-паттернов можно найти на сайте AjaxPatterns.org.
А можно немного подробнее объяснить этот момент - "Немного покопавшись в отладчике, я выяснил, что проблема зарылась в функции OnSelectionChange". Имеется ввиду обычный браузерный Firebug (и аналоги) или что-то посерьезнее?
ОтветитьУдалитьНе, обычный браузерный, IE Developer Tools.
Удалить