History.pushState के माध्यम से इतिहास के परिवर्तनों के बारे में कैसे सूचित किया जाए?


154

इसलिए अब HTML5 history.pushStateब्राउज़र इतिहास को बदलने के लिए परिचय देता है, वेबसाइटें URL के टुकड़े करने वाले पहचानकर्ता को बदलने के बजाय अजाक्स के साथ संयोजन में इसका उपयोग करना शुरू कर देती हैं।

अफसोस की बात है कि उन कॉल का पता अब तक नहीं लगाया जा सकता है onhashchange

मेरा प्रश्न है: क्या कोई विश्वसनीय तरीका (हैक?) है जब किसी वेबसाइट का उपयोग करता है history.pushState? विनिर्देश उन घटनाओं के बारे में कुछ भी नहीं बताता है जो उठाए गए हैं (कम से कम मुझे कुछ भी नहीं मिला)।
मैंने एक मुखौटा बनाने की कोशिश की और window.historyअपने स्वयं के जावास्क्रिप्ट ऑब्जेक्ट के साथ बदल दिया , लेकिन इसका कोई प्रभाव नहीं पड़ा।

आगे की व्याख्या: मैं एक फ़ायरफ़ॉक्स ऐड-ऑन विकसित कर रहा हूं जो इन परिवर्तनों का पता लगाने और तदनुसार कार्य करने की आवश्यकता है।
मुझे पता है कि कुछ दिन पहले एक ऐसा ही सवाल था, जिसमें पूछा गया था कि क्या कुछ DOM ईवेंट्स को सुनना कुशल होगा लेकिन मैं इस पर भरोसा नहीं करूंगा क्योंकि ये ईवेंट बहुत अलग कारणों से उत्पन्न हो सकते हैं।

अपडेट करें:

यहाँ एक jsfiddle (फ़ायरफ़ॉक्स 4 या क्रोम 8 का उपयोग करें) से पता चलता है कि onpopstateजब pushStateकॉल किया जाता है तो ट्रिगर नहीं होता है (या क्या मैं कुछ गलत कर रहा हूं? इसे सुधारने के लिए स्वतंत्र महसूस करें!)।

अपडेट 2:

एक और (पक्ष) समस्या यह है कि window.locationउपयोग करते समय अद्यतन नहीं किया जाता है pushState(लेकिन मैं इस बारे में पहले से ही एसओ के बारे में यहां पढ़ता हूं)।

जवाबों:


194

5.5.9.1 घटना की परिभाषा

Popstate घटना कुछ मामलों में निकाल दिया जाता है जब एक सत्र इतिहास प्रविष्टि के लिए नेविगेट।

इसके अनुसार, जब आप उपयोग करते हैं तो पॉपस्टेट को निकाल देने का कोई कारण नहीं है pushState। लेकिन इस तरह के एक घटना के रूप pushstateमें काम आएगा। क्योंकि historyएक होस्ट ऑब्जेक्ट है, आपको इसके साथ सावधान रहना चाहिए, लेकिन फ़ायरफ़ॉक्स इस मामले में अच्छा लगता है। यह कोड ठीक काम करता है:

(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 हो जाता है :

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

आप window.history.replaceStateउसी तरह से बंदर-पैच कर सकते हैं ।

नोट: निश्चित रूप से आप onpushstateकेवल वैश्विक ऑब्जेक्ट में जोड़ सकते हैं , और आप इसे अधिक ईवेंट के माध्यम से भी संभाल सकते हैंadd/removeListener


आपने कहा कि आपने पूरी historyवस्तु बदल दी । इस मामले में यह अनावश्यक हो सकता है।
14

@galambalazs: हाँ शायद। शायद (नहीं पता) आसानी से window.historyहै, लेकिन इतिहास वस्तु के गुण नहीं हैं ... इस समाधान के लिए धन्यवाद एक गुच्छा :)
फेलिक्स क्लिंग

कल्पना के अनुसार एक "पेजेट्रांस" घटना है, ऐसा लगता है कि इसका अभी तक कार्यान्वयन नहीं किया गया है।
मोहम्मद मंसूर

2
@ user280109 - अगर मुझे पता होता तो मैं आपको बता देता। :) मुझे लगता है कि ओपेरा एटीएम में ऐसा करने का कोई तरीका नहीं है।
gblazex

1
@cprcrack यह वैश्विक दायरे को साफ रखने के लिए है। आपको pushStateबाद में उपयोग के लिए मूल विधि को सहेजना होगा । इसलिए एक वैश्विक चर के बजाय मैंने एक IIFE में पूरे कोड को एनकोड करने के लिए चुना: en.wikipedia.org/wiki/Immediately-invoked_function_expression
gblazex

