I'm using Framework7 to build a web application.
https://framework7.io/
I know Framework7 provides routing APIs to navigate through the HTML pages.
https://framework7.io/docs/view.html#router-api-methods-properties
As far as I know the pages are loaded on the fly via AJAX requests. Is it possible to preload all of them and not having any AJAX request after that? I'd like to build a single page application (SPA) in which all the data (HTML pages, CSS and JavaScript code) is loaded at startup
I think theirs no direct solution to do it like your question, since every route will trigger requests when you visit it, but you can try to fix your issue by doing this trick:
Declare global or helper js file.
after declare it, create method/methods inside it which it most be trigger when you visit route, and save the result in global variable (ex: aboutUsDataCache) which can access it in route.
replace all ajax request routes action to method which created for it.
add condition to check if aboutUsDataCache is empty or not, if not empty thats mean we trigger request and has data and we are in SPA, so that we dont to access method again.
Example from real project:
I have main-config.js which is load before routes and has this code:
module.exports.tmpAppCache = {
fullCadaverList: false,
fullImagesList: false,
fullVideosList: false,
fullPdfList: false,
};
and I have ajax-helper.js file which its loaded before routes too and have methods
like this:
export function getFullPdfList() {
// Your ajax request here
}
in routes has this code:
{
path: '/pdf/',
async: function (routeTo, routeFrom, resolve, reject) {
if(globalObject.tmpAppCache.fullPdfList !== false){
resolve(
{
component: pdfPage,
},
{
context: {
data: globalObject.tmpAppCache.fullPdfList
}
}
);
}else{ getFullPdfList()
}
},
},
This example from real project, I try to remove all unwanted code to be something clear so that theres some note:
This example usage WebPack, so that you see import and export some time..
I remove promise from functions to make code example is clear.
since you need to trigger all request at start, you need to call all methods in index route, so that when you go to any page like our example you will see data is cached and request will not trigger.
Good luck
Related
I have a simple website that uses async components. Here's an example of one:
Vue.component('news', function(resolve, reject){
let data = {
action: 'load_news_article',
article_id: 0 // need the article ID here
};
$.post(window.ajax_url, data, function(r){
r = JSON.parse(r);
if( r.success ) {
resolve({
template: r.data
});
}
});
});
I have dynamic routes that map to this component as well. Those <router-link> URL's look like /news/some-article. I created the route as /news/:article in the router.
How can I access the article variable before firing my ajax? My goal is to pass it to the server along with the initial component load. If Vue doesn't allow that, then I can't think of any other way other than letting the router navigate the page, then I parse the new URL with my own JS.
It's a really simple website. There's only 5 components (one for each page) and only this one is dynamic. So I'm looking for a solution that doesn't require me to overhaul my structure.
In my Backbone app (Rails backend), I am using [Handlebars] Javascript templates (JST's) for rendering my Backbone views. Whenever I do a browser refresh while on one of those URL templates, it jumps back to the root URL. I want it to stay on that current template after page-refresh. I want to do something like this...
getPage: function() {
$(window).on('beforeunload', function(){
var fragment = Backbone.history.fragment;
});
if (fragment === "this_current_template") {
return App.getCurrentTemplatePage(); // render it
} else if (fragment === "") {
return App.getFrontMainPage(); // render it
}
}
getPage is a method in my backbone App object. My fragment variable can't be recognized outside of the $(window) event handler. I can't seem to find a way to persist my previous Backbone.history.fragment in a variable, even if I make fragment global. It's all swept away after refresh. I know about Backbone.history.loadUrl(Backbone.history.fragment);, but I just can't get that to work either. It only seems to work prior to browser refresh.
Is it something I'll have to change in the root route of my backend code? or is there a way to persist the current page using Backbone / JS on page reload? Any help on this would be much appreciated!
First of all you need to create a Router. According to Backbone docs:
var Router = Backbone.Router.extend({
routes: {
"main": "main"
},
main: function() {
// your code here
}
});
After that you only need to init Backbone.history:
Backbone.history.start({});
After that Backbone automatically will try to find appropriate route for your hashbang and fire the appropriate action. In given example if your initial hashbang will be #main then Router.main action will be executed.
I have a Durandal application, and I use router.mapUnknownRoutes to display a user-friendly error page if the URL does not correspond to a known route. This works fine -- if I go to, say /foo, and that doesn't match a route, then the module specified by mapUnknownRoutes is correctly displayed.
However I cannot find any way to display that same error page when I have a parameterised route, and the parameter does not match anything on the backend.
For example, say I have a route like people/:slug where the corresponding module's activate method looks like this:
this.activate = function (slug) {
dataService.load(slug).then(function () {
// ... set observables used by view ...
});
};
If I go to, say /people/foo, then the result depends on whether dataService.load('foo') returns data or an error:
If foo exists on the backend then no problem - the observables are set and the composition continues.
If foo doesn't exist, then the error is thrown (because there is no catch). This results in an unhandled error which causes the navigation to be cancelled and the router to stop working.
I know that I can return false from canActivate and the navigation will be cancelled in a cleaner way without borking the router. However this isn't what I want; I want an invalid URL to tell the user that something went wrong.
I know that I can return { redirect: 'not-found' } or something similar from canActivate. However this is terrible because it breaks the back button -- after the redirect happens, if the user presses back they go back to /people/foo which causes another error and therefore another redirect back to not-found.
I've tried a few different approaches, mostly involving adding a catch call to the promise definition:
this.activate = function (slug) {
dataService.load(slug).then(function () {
// ... set observables used by view ...
}).catch(function (err) {
// ... do something to indicate the error ...
});
};
Can the activate (or canActivate) notify the router that the route is in fact invalid, just as though it never matched in the first place?
Can the activate (or canActivate) issue a rewrite (as opposed to a redirect) so that the router will display a different module without changing the URL?
Can I directly compose some other module in place of the current module (and cancel the current module's composition)?
I've also tried an empty catch block, which swallows the error (and I can add a toast here to notify the user, which is better than nothing). However this causes a lot of binding errors because the observables expected by the view are never set. Potentially I can wrap the whole view in an if binding to prevent the errors, but this results in a blank page rather than an error message; or I have to put the error message into every single view that might fail to retrieve its data. Either way this is view pollution and not DRY because I should write the "not found" error message only once.
I just want an invalid URL (specifically a URL that matches a route but contains an invalid parameter value) to display a page that says "page not found". Surely this is something that other people want as well? Is there any way to achieve this?
I think you should be able to use the following from the activate or canActivate method.
router.navigate('not-found', {replace: true});
It turns out that Nathan's answer, while not quite right, has put me on the right track. What I have done seems a bit hacky but it does work.
There are two options that can be passed to router.navigate() - replace and trigger. Passing replace (which defaults to false) toggles between the history plugin using pushState and replaceState (or simulating the same using hash change events). Passing trigger (which defaults to true) toggles between actually loading the view (and changing the URL) vs only changing the URL in the address bar. This looks like what I want, only the wrong way around - I want to load a different view without changing the URL.
(There is some information about this in the docs, but it is not very thorough: http://durandaljs.com/documentation/Using-The-Router.html)
My solution is to navigate to the not-found module and activate it, then navigate back to the original URL without triggering activation.
So in my module that does the database lookup, in its activate, if the record is not found I call:
router.navigate('not-found?realUrl=' + document.location.pathname + document.location.hash, { replace: true, trigger: true });
(I realise the trigger: true is redundant but it makes it explicit).
Then in the not-found module, it has an activate that looks like:
if (params.realUrl) {
router.navigate(params.realUrl, { replace: true, trigger: false });
}
What the user sees is, it redirects to not-found?realUrl=people/joe and then immediately the URL changes back to people/joe while the not-found module is still displayed. Because these are both replace style navigations, if the user navigates back, they go to the previous entry, which is the page they came from before clicking the broken link (i.e. what the back button is supposed to do).
Like I said, this seems hacky and I don't like the URL flicker, but it seems like the best I can do, and most people won't notice the address bar.
Working repo that demonstrates this solution
In my application I want to read the parameters user is entering and then I want to use that parameter. http://responsive.beta.postify.com/X I want to read that X value. But first how do I ensure that the router expects a parameter?
My router is like this
Cards.Router.map(function ()
{
this.resource('cards', {path: '/'}, function ()
{
// additional child routes
this.resource('selectImage');
this.resource('message');
this.resource('recipient');
this.resource('orderStatus');
this.resource('thankyou');
this.resource('accountInfo');
this.resource('recentOrders');
this.resource('howTo');
this.resource('faq');
});
});
I want that parameter whenever the app loads. That is going to be my clientID which I would be using to fetch data from server depending upon the client.
Any thoughts on it?
When I do something like this
Cards.Router.map(function ()
{
this.resource('cards', {path: ':clientID'}, function ()
{
// additional child routes
this.resource('selectImage');
this.resource('message');
this.resource('recipient');
this.resource('orderStatus');
this.resource('thankyou');
this.resource('accountInfo');
this.resource('recentOrders');
this.resource('howTo');
this.resource('faq');
});
});
and in my browser if I put like this http://responsive.beta.postify.com/#/26 then its working but if I do like http://responsive.beta.postify.com/26 then it is not working.
To answer your question directly, to use a parameter in a route you would do something like this:
this.resource('cards', { path: '/:user_id' });
Then in your route
App.CardsRoute = Ember.Route.extend({
model: function(params) {
return this.store.find('post', params.user_id);
}
});
This is how you can get a parameter in a certain route. Now as far as your application goes, using the code I posted above should get you that parameter as long as they access the root ('/') of your application on first load and have the user_id in the url.
I would suggest a different strategy maybe for getting the client_id and storing it for later user in your application. For example, in my application I have an Ember.Application.initializer({}) where I store the client_id. All depends on your server configuration and how your app is built, but I would definitely try and get the client_id a different way if you can!
Good luck.
This is a simple question, but I am new to routing and haven't been able to find an answer to this.
I have a Marionette Router (if I intimidate you, its really the same thing as a Backbone Router. Very simple).
sys.routes = {
"app/:id": "onAppRoute",
};
sys.Router = new Marionette.AppRouter({
controller: {
onAppRoute: function(route) {
console.log('Called app route!');
},
},
appRoutes: sys.routes,
});
Backbone.history.start({pushState: true})
This works - if you hit the back button my browser, the url will change within my Single Page Application and will call the onAppRoute function.
However, let's say I open a new browser window and paste in my page url to a certain 'app':
http://localhost/app/myApplication
This doesn't call the onAppRoute function. It doesn't even seem like it should, though, but I really don't know.
I want it to.
I don't know if I am doing it wrong, or if I should just manually fire it by grabbing my page url on page load, parsing it, then 'navigating' to that route. Seems hacky.
Contrary to your intuition, backbone's default behaviour is to trigger matching routes on page load! cf. http://backbonejs.org/#Router - look for the option silent: true. You'd have to specify that for the router to IGNORE your matching routes on page load, i.e. not trigger the corresponding callbacks.
So your problem lies somewhere else: your routes do NOT match the url you have stated as an example. Clearly, you require an :id parameter, trailing http://localhost/app/myApplication. Therefore, http://localhost/app/myApplication/213 would cause your callback to be triggered on page load, given you didn't pass silent: true as an option to backbone.history.start().
If you want to match the 'root' url, i.e. no params, you would define the following route:
routes: {
'/': someFunction
}
The :id part is a parameter, which will be extracted by Backbone.Router and sent as an argument to onAppRoute.
But in your URL you don't have any parameters /localhost/app/myApplication