History.pushState aracılığıyla tarihin değişimleri hakkında nasıl bildirim alabilirim?


154

HTML5 history.pushStatetarayıcı geçmişini değiştirmeye başladığı için, web siteleri bunu URL'nin parça tanımlayıcısını değiştirmek yerine Ajax ile birlikte kullanmaya başlar.

Ne yazık ki, bu çağrılar artık tespit edilemez anlamına gelir onhashchange.

Sorum şu: Bir web sitesinin ne zaman kullanıldığını tespit etmenin güvenilir bir yolu var mı (hack?;)) history.pushState? Spesifikasyon, ortaya çıkan olaylar hakkında hiçbir şey ifade etmiyor (en azından hiçbir şey bulamadım).
Bir cephe oluşturmaya çalıştım ve window.historykendi JavaScript nesnesimle değiştirdim , ancak hiçbir etkisi olmadı.

Daha fazla açıklama: Bu değişiklikleri algılayıp buna göre hareket etmesi gereken bir Firefox eklentisi geliştiriyorum.
Birkaç gün önce bazı DOM etkinliklerini dinlemenin etkili olup olmayacağını soran benzer bir soru olduğunu biliyorum, ancak buna güvenmemeyi tercih ederim çünkü bu olaylar çok farklı nedenlerle oluşturulabilir.

Güncelleme:

İşte bir jsfiddle (Firefox 4 veya Chrome 8 kullanın) çağrıldığında onpopstatetetiklenmeyen pushState(veya yanlış bir şey mi yapıyorum? Geliştirmek için çekinmeyin!)

Güncelleme 2:

Başka bir (yan) sorunu window.locationkullanırken güncellenmez pushState(ama ben zaten burada SO düşünüyorum bence).

Yanıtlar:


194

5.5.9.1 Olay tanımları

Popstate bir oturum geçmiş girişine gezinirken olay bazı durumlarda ateşlenir.

Buna göre, kullandığınızda popstate'in işten çıkarılmasına gerek yok pushState. Ancak böyle bir olay pushstateişinize yarayacaktır. historyBir ana bilgisayar nesnesi olduğundan , buna dikkat etmelisiniz, ancak Firefox bu durumda iyi görünüyor. Bu kod gayet iyi çalışıyor:

(function(history){
    var pushState = history.pushState;
    history.pushState = function(state) {
        if (typeof history.onpushstate == "function") {
            history.onpushstate({state: state});
        }
        // ... whatever else you want to do
        // maybe call onhashchange e.handler
        return pushState.apply(history, arguments);
    };
})(window.history);

JSFiddle'ınız şöyle olur :

window.onpopstate = history.onpushstate = function(e) { ... }

window.history.replaceStateAynı şekilde maymun yaması yapabilirsiniz .

Not: elbette onpushstatesadece global nesneye ekleyebilir ve hatta daha fazla olayı işlemesini sağlayabilirsiniz.add/removeListener


Tüm historynesneyi değiştirdiğinizi söylediniz . Bu durumda bu gereksiz olabilir.
gblazex

@galambalazs: Evet, muhtemelen. Belki (bilmiyorum) window.historysalt okunur ama tarih nesnesinin özellikleri değil ... Bu çözüm için bir demet teşekkürler :)
Felix Kling

Spesifikasyona göre bir "sayfa dönüşümü" olayı var, henüz uygulanmadı gibi görünüyor.
Mohamed Mansour

2
@ user280109 - Bilseydim sana söylerdim. :) Bunu Opera ATM'sinde yapmanın bir yolu olmadığını düşünüyorum.
gblazex

1
@cprcrack Global kapsamı temiz tutmak içindir. Yerel pushStateyöntemi daha sonra kullanmak üzere kaydetmeniz gerekir . Bunun yerine küresel bir değişkenin Bir hayattan içine bütün kodunu encapsule seçti: en.wikipedia.org/wiki/Immediately-invoked_function_expression
gblazex

4

Bunu kullanıyordum:

var _wr = function(type) {
    var orig = history[type];
    return function() {
        var rv = orig.apply(this, arguments);
        var e = new Event(type);
        e.arguments = arguments;
        window.dispatchEvent(e);
        return rv;
    };
};
history.pushState = _wr('pushState'), history.replaceState = _wr('replaceState');