4

मैं इसका उपयोग करता था:

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!');
});

यह लगभग वैसा ही है जैसा गैलाम्बालाज़ ने किया था।

यह आमतौर पर यद्यपि overkill है। और यह सभी ब्राउज़रों में काम नहीं कर सकता है। (मुझे केवल अपने ब्राउज़र के अपने संस्करण की परवाह है।)

(और यह एक संस्करण छोड़ देता है _wr, इसलिए आप इसे या कुछ और लपेटना चाह सकते हैं। मुझे इसकी कोई परवाह नहीं है।)


4

अंत में ऐसा करने का "सही" तरीका मिला! इसमें आपके एक्सटेंशन में एक विशेषाधिकार जोड़ने और पृष्ठभूमि पृष्ठ (न केवल एक सामग्री स्क्रिप्ट) का उपयोग करने की आवश्यकता होती है, लेकिन यह काम करता है।

वह ईवेंट browser.webNavigation.onHistoryStateUpdatedजो आप चाहते हैं , जो historyURL को बदलने के लिए एपीआई का उपयोग करने पर निकाल दिया जाता है । यह केवल उन साइटों के लिए आग उगलता है, जिनकी आपको एक्सेस करने की अनुमति है, और आपको ज़रूरत पड़ने पर स्पैम पर कटौती करने के लिए URL फ़िल्टर का भी उपयोग कर सकते हैं। इसके लिए webNavigationअनुमति आवश्यक है (और संबंधित डोमेन के लिए होस्ट की अनुमति)।

ईवेंट कॉलबैक को टैब आईडी, वह URL जो "नेविगेट" किया जा रहा है, और ऐसे अन्य विवरण मिलते हैं। यदि आपको उस पृष्ठ पर सामग्री स्क्रिप्ट में एक कार्रवाई करने की आवश्यकता होती है, जब घटना आग लग जाती है, या तो प्रासंगिक स्क्रिप्ट को सीधे पृष्ठभूमि पृष्ठ से इंजेक्ट करें, या सामग्री स्क्रिप्ट portको पृष्ठभूमि पृष्ठ पर खुलने दें जब यह लोड होता है, तो पृष्ठभूमि पृष्ठ सहेजें टैब आईडी द्वारा अनुक्रमित संग्रह में वह पोर्ट, और घटना के आग लगने पर संबंधित पोर्ट (पृष्ठभूमि स्क्रिप्ट से सामग्री स्क्रिप्ट तक) पर संदेश भेजें।


3

आप window.onpopstateघटना के लिए बाध्य कर सकते हैं ?

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

डॉक्स से:

विंडो पर पॉपस्टैट इवेंट के लिए एक ईवेंट हैंडलर।

जब भी सक्रिय इतिहास प्रविष्टि में बदलाव होता है, तो हर बार एक पॉपस्टेट घटना को खिड़की में भेज दिया जाता है। अगर इतिहास प्रविष्टि को सक्रिय किया जा रहा है, तो उसे history.pushState () के लिए कॉल किया गया था या इतिहास के कॉल से प्रभावित था ।replaceState (), पॉपस्टैट इवेंट की राज्य संपत्ति में इतिहास प्रविष्टि की राज्य ऑब्जेक्ट की एक प्रति शामिल है।


6
मैं पहले से ही इस की कोशिश की और घटना केवल शुरू हो रहा है जब उन के इतिहास में वापस चला जाता है (या के किसी भी history.go, .back) कार्य कहा जाता है। पर नहीं pushState। यहाँ यह परीक्षण करने का मेरा प्रयास है, हो सकता है कि मैं कुछ गलत करूँ: jsfiddle.net/fkling/vV9vd यह केवल इस तरह से संबंधित है कि यदि इतिहास द्वारा बदल दिया गया था pushState, तो संबंधित राज्य वस्तु घटना हैंडलर को पास कर दी जाती है जब अन्य तरीके कहा जाता है।
फेलिक्स क्लिंग

1
आह। उस मामले में केवल एक चीज जिसके बारे में मैं सोच सकता हूं, वह यह है कि इतिहास स्टैक की लंबाई को देखने के लिए एक समयबाह्य दर्ज करें और यदि स्टैक का आकार बदल गया है तो किसी घटना को आग लगा दें।
स्टेफ

