PagerJS как выполнить "двустороннюю привязку" с параметрами URL?

PagerJS может выбрать параметры URL и привязать их к модели. Например, в этом примере с веб-сайта PagerJS (см. Ссылку), когда вы нажмете на ссылку, она перейдет к #/user?first=Philip&last=Fry, а появится подстраница с привязкой к данным, отображающая "Philip Fry":

<a class="btn" data-bind="page-href: {path: 'user', params: {first: 'Philip', last: 'Fry'}}">Send parameter to page</a>

<div data-bind="page: {id: 'user', params: ['first','last']}" class="well-small">
    <div>
        <span>First name:</span>
        <span data-bind="text: first"></span>
    </div>
    <div>
        <span>Last name:</span>
        <span data-bind="text: last"></span>
    </div>
</div>

Это односторонняя привязка: если наблюдаемые изменения, из-за действий пользователя на странице, URL-адрес не будет обновляться.

Каков рекомендуемый способ сохранения параметров URL в синхронизации с наблюдаемыми при использовании PagerJS?

Я хотел бы сохранить пользовательские динамически созданные критерии поиска, созданные путем выбора группы элементов управления пользовательского интерфейса, в параметрах URL, чтобы он мог поделиться URL-адресом с другими или закладок, без перезагрузки страницы, конечно.

Ответ 1

Отказ от ответственности: я ничего не знаю о pager.js, но я надеюсь, что мой общий опыт нокаута все же может помочь.

Если посмотреть на пример, привязка page, похоже, создает наблюдаемые значения, используя начальные значения из URL-адреса. Моим первым инстинктом было бы расширить эту привязку и убедиться, что подписка на каждое из этих значений обновляет URL-адрес.

Назовите это связывание twoway-page:

ko.bindingHandlers["twoway-page"] = {
  init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
    // Call pager.js' page binding
    ko.bindingHandlers.page.init(element, valueAccessor, allBindings, viewModel, bindingContext);

    // ...
  }
}

И назовите это на примере привязки:

<div data-bind="twoway-page: {
                  id: 'start', 
                  params: ['first','last']
                 }">

После вызова page.init привязка страницы расширила область просмотра, добавив наблюдаемые значения, определенные в массиве params, к объекту viewModel. Это означает, что мы можем подписаться на изменения этих наблюдаемых.

Следующая задача - вычислить правильный хеш. Я посмотрел, как привязка page-href вычисляет свой атрибут href. Оказывается, он использует pager.page.path() для объекта с атрибутами path и params. Например:.

var hash = pager.page.path({
  path: "user",
  params: {
    "first": "John",
    "last": "Doe"
  }
});

Я попытался построить подобный объект в вычисленном наблюдаемом.

// ... 
var options = valueAccessor();
var pathObj = ko.computed(function() {

    var result = {
        path: options.id,
        params: {}
    };

    options.params.forEach(function(param) {
        result.params[param] = viewModel[param]();
    });

    return result;
}).extend({ rateLimit: { timeout: 200, method: "notifyWhenChangesStop" } });

Я не смог найти "чистый" способ обновления хэша с помощью метода pager.js, но я заметил, что внутри pagerjs использует location.hash = "newhash" для установки значения (хотя, похоже, также существует история /html 5 альтернатива...). Во всяком случае, мы можем подписаться на наш наблюдаемый, чтобы обновить хэш:

// ...
pathObj.subscribe(function(newValue) {
    location.hash = pager.page.path(newValue);
});

Теперь вместо привязок text из примера мы будем использовать привязки textInput, чтобы мы могли обновить значения:

<div>
  <span>First name:</span>
  <input type="text" data-bind="textInput: first">
</div>

Итак, чтобы обернуть: лучше всего было бы

  • Расширение существующей привязки pager.js
  • Создайте подписки на все наблюдаемые, которые необходимо обновить в URL
  • Автоматическое обновление хэша при изменении значений; используйте расширение rateLimit, чтобы предотвратить перегрузку обновлений.

Делать вещи с хэшем местоположения немного сложно показать в скрипке, поэтому я записал gif моего доказательства концепции:

живое обновление хэша с использованием пользовательской привязки pagerjs

Полный код обязательного связывания:

ko.bindingHandlers["twoway-page"] = {
    init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
        ko.bindingHandlers.page.init(element, valueAccessor, allBindings, viewModel, bindingContext)

        var options = valueAccessor();

        var pathObj = ko.computed(function() {

            var result = {
                path: options.id,
                params: {}
            };

            options.params.forEach(function(param) {
                result.params[param] = viewModel[param]();
            });

            return result;
        }).extend({ rateLimit: { timeout: 200, method: "notifyWhenChangesStop" } });

        pathObj.subscribe(function(newValue) {
            location.hash = pager.page.path(newValue);
        })

        return { controlsDescendantBindings: true }
    }
};

Ответ 2

Я не могу добавить комментарий, поэтому мне нужно добавить новый ответ. Очень приятный ответ от user3297291, это очень помогло мне. Однако некоторые проблемы возникают в реальном мире, например:

  • глубокая навигация: страницы, подстраницы, подзаголовки и т.д. options.id недостаточно, нам нужен полный путь page/subpage/subsubpage (без #!/)

  • при совместном использовании некоторых параметров URL между страницами, в любое время смены параметров, каждая страница вызывает навигацию, последняя будет отображаться. Я разделяю одинаковые параметры ID между некоторыми страницами (productID и т.д.).

Для проблемы 1 вычислите полный путь:

...var options = valueAccessor();
var parent = pager.getParentPage(bindingContext);
var fullroute = parent.getFullRoute()();
if(fullroute.length == 0) 
    var path = fullroute.join('/')+options.id;
else 
    var path = fullroute.join('/')+'/'+options.id;

result объект превращается в:

var result = {
    path: path, 
    params: {}
};

Для проблемы 2 мы должны сказать им, что они могут инициировать только навигацию, когда активная страница является ее собственной.

Внутри метода subscribe мы вычисляем путь activePage и сравниваем их:

pathObj.subscribe(function(newValue) {
    //calculate activePath
    var activeParent = pager.activePage$().parentPage.getFullRoute()();
    var activeId = pager.activePage$().getCurrentId();
    if(activeParent.length == 0)
        var activePath = activeParent .join('/')+activeId;
    else 
        var activePath = activeParent .join('/')+'/'+activeId;

    if( path == activePath ){
        location.hash = pager.page.path(newValue);
    }
})

Кроме того, будьте осторожны при выполнении циклов throw throw, это может быть массив или объект, если вы указали значения по умолчанию:

<div data-bind="page: {id: 'search', params: ['product','price']}" class="well">

необходимо:

options.params.forEach(function(param) {...

Vs

<div data-bind="page: {id: 'search', params: {'product': null,'price': 0}}" class="well">

требуется что-то вроде:

Object.keys(options.params).forEach(function(key,index) { 
    result.params[key] = viewModel[key](); 
}

Еще раз спасибо пользователю3297291 за ваш ответ, это действительно изменило ситуацию.