window.addEventListener('replaceState', function(e) {
    console.warn('THEY DID IT AGAIN!');
});

Galambalazların yaptığı ile neredeyse aynı .

Gerçi genellikle aşırıya kaçar. Ve tüm tarayıcılarda çalışmayabilir. (Yalnızca tarayıcımın sürümünü önemsiyorum.)

(Ve bir varya bırakır _wr, bu yüzden onu falan sarmak isteyebilirsiniz. Bunu umursamadım.)


4

Sonunda bunu yapmak için "doğru" yolunu buldu! Uzantınıza bir ayrıcalık eklenmesini ve arka plan sayfasını (yalnızca bir içerik komut dosyası değil) kullanmasını gerektirir, ancak çalışır.

İstediğiniz etkinlik browser.webNavigation.onHistoryStateUpdated, bir sayfa historyURL'yi değiştirmek için API kullandığında tetiklenir . Yalnızca erişim iznine sahip olduğunuz siteler için tetiklenir ve ayrıca gerektiğinde spam'i daha da azaltmak için bir URL filtresi kullanabilirsiniz. webNavigationİzni (ve tabii ki ilgili alan (lar) için ev sahibi iznini gerektirir ).

Etkinlik geri araması sekme kimliğini, "yönlendirilen" URL'yi ve diğer ayrıntıları alır. Etkinlik tetiklendiğinde o sayfadaki içerik komut dosyasında bir işlem yapmanız gerekiyorsa, ilgili komut dosyasını doğrudan arka plan sayfasından enjekte edin veya portyüklendiğinde içerik komut dosyasının arka plan sayfasını açmasını sağlayın, arka plan sayfasını kaydedin bu bağlantı noktasını sekme kimliğiyle dizinlenmiş bir koleksiyonda toplayın ve etkinlik tetiklendiğinde ilgili bağlantı noktası üzerinden (arka plan komut dosyasından içerik komut dosyasına) bir mesaj gönderin.


3

window.onpopstateEtkinliğe bağlanabilir misiniz ?

https://developer.mozilla.org/en/DOM%3awindow.onpopstate

Dokümanlardan:

Penceredeki popstate olayı için bir olay işleyici.

Etkin geçmiş girişi her değiştiğinde pencereye bir popstate olayı gönderilir. Etkinleştirilen geçmiş girdisi history.pushState () öğesine yapılan bir çağrı tarafından oluşturulmuşsa veya history.replaceState () öğesine yapılan bir çağrıdan etkilenmişse, popstate olayının state özelliği, geçmiş girdisinin durum nesnesinin bir kopyasını içerir.


6
Bunu zaten denedim ve olay yalnızca kullanıcılar geçmişte (veya herhangi bir history.go, .back) fonksiyonlar çağrıldığında tetiklenir . Ama değil pushState. İşte bunu test etmek benim girişimi, belki bir şeyler yanlış yapın: jsfiddle.net/fkling/vV9vd Sadece o şekilde ilgili görünüyor eğer tarih ile değiştirildi pushStategelen durum nesnesi olay işleyicisi geçirilir, ne zaman diğer yöntemler arandı.
Felix Kling

1
Ah. Bu durumda düşünebildiğim tek şey, geçmiş yığının uzunluğuna bakmak için zaman aşımı kaydetmek ve yığın boyutu değiştiyse bir olayı ateşlemektir.
stef

Tamam, bu ilginç bir fikir. Tek şey, zaman aşımı, kullanıcının herhangi bir (uzun) gecikme fark etmeyecek kadar sık ​​sık tetiklenmesi gerektiğidir (yeni URL için veri yüklemem ve göstermem gerekiyor). Her zaman zaman aşımlarından ve yoklamalardan kaçınmaya çalışıyorum, ancak şimdiye kadar bu tek çözüm gibi görünüyor. Yine de başka teklifleri bekleyeceğim. Ama şimdilik çok teşekkür ederim!
Felix Kling

Her tıklamada geçmiş çubuğunun uzunluğunu kontrol etmek bile yeterli olabilir.
Felix Kling

1
Evet - işe yarar. Ben bununla bir oyun yaşıyorum - bir sorun, yığın boyutu değişmez çünkü bir olay atmak olmaz replaceState çağrı.
stef

1

