it-swarm.com.ru

Как заставить кнопку «Назад» работать с конечным автоматом пользовательского интерфейса AngularJS?

Я реализовал одностраничное приложение angularjs, используя i-router .

Первоначально я определял каждое состояние, используя отдельный URL, однако это было сделано для недружественных, GUID упакованных URL.

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

Определить вложенные состояния

angular
.module 'app', ['ui.router']
.config ($stateProvider) ->
    $stateProvider
    .state 'main', 
        templateUrl: 'main.html'
        controller: 'mainCtrl'
        params: ['locationId']

    .state 'folder', 
        templateUrl: 'folder.html'
        parent: 'main'
        controller: 'folderCtrl'
        resolve:
            folder:(apiService) -> apiService.get '#base/folder/#locationId'

Переход в определенное состояние

#The ui-sref attrib transitions to the 'folder' state

a(ui-sref="folder({locationId:'{{folder.Id}}'})")
    | {{ folder.Name }}

Эта система работает очень хорошо, и мне нравится ее чистый синтаксис. Однако, поскольку я не использую URL, кнопка "Назад" не работает.

Как сохранить аккуратный конечный автомат пользовательского интерфейса, но включить функциональность кнопки "Назад"?

84
biofractal

Заметка

Ответы, в которых предлагается использовать варианты $window.history.back(), все пропустили важную часть вопроса: как восстановить состояние приложения до правильного местоположения состояния при переходе истории (назад/вперед/обновить). С этим в мыслях; Пожалуйста, продолжайте читать.


Да, браузер можно перевести назад/вперед (история) и обновить его, запустив при этом чистый конечный автомат ui-router, но для этого потребуется немного времени. 

Вам нужно несколько компонентов:

  • Уникальные URL. Браузер включает кнопки «Назад» и «Вперед» только при изменении URL, поэтому вы должны генерировать уникальный URL для каждого посещенного состояния. Эти URL не обязательно должны содержать информацию о состоянии.

  • Служба сеансов. Каждый сгенерированный URL-адрес соотносится с определенным состоянием, поэтому вам нужен способ сохранить пары URL-состояний, чтобы вы могли получить информацию о состоянии после того, как ваше угловое приложение было перезапущено назад/вперед или обновлением щелчков.

  • Государственная история. Простой словарь состояний UI-роутера с уникальным URL. Если вы можете полагаться на HTML5, тогда вы можете использовать HTML5 History API , но если, как и я, вы не можете, вы можете реализовать его самостоятельно в несколько строк кода (см. Ниже).

  • Служба определения местоположения. Наконец, вам необходимо иметь возможность управлять как изменениями состояния ui-маршрутизатора, вызванными внутренним кодом вашего кода, так и обычными изменениями URL-адреса браузера, обычно инициируемыми пользователем, нажимающим кнопки браузера или вводящим данные на панели браузера. Все это может быть немного сложнее, потому что легко запутаться в том, что вызвало что.

Вот моя реализация каждого из этих требований. Я собрал все в три службы:

Служба сеансов

class SessionService

    setStorage:(key, value) ->
        json =  if value is undefined then null else JSON.stringify value
        sessionStorage.setItem key, json

    getStorage:(key)->
        JSON.parse sessionStorage.getItem key

    clear: ->
        @setStorage(key, null) for key of sessionStorage

    stateHistory:(value=null) ->
        @accessor 'stateHistory', value

    # other properties goes here

    accessor:(name, value)->
        return @getStorage name unless value?
        @setStorage name, value

angular
.module 'app.Services'
.service 'sessionService', SessionService

Это оболочка для объекта javascript sessionStorage. Я сократил это для ясности здесь. Для полного объяснения этого смотрите: Как я могу справиться с обновлением страницы с помощью приложения AngularJS Single Page

Государственная историческая служба

class StateHistoryService
    @$inject:['sessionService']
    constructor:(@sessionService) ->

    set:(key, state)->
        history = @sessionService.stateHistory() ? {}
        history[key] = state
        @sessionService.stateHistory history

    get:(key)->
        @sessionService.stateHistory()?[key]

