问题
I am using the Ratchet.js/push.js library to create the UI for a mobile web app. In this library, links are handled by "pushing" the to-be-loaded file into the ".content" DOM element rather than loading the entire page. However, push.js does not load any scripts it finds when loading a page - which disables my Knockout.js code.
I found a solution here on StackOverflow that works pretty well - just add an event listener for the push event. I modified it so that it can load any script across multiple pages and so it works with external script files:
window.addEventListener('push', function () {
var scriptsList = document.querySelectorAll('script.js-custom'); // Add a "js-custom" class to your script tag
for (var i = 0; i < scriptsList.length; ++i) {
// Handle scripts in separate files by assigning the script file name to its id.
// We save it in a variable because the ".done" callback is asynchronous.
scriptName = scriptsList[i].id; // IMPORTANT: Only one loadable script per page!
$.getScript("/path info here/" + scriptName)
.done(function (script, textStatus) {
eval(script);
})
... error handling ...
}
});
In the target HTML page, scripts are given class and id tags so they work with the above:
<script src="Challenge.js" class="js-custom" id="challenge.js"></script>
Note, too, that Knockout bindings have to occur to a specific named DOM element so that knockout doesn't get confused:
ko.cleanNode($("#ChallengePage")[0]);
ko.applyBindings(challengeFn, $("#ChallengePage")[0]);
We use cleanNode to avoid the "already bound" errors.
OK! So all this works great and I hope that someone who is struggling with this finds it useful.
HOWEVER, when the link is given a transition:
<a href="challenge.html" data-transition="slide-in">....
Then it breaks with a "Cannot read property 'nodeType' of undefined. I had thought that maybe it was just a problem of waiting for the transition to finish, but even if I replace the eval of the script with:
scriptContents = script;
setTimeout(function () { eval(scriptContents); }, 1000);
it doesn't help.
Any advice or assistance would be greatly appreciated! I don't really need to "push" pages if I don't get to use the transitions so I am hoping that someone will have the last key to making this all work!
UPDATE: The error was occurring because the "document.querySelectorAll" call when using a transition uses the current document rather than the document being pushed. Also, using "webkitTransitionEnd" as my event works as well but this doesn't fix the document issue. Thus, I can make this work, but only for a single transition - now I don't have a way of getting the document being loaded. Ideally, a solution that works whether a links uses a transition or not is what I am looking for.
回答1:
The combination of Ratchet and Knockout will likely be popular in the coming months so I hope that others find this solution.
To combine the Ratchet.js and Knockout.js libraries requires only that you handle the fact that Ratchet.js (via Push.js) will attempt to manage your page transitions. During a transition, the JavaScript on your target page - including Knockout - will not be run unless you specifically make this happen. That is what this solution does: it makes it possible to load and run your Knockout JavaScript code even though Ratchet is managing page transitions.
In my solution, we always place JavaScript in a separate file and implement Content Security Policy that forbids any JS code from running on the page. It is simply good security hygiene and helps reduce the attack surface for XSS attacks. So the solution below 1) assumes that the JS is in a separate file and 2) assumes that the HTML and JS files have the exact same name and path - except for the extensions (sort of like treating the .js file like an ASP.NET code-behind for the HTML file).
On your "root" page - the one that starts all of your interactions with other pages on your mobile web app, place the following function. It will load the appropriate .js file whenever the corresponding .html file is loaded by Ratchet:
window.addEventListener('push', function (params) {
var targetPage = params.target.document.baseURI.replace(".html", ".js");
$.getScript(targetPage)
.done(function (script, textStatus) {
eval(script);
})
.fail(function (jqxhr, settings, exception) {
alert("Error loading script: " + exception);
});
});
Note that you will have to apply your Knockout bindings to a named and unique div in your HTML page (generally a div that lives directly underneath the Ratchet .content div). This is just because each page load has to apply its Knockout bindings to just the HTML being loaded.
ko.cleanNode($("#DivPageName")[0]);
ko.applyBindings(KnockoutFn, $("#DivPageName")[0]);
UPDATE: I have found that this solution gets "confused" at times as pages are pushed and popped from the history stack. I have decided not to use it although it seems like it is about 97% there. If anyone has any improvements that would make this completely reliable, I am all ears!
来源:https://stackoverflow.com/questions/23859570/knockout-js-plays-nicely-with-ratchet-and-push-js-until-i-add-a-data-transition