ठीक है यह एक दिलचस्प विचार है। केवल एक चीज यह है कि टाइमआउट को अक्सर पर्याप्त फायर करना होगा ताकि उपयोगकर्ता को किसी भी (लंबे) देरी की सूचना न हो (मुझे नए URL के लिए डेटा लोड और दिखाना होगा)। मैं हमेशा जहां संभव हो, टाइमआउट और मतदान से बचने की कोशिश करता हूं, लेकिन अब तक यह एकमात्र समाधान है। मैं अभी भी अन्य प्रस्तावों की प्रतीक्षा करूंगा। लेकिन अभी के लिए आपका बहुत-बहुत धन्यवाद!
फेलिक्स क्लिंग

हर क्लिक पर इतिहास की छड़ी की लंबाई की जाँच करना पर्याप्त हो सकता है।
फेलिक्स क्लिंग

1
हाँ - यह काम करेगा। मैं इस के साथ एक नाटक कर रहा हूँ - एक मुद्दा है प्रतिस्थापन कॉल जो एक घटना को नहीं फेंकेगा क्योंकि स्टैक का आकार नहीं बदला होगा।
स्टिफ

1

चूंकि आप फ़ायरफ़ॉक्स एडऑन के बारे में पूछ रहे हैं, इसलिए यहां वह कोड है जो मुझे काम करने के लिए मिला है। उपयोग करने unsafeWindowकी अब अनुशंसा नहीं की जाती है , और जब एक क्लाइंट स्क्रिप्ट से पुशस्टेट को संशोधित किया जाता है, तो त्रुटियां होती हैं:

प्रॉपर्टी हिस्ट्री तक पहुंचने की अनुमति से इनकार

इसके बजाय, एक API है जिसे एक्सपोर्टफ़ंक्शन कहा जाता है जो फ़ंक्शन को 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});

अंतिम पंक्ति पर, आप बदलना चाहिए unsafeWindow.history,करने के लिए window.history। यह मेरे लिए असुरक्षित तरीके से काम नहीं कर रहा था।
लिंडसे-नीड्स-स्लीप

0

galambalazs का बंदर बंदर जवाबwindow.history.pushState और window.history.replaceState, लेकिन किसी कारण से इसने मेरे लिए काम करना बंद कर दिया। यहाँ एक विकल्प है जो उतना अच्छा नहीं है क्योंकि यह मतदान का उपयोग करता है:

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

क्या मतदान के लिए कोई विकल्प है?
SuperUberDuper

@SuperUberDuper: अन्य उत्तर देखें।
फ्लिमल

@ फिलम पोलिंग अच्छा नहीं है, लेकिन बदसूरत है। लेकिन ठीक है, आपके पास आपके द्वारा कहे गए विकल्प नहीं थे।
यारोप्रो जूल

यह बन्दर पैचिंग से बहुत बेहतर है, बन्दर पैचिंग को अन्य स्क्रिप्ट द्वारा पूर्ववत किया जा सकता है और आपको इसके बारे में कोई सूचना नहीं मिलती है।
इवान कैस्टेलानोस

0

मुझे लगता है कि इस विषय को और अधिक आधुनिक समाधान की आवश्यकता है।

मुझे यकीन है कि nsIWebProgressListenerवापस चारों ओर था तो मुझे आश्चर्य है कि किसी ने भी इसका उल्लेख नहीं किया।

फ्रेमवर्क से (e10s संगतता के लिए):

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

तब में सुन रहा था onLoacationChange

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

यह स्पष्ट रूप से सभी pushState है। लेकिन एक टिप्पणी की चेतावनी है कि यह "पुशस्टो के लिए ALSO ट्रिगर करता है"। इसलिए हमें यह सुनिश्चित करने के लिए यहां कुछ और फ़िल्टरिंग करने की आवश्यकता है कि यह सिर्फ पुशस्टेट सामान है।

इसके आधार पर: https://github.com/jgraham/gecko/blob/55d8d9aa7311386ee2dabfccb481684c8920a527/toolkit/modules/adodons/adebons/WebNavigation.jsm#L18

और: संसाधन: //gre/modules/WebNavigationContent.js


इस उत्तर का टिप्पणी से कोई लेना-देना नहीं है, जब तक कि मुझसे गलती न हो। WebProgressListeners FF एक्सटेंशन के लिए हैं? यह प्रश्न HTML5 इतिहास के बारे में है
क्लार

@ क्लोअर इन टिप्पणियों को हटाने की अनुमति देता है कृपया
Noitidart

0