angular
.module 'app.Services'
.service 'stateHistoryService', StateHistoryService

StateHistoryService следит за хранением и извлечением исторических состояний, основанных на сгенерированных уникальных URL. Это действительно просто удобная оболочка для объекта в стиле словаря.

Государственная служба определения местоположения

class StateLocationService
    preventCall:[]
    @$inject:['$location','$state', 'stateHistoryService']
    constructor:(@location, @state, @stateHistoryService) ->

    locationChange: ->
        return if @preventCall.pop('locationChange')?
        entry = @stateHistoryService.get @location.url()
        return unless entry?
        @preventCall.Push 'stateChange'
        @state.go entry.name, entry.params, {location:false}

    stateChange: ->
        return if @preventCall.pop('stateChange')?
        entry = {name: @state.current.name, params: @state.params}
        #generate your site specific, unique url here
        url = "/#{@state.params.subscriptionUrl}/#{Math.guid().substr(0,8)}"
        @stateHistoryService.set url, entry
        @preventCall.Push 'locationChange'
        @location.url url

angular
.module 'app.Services'
.service 'stateLocationService', StateLocationService

StateLocationService обрабатывает два события:

  • locationChange. Это вызывается, когда местоположение браузера изменяется, как правило, когда нажимается кнопка назад/вперед/обновить, или когда приложение запускается впервые, или когда пользователь вводит URL-адрес. Если в StateHistoryService существует состояние для текущего location.url, то оно используется для восстановления состояния через $state.go в ui-router.

  • stateChange. Это вызывается, когда вы перемещаете состояние изнутри. Имя и параметры текущего состояния хранятся в переменной StateHistoryService сгенерированной ссылкой. Этот сгенерированный URL может быть чем угодно, может или не может идентифицировать состояние понятным для человека способом. В моем случае я использую параметр состояния плюс случайно сгенерированную последовательность цифр, полученных из guid (см. Раздел «Генератор guid»). Сгенерированный URL-адрес отображается на панели браузера и, что особенно важно, добавляется во внутренний стек истории браузера с помощью @location.url url. Он добавляет URL-адрес в стек истории браузера, который включает кнопки вперед/назад.

Большая проблема этого метода заключается в том, что вызов @location.url url в методе stateChange вызовет событие $locationChangeSuccess и вызовет метод locationChange. Аналогичным образом вызов @state.go из locationChange вызовет событие $stateChangeSuccess и, следовательно, метод stateChange. Это становится очень запутанным и портит историю браузера без конца.

Решение очень простое. Вы можете видеть, как массив preventCall используется в качестве стека (pop и Push). Каждый раз, когда вызывается один из методов, другой метод вызывается только один раз. Этот метод не мешает корректному запуску событий $ и сохраняет все как есть.

Теперь все, что нам нужно сделать, это вызвать методы HistoryService в соответствующее время в жизненном цикле перехода между состояниями. Это делается с помощью метода .run приложений AngularJS, например:

Angular app.run

angular
.module 'app', ['ui.router']
.run ($rootScope, stateLocationService) ->

    $rootScope.$on '$stateChangeSuccess', (event, toState, toParams) ->
        stateLocationService.stateChange()

    $rootScope.$on '$locationChangeSuccess', ->
        stateLocationService.locationChange()

Создать Guid

Math.guid = ->
    s4 = -> Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1)
    "#{s4()}#{s4()}-#{s4()}-#{s4()}-#{s4()}-#{s4()}#{s4()}#{s4()}"

При этом все кнопки «Вперед» и «Назад» и кнопка «Обновить» работают должным образом.

73
biofractal
app.run(['$window', '$rootScope', 
function ($window ,  $rootScope) {
  $rootScope.goBack = function(){
    $window.history.back();
  }
}]);

<a href="#" ng-click="goBack()">Back</a>
46
Guillaume Massé

Протестировав различные предложения, я обнаружил, что самый простой способ часто является лучшим.

Если вы используете угловой UI-роутер и вам нужна кнопка, чтобы вернуться назад, лучше всего это:

<button onclick="history.back()">Back</button>

или же

<a onclick="history.back()>Back</a>

// Предупреждение не устанавливайте href, иначе путь будет поврежден.

Объяснение: Предположим, стандартное приложение управления . Поиск объекта -> Просмотр объекта -> Изменить объект

Использование угловых решений Из этого состояния:

Поиск -> Вид -> Редактировать

Кому:

Поиск -> Просмотр

Ну, это то, что мы хотели, за исключением того, что теперь, если вы нажмете кнопку возврата браузера, вы снова окажетесь там:

Поиск -> Вид -> Редактировать

И это не логично

Однако, используя простое решение

<a onclick="history.back()"> Back </a>

от :

Поиск -> Вид -> Редактировать

после нажатия на кнопку:

Поиск -> Просмотр

после нажатия на кнопку возврата браузера:

Поиск

Последовательность соблюдается. :-)

22
bArraxas

Если вы ищете простейшую кнопку «назад», вы можете настроить директиву так:

    .directive('back', function factory($window) {
      return {
        restrict   : 'E',
        replace    : true,
        transclude : true,
        templateUrl: 'wherever your template is located',
        link: function (scope, element, attrs) {
          scope.navBack = function() {
            $window.history.back();
          };
        }
      };
    });

Имейте в виду, что это довольно неразумная кнопка «назад», потому что она использует историю браузера. Если вы включите его на свою целевую страницу, он отправит пользователя обратно на любой URL-адрес, с которого он пришел, до того, как вы перейдете на ваш.

7
dgrant069

Кнопка браузера назад/вперед
Я столкнулся с той же проблемой и решил ее, используя popstate event из объекта $ window и ui-router's $state object. Событие popstate отправляется окну каждый раз, когда изменяется активная запись истории.
События $stateChangeSuccess и $locationChangeSuccess не запускаются при нажатии кнопки браузера, даже если в адресной строке указано новое местоположение.
Итак, если вы перешли от состояний main к folder к main снова, когда вы нажмете back в браузере, вы должны вернуться к маршруту folder. Путь обновляется, но представление не отображается и по-прежнему отображает все, что у вас есть на main. попробуй это:

angular
.module 'app', ['ui.router']
.run($state, $window) {

     $window.onpopstate = function(event) {

        var stateName = $state.current.name,
            pathname = $window.location.pathname.split('/')[1],
            routeParams = {};  // i.e.- $state.params

        console.log($state.current.name, pathname); // 'main', 'folder'

        if ($state.current.name.indexOf(pathname) === -1) {
            // Optionally set option.notify to false if you don't want 
            // to retrigger another $stateChangeStart event
            $state.go(
              $state.current.name, 
              routeParams,
              {reload:true, notify: false}
            );
        }
    };
}

после этого кнопки назад/вперед должны работать плавно.

примечание: проверьте совместимость браузера для window.onpopstate (), чтобы быть уверенным

3
jfab fab

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

Директива использования

<a data-go-back-history>Previous State</a>

Угловая декларация директивы

.directive('goBackHistory', ['$window', function ($window) {
    return {
        restrict: 'A',
        link: function (scope, Elm, attrs) {
            Elm.on('click', function ($event) {
                $event.stopPropagation();
                if ($window.history.length) {
                    $window.history.back();
                } else {
                    $window.close();  
                }
            });
        }
    };
}])

Примечание: работает с использованием UI-роутера или нет.

3
hthetiot

Кнопка «Назад» также не работала для меня, но я понял, что проблема заключалась в том, что у меня было содержимое html на моей главной странице, в элементе ui-view.

то есть 

<div ui-view>
     <h1> Hey Kids! </h1>
     <!-- More content -->
</div>

Поэтому я переместил содержимое в новый файл .html и пометил его как шаблон в файле .js с маршрутами. 

то есть.

   .state("parent.mystuff", {
        url: "/mystuff",
        controller: 'myStuffCtrl',
        templateUrl: "myStuff.html"
    })
0
CodyBugstein