Bir Firefox eklentisi hakkında soru sorduğunuz için, işte çalışmam gereken kod. Kullanımı unsafeWindowise artık önerilmemektedir ve pushState modifiye edildikten sonra bir istemci komut dosyasından çağrıldığında dışarı hatalar:

Mülk geçmişine erişim izni reddedildi.

Bunun yerine, işlevin şöyle enjekte edilmesini sağlayan exportFunction adında bir API var window.history:

var pushState = history.pushState;

function pushStateHack (state) {
    if (typeof history.onpushstate == "function") {
        history.onpushstate({state: state});
    }

    return pushState.apply(history, arguments);
}

history.onpushstate = function(state) {
    // callback here
}

exportFunction(pushStateHack, unsafeWindow.history, {defineAs: 'pushState', allowCallbacks: true});

Son satırında, değiştirmek gerekir unsafeWindow.history,için window.history. Benim için güvenli olmayan bir pencere ile çalışmıyordu.
Lindsay-Needs-Sleep

0

galambalazs maymun yamaları cevapwindow.history.pushState ve window.history.replaceStateama bir nedenle benim için çalışmayı durdurdu. İşte bu kadar hoş olmayan bir alternatif çünkü yoklama kullanıyor:

(function() {
    var previousState = window.history.state;
    setInterval(function() {
        if (previousState !== window.history.state) {
            previousState = window.history.state;
            myCallback();
        }
    }, 100);
})();

yoklama için bir alternatif var mı?
SuperUberDuper

@ SuperUberDuper: diğer cevaplara bakınız.
Flimm

@Flimm Yoklama hoş değil, çirkin. Ama dediğin seçeneğin yoktu.
Yairopro

Bu, maymun yamadan çok daha iyidir, maymun yama diğer komut dosyası tarafından geri alınabilir ve bu konuda HİÇBİR bildirim alırsınız.
Ivan Castellanos

0

Bu konunun daha modern bir çözüme ihtiyacı olduğunu düşünüyorum.

Eminim nsIWebProgressListenergeri döndü o zaman kimse bahsetmedi şaşırdım.

Bir çerçeve yazısından (e10s uyumluluğu için):

let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebProgress);
webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_WINDOW | Ci.nsIWebProgress.NOTIFY_LOCATION);

Sonra dinleme onLoacationChange