वैसे, मुझे pushStateसंपत्ति के बदले के कई उदाहरण दिखाई देते हैं, historyलेकिन मुझे यकीन नहीं है कि यह एक अच्छा विचार है, मैं इतिहास के समान एपीआई के साथ एक सेवा घटना बनाना पसंद करता हूं जिस तरह से आप न केवल धक्का राज्य को नियंत्रित कर सकते हैं, बल्कि राज्य को भी बदल सकते हैं। और साथ ही यह वैश्विक इतिहास एपीआई पर भरोसा नहीं करने वाले कई अन्य कार्यान्वयनों के लिए दरवाजे खोलता है। कृपया निम्न उदाहरण देखें:

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];
});

यदि आपको EventEmitterपरिभाषा की आवश्यकता है , तो ऊपर दिया गया कोड NodeJS ईवेंट एमिटर पर आधारित है: https://github.com/nodejs/node/blob/36732084db9d0ff59b6ce31e8395cdcdaa156be/lib/events.jsutils.inheritsकार्यान्वयन यहां पाया जा सकता है: https://github.com/nodejs/node/blob/36732084db9d0ff59b6ce31e839450cd91a156be/lib/util.jn#L970


मैंने अनुरोधित जानकारी के साथ अपना उत्तर संपादित किया है, कृपया जाँच करें!
विक्टर क्विरोज़

@VictorQueiroz कार्यों को संक्षिप्त करने के लिए एक अच्छा और साफ विचार है। लेकिन अगर कुछ तीसरे पुस्‍तकालय पुस्‍तकालय पुकारते हैं, तो आपका HistoryApi अधिसूचित नहीं किया जाएगा।
यारोप्रो

0

मैं इसके बजाय मूल इतिहास विधि को अधिलेखित नहीं करूंगा, इसलिए यह सरल कार्यान्वयन मेरे स्वयं के फ़ंक्शन को इवेंटेडपश राज्य बनाता है जो कि केवल एक ईवेंट भेजता है और इतिहास को वापस करता है। pushState ()। किसी भी तरह से ठीक काम करता है, लेकिन मुझे यह कार्यान्वयन थोड़ा साफ लगता है क्योंकि देशी तरीके भविष्य के डेवलपर्स के प्रदर्शन के लिए जारी रहेंगे।

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 द्वारा दिए गए समाधान के आधार पर , यदि आप एक ही दृष्टिकोण का पालन करना चाहते हैं, लेकिन तीर के कार्यों का उपयोग करते हुए, अपने जावास्क्रिप्ट तर्क में नीचे दिए गए उदाहरण का पालन करें:

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();
});

फिर, एक अन्य फ़ंक्शन लागू करें _notifyUrl(url)जो किसी भी आवश्यक कार्रवाई को चालू करता है जब आपको वर्तमान पृष्ठ url अपडेट होने की आवश्यकता हो सकती है (भले ही पृष्ठ सभी पर लोड नहीं किया गया हो)

  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

चूंकि मुझे नया URL चाहिए था, इसलिए मैंने इसे पाने के लिए @gblazex और @Alberto S. के कोड अनुकूलित किए हैं:

(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

मुझे नहीं लगता कि यह एक अच्छा विचार है, भले ही आप मूल कार्यों को संशोधित कर सकते हैं, और आपको हमेशा अपना आवेदन दायरा रखना चाहिए, इसलिए एक अच्छा तरीका वैश्विक पुशस्टैट फ़ंक्शन का उपयोग नहीं कर रहा है, इसके बजाय, अपने स्वयं के एक का उपयोग करें:

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>

ध्यान दें कि यदि कोई अन्य कोड देशी पुशस्टैट फ़ंक्शन का उपयोग करता है, तो आपको एक इवेंट ट्रिगर नहीं मिलेगा (लेकिन यदि ऐसा होता है, तो आपको अपना कोड चाहिए)


-5

मानक राज्यों के रूप में:

ध्यान दें कि केवल कॉलिंग history.pushState () या history.replaceState () पॉपस्टैट इवेंट को ट्रिगर नहीं करेगी। पॉपस्टैट ईवेंट केवल एक ब्राउज़र क्रिया करके ट्रिगर की जाती है जैसे कि बैक बटन पर क्लिक करना (या कॉलिंग हिस्ट्री.बैक (जावास्क्रिप्ट में))

हमें window.ventHandlers.onpopstate को ट्रिगर करने के लिए history.back () पर कॉल करने की आवश्यकता है

तो का संकेत:

history.pushState(...)

करना:

history.pushState(...)
history.pushState(...)
history.back()
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.