/** * Loads a resource within the app. */ function ResourceLoader(baseurl) { this.BASEURL = baseurl; } ResourceLoader.prototype.loadResource = function(resource, callback) { var self = this; evaluateScripts([resource], function(success) { if(success) { var resource = Template.call(self); callback.call(self, resource); } else { var title = "Resource Loader Error", description = `Error loading resource '${resource}'. \n\n Try again later.`, alert = createAlert(title, description); navigationDocument.presentModal(alert); } }); } /* * Helper method to convert a relative URL into an absolute URL */ ResourceLoader.prototype.prepareURL = function(url) { // Handle URLs relative to the "server root" (baseURL) if (url.indexOf("/") === 0) { url = this.baseURL + url.substr(1); } return url; }; /* * Helper method to request templates from the server. */ ResourceLoader.prototype.fetch = function(options) { console.log("Fetching " + options.url); if (typeof options.url !== "string") { throw new TypeError("ResourceLoader.fetch: url option must be a string"); } // cancel the previous request if it is still in-flight if (options.concurrent !== true) { this.cancelFetch(); } // parse the request URL const docURL = this.prepareURL(options.url); const xhr = new XMLHttpRequest(); xhr.open("GET", docURL); xhr.responseType = "document"; xhr.onload = function() { const responseDoc = xhr.response; this.prepareDocument(responseDoc); if (typeof options.success === "function") { options.success(responseDoc); } else { navigationDocument.pushDocument(responseDoc); } }.bind(this); xhr.onerror = function() { if (typeof options.error === "function") { options.error(xhr); } else { const alertDocument = createLoadErrorAlertDocument(docURL, xhr, true); navigationDocument.presentModal(alertDocument); } }; xhr.send(); // preserve the request so it can be cancelled by the next fetch if (options.concurrent !== true) { this._fetchXHR = xhr; } } /* * Helper method to cancel a running XMLHttpRequest */ ResourceLoader.prototype.cancelFetch = function() { const xhr = this._fetchXHR; if (xhr && xhr.readyState !== XMLHttpRequest.DONE) { xhr.abort(); } delete this._fetchXHR; }; /* * Helper method to mangle relative URLs in XMLHttpRequest response documents */ ResourceLoader.prototype.prepareDocument = function(document) { traverseElements(document.documentElement, this.prepareElement); }; /* * Helper method to mangle relative URLs in DOM elements */ ResourceLoader.prototype.prepareElement = function(elem) { if (elem.hasAttribute("src")) { const rawSrc = elem.getAttribute("src"); const parsedSrc = ResourceLoader.prototype.prepareURL(rawSrc); elem.setAttribute("src", parsedSrc); } if (elem.hasAttribute("srcset")) { // TODO Prepare srcset attribute } } /** * Convenience function to iterate and recurse through a DOM tree */ function traverseElements(elem, callback) { callback(elem); const children = elem.children; for (var i = 0; i < children.length; ++i) { traverseElements(children.item(i), callback); } }