onLocationChange: function onLocationChange(webProgress, request, locationURI, flags) {
       if (flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT

Görünüşe göre tüm pushState'leri yakalayacak. Ancak "pushState için AYRICA tetikler" uyarısı da vardır. Bu yüzden, sadece itmeli öğeler olduğundan emin olmak için burada biraz daha filtreleme yapmamız gerekiyor.

Dayanarak: https://github.com/jgraham/gecko/blob/55d8d9aa7311386ee2dabfccb481684c8920a527/toolkit/modules/addons/WebNavigation.jsm#L18

Ve: kaynak: //gre/modules/WebNavigationContent.js


Yanlış olmadıkça bu cevabın yorumla ilgisi yoktur. WebProgressListeners FF uzantıları için mi? Bu soru HTML5 geçmişi hakkında
Kloar

@Kloar lütfen bu yorumları
silelim

0

Peki, ben pushStateözelliğini değiştirmek için birçok örnek görüyorum historyama bunun iyi bir fikir olduğundan emin değilim, sadece push durumunu kontrol etmek değil, durumu değiştirmek için tarihe benzer bir API ile bir hizmet olayı oluşturmayı tercih ederim ve küresel tarih API'sine dayanmayan diğer birçok uygulamanın kapılarını açar. Lütfen aşağıdaki örneği kontrol edin:

function HistoryAPI(history) {
    EventEmitter.call(this);
    this.history = history;
}

HistoryAPI.prototype = utils.inherits(EventEmitter.prototype);

const prototype = {
    pushState: function(state, title, pathname){
        this.emit('pushstate', state, title, pathname);
        this.history.pushState(state, title, pathname);
    },

    replaceState: function(state, title, pathname){
        this.emit('replacestate', state, title, pathname);
        this.history.replaceState(state, title, pathname);
    }
};

Object.keys(prototype).forEach(key => {
    HistoryAPI.prototype = prototype[key];
});

EventEmitterTanıma ihtiyacınız varsa , yukarıdaki kod NodeJS olay yayıcısına dayanır: https://github.com/nodejs/node/blob/36732084db9d0ff59b6ce31e839450cd91a156be/lib/events.js . utils.inheritsuygulama burada bulunabilir: https://github.com/nodejs/node/blob/36732084db9d0ff59b6ce31e839450cd91a156be/lib/util.js#L970


Cevabımı istenen bilgilerle düzenledim, lütfen kontrol edin!
Victor Queiroz

@VictorQueiroz Fonksiyonları kapsüllemek için güzel ve temiz bir fikir. Ancak bazı üçüncü bölüm kitaplıkları pushState'i çağırırsa, HistoryApi'niz bilgilendirilmez.
Yairopro

0

Ben bu basit uygulama sadece bir olay gönderir ve history.pushState () döndüren eventedPush durumu adlı kendi işlevini oluşturmak yerine yerel tarih yönteminin üzerine yazmak istemem. Her iki şekilde de iyi çalışıyor, ancak yerel yöntemler gelecekteki geliştiricilerin beklediği gibi çalışmaya devam edeceği için bu uygulamayı biraz daha temiz buluyorum.

function eventedPushState(state, title, url) {
    var pushChangeEvent = new CustomEvent("onpushstate", {
        detail: {
            state,
            title,
            url
        }
    });
    document.dispatchEvent(pushChangeEvent);
    return history.pushState(state, title, url);
}

document.addEventListener(
    "onpushstate",
    function(event) {
        console.log(event.detail);
    },
    false
);

eventedPushState({}, "", "new-slug"); 

0

@Gblazex tarafından verilen çözüme dayanarak , aynı yaklaşımı izlemek, ancak ok işlevlerini kullanmak istiyorsanız, javascript mantığınızda aşağıdaki örneği izleyin:

private _currentPath:string;    
((history) => {
          //tracks "forward" navigation event
          var pushState = history.pushState;
          history.pushState =(state, key, path) => {
              this._notifyNewUrl(path);
              return pushState.apply(history,[state,key,path]); 
          };
        })(window.history);

//tracks "back" navigation event
window.addEventListener('popstate', (e)=> {
  this._onUrlChange();
});

Ardından, _notifyUrl(url)geçerli sayfa URL'si güncellendiğinde (sayfa hiç yüklenmemiş olsa bile) ihtiyacınız olabilecek işlemleri tetikleyen başka bir işlev uygulayın

  private _notifyNewUrl (key:string = window.location.pathname): void {
    this._path=key;
    // trigger whatever you need to do on url changes
    console.debug(`current query: ${this._path}`);
  }

0

Sadece yeni URL'yi istediğimden, @gblazex ve @Alberto S. kodlarını uyarlamak için uyarladım:

(function(history){

  var pushState = history.pushState;
    history.pushState = function(state, key, path) {
    if (typeof history.onpushstate == "function") {
      history.onpushstate({state: state, path: path})
    }
    pushState.apply(history, arguments)
  }
  
  window.onpopstate = history.onpushstate = function(e) {
    console.log(e.path)
  }

})(window.history);

0

Yapabilseniz bile yerel işlevleri değiştirmenin iyi bir fikir olduğunu düşünmüyorum ve her zaman uygulama kapsamınızı korumalısınız, bu nedenle iyi bir yaklaşım, küresel pushState işlevini kullanmak değil, kendinizinkini kullanın:

function historyEventHandler(state){ 
    // your stuff here
} 

window.onpopstate = history.onpushstate = historyEventHandler

function pushHistory(...args){
    history.pushState(...args)
    historyEventHandler(...args)
}
<button onclick="pushHistory(...)">Go to happy place</button>

Başka bir kod yerel pushState işlevini kullanırsa, bir olay tetikleyicisi almayacağınıza dikkat edin (ancak bu gerçekleşirse, kodunuzu kontrol etmeniz gerekir)


-5

Standart durumlar olarak:

Sadece history.pushState () veya history.replaceState () öğesinin çağrılmasının bir popstate olayını tetiklemeyeceğini unutmayın. Popstate olayı yalnızca geri düğmesini tıklamak (veya JavaScript'te history.back () öğesini aramak gibi) bir tarayıcı eylemi gerçekleştirerek tetiklenir.

WindowEventHandlers.onpopstate tetikleyicisine history.back () yöntemini çağırmamız gerekir

Öyle insted:

history.pushState(...)

yapmak:

history.pushState(...)
history.pushState(...)
history.back()
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.