Everything I've been searching for is just a tutorial how to use pushState, replaceState, history.state, etc. Those concepts are simple but one thing I'm wondering how people solve is how to know what the initial state is.
Say you SPA is hosted at https://example.com/en-us/myapp/. Go there and your home page of the app is loaded, click around and it does a pushState to see you to https://example.com/en-us/myapp/get/users. Great, now you see a list of users and thanks to the history api, it wasn't an actual page load.
But now let's pretend a user had that https://example.com/en-us/myapp/get/users state bookmarked and the started the app off at this URL. Ok, so your server listens to that and serves up the app. My question is, how do you know that get/users is the current state and you need to show the associated view? Do you just know that your app is hosted at https://example.com/en-us/myapp/ and so you get whatever is after that to know?
Something like this:
function getState (uri) {
return uri.match(/^https:\/{2}(?:w{3}\.)?example.com\/en-us\/myapp\/?(.*)/i)[1];
}
var state = getState(location.href);
and if state is falsey then load the initial view, otherwise handle the state and show the list of users when state === 'get/users'?
Yes, that is quite right. However, you could try using location.pathname to fetch the state, so that your regex does not need to include the domain name.
For example:
function getState (uri){
var path = uri.split("myapp", 2)[1]; // This will split the pathname after 'myapp'
console.log(path) // Just for debugging purposes
// Now we can decide what to do with the path (i.e. "/get/users")
// For example, we can use a switch or a simple if statement
if (path === '/get/users'){
return true
} else {
return false
}
}
var state = getState(location.pathname);
That is just a simple example of a router. You can now try building your very own router for your SPA. Also, there are many libraries out there for you to use if you would like a different approach. You can take a look at these ones if you would like.
navigo
router.js
Also, if you are using a framework to build your SPA, they often have their own routing ability built in. These are just some of the many frameworks that have routers built in. (Sorry, I've <10 reputation so I'm not allowed more than two links).
Vue.js — vuejs.org/v2/guide/routing.html
Mithril.js — mithril.js.org/#routing
Ember.js — guides.emberjs.com/v2.13.0/routing/
Of course, it is ultimately your choice which to use. You could expand upon the example I've provided, by simply implementing a switch for different links/pages in your SPA. I wish you the best with your app!
Related
I have a Next.js (fix may not necessarily have anything to do with Next.js) app that I want to limit some navigation. This page in the Next.js docs mentions "you should guard against navigation to routes you do not want programmatically", but I'm unsure how to do this. Let's say I have the following pages:
/bikes
/boats
/cars
and I only want to allow a user to see /bikes. How would I be able to do this. I'm able to redirect from an undesired page to an acceptable page, but only after the undesired page loads. Is there a way to redirect before this (maybe by changing the URL as soon as it is changed)?
I appreciate that this is an old question, however, I wish I had known this answer to it earlier than I did, so I would like to answer it for the record.
Our next.js app had a relatively complex set of permissions associated with accessing each page (with the ability to access the page, or different data presented on it) based on the authentication status of a user as well as the authorization scopes granted to them.
The solution that we used was the to return a redirect from getServerSideProps(). Normally one would return something like the following:
const getServerSideProps = (context) => {
return {
props: {
myProp: 1
}
};
}
However, it is also possible to return a redirect. This redirect is performed on the server side prior to the page content being sent to the browser:
const getServerSideProps = (context) => {
return {
redirect: '/login'
};
}
This is relatively new functionality - it was only pulled in towards the end of last year, and there isn't much documentation for it anywhere, but it perfectly serves this use case.
I have a situation where I have written the front-end of my website as micro-services. Every functional section of my website is a react-habitat application bundled with css and html. I have done this so I can re-use these sections wherever I want on the site.
My problem is however in the checkout area I need to pass the customerId from the login application (on successful response) into the delivery address application so I can bring back the default address. I don't want to create a new application and duplicate the code for login and delivery address applications because I will need to use them elsewhere on the website.
So my question is. How can I pass this customerId from my login application to my delivery address application without a page reload? Is there a way to access a react application method from outside of that react application?
Login click handler:
clickHandler(e) {
e.preventDefault();
let payload = {"email":this.props.email, "password":this.props.password};
AuthenticationApp.login(payload).then((response) => {
if (this.props.referrer === "checkout") {
$("#checkout_login-register-tab").addClass("done");
$("#checkout_login-delivery-tab").removeClass("todo");
// Temporary hack to reload page until we can pass new user session into delivery/billing react app from here.
location.reload();
}
this.props.updateParentState({isLoggedIn: true});
})
.catch(function(response){
console.log("res2 - catch", response)
});
}
I have forced a page reload here so the delivery address application re-renders and picks up customerId from the service. I need a way to re-render the delivery application at this stage (from this separate application), either by accessing a function somehow or forcing the application to reset? Is this possible or is there a workaround other than a page reload?
You can do something like this:
loadDeliveryApp(data) {
ReactDOM.render(<DeliveryApp data={data} />, document.getElementById('delivery-app'))
ReactDOM.unmountComponentAtNode(document.getElementById("login-app"))
}
clickHandler(e) {
e.preventDefault()
// your login stuff
loadDeliveryApp(data)
}
Here is the working demo.
https://codepen.io/madhurgarg71/pen/dzgxMd
I'm the original creator of React Habitat.. all your applications can talk to each other via either a flux observer or something like redux.
I suggest flux for react habitat normally as the individual stores keep it light weight instead of one BIG store in redux where you might have dead code in a habitat situation.
You will need to create one common store that both your applications import so that if one changes it, the other will get the update. I can put a demo together later if that helps.
I have a Torii adapter that is posting my e.g. Facebook and Twitter authorization tokens back to my API to establish sessions. In the open() method of my adapter, I'd like to know the name of the provider to write some logic around how to handle the different types of providers. For example:
// app/torii-adapters/application.js
export default Ember.Object.extend({
open(authorization) {
if (this.provider.name === 'facebook-connect') {
var provider = 'facebook';
// Facebook specific logic
var data = { ... };
}
else if (this.provider.name === 'twitter-oauth2') {
var provider = 'twitter';
// Twitter specific logic
var data = { ... };
}
else {
throw new Error(`Unable to handle unknown provider: ${this.provider.name}`);
}
return POST(`/api/auth/${provider}`, data);
}
}
But, of course, this.provider.name is not correct. Is there a way to get the name of the provider used from inside an adapter method? Thanks in advance.
UPDATE: I think there are a couple ways to do it. The first way would be to set the provider name in localStorage (or sessionStorage) before calling open(), and then use that value in the above logic. For example:
localStorage.setItem('providerName', 'facebook-connect');
this.get('session').open('facebook-connect');
// later ...
const providerName = localStorage.getItem('providerName');
if (providerName === 'facebook-connect') {
// ...
}
Another way is to create separate adapters for the different providers. There is code in Torii to look for e.g. app-name/torii-adapters/facebook-connect.js before falling back on app-name/torii-adapters/application.js. I'll put my provider-specific logic in separate files and that will do the trick. However, I have common logic for storing, fetching, and closing the session, so I'm not sure where to put that now.
UPDATE 2: Torii has trouble finding the different adapters under torii-adapters (e.g. facebook-connect.js, twitter-oauth2.js). I was attempting to create a parent class for all my adapters that would contain the common functionality. Back to the drawing board...
UPDATE 3: As #Brou points out, and as I learned talking to the Torii team, fetching and closing the session can be done—regardless of the provider—in a common application adapter (app-name/torii-adapters/application.js) file. If you need provider-specific session-opening logic, you can have multiple additional adapters (e.g. app-name/torii-adapters/facebook-oauth2.js) that may subclass the application adapter (or not).
Regarding the session lifecycle in Torii: https://github.com/Vestorly/torii/issues/219
Regarding the multiple adapters pattern: https://github.com/Vestorly/torii/issues/221
Regarding the new authenticatedRoute() DSL and auto-sesssion-fetching in Torii 0.6.0: https://github.com/Vestorly/torii/issues/222
UPDATE 4: I've written up my findings and solution on my personal web site. It encapsulates some of the ideas from my original post, from #brou, and other sources. Please let me know in the comments if you have any questions. Thank you.
I'm not an expert, but I've studied simple-auth and torii twice in the last weeks. First, I realized that I needed to level up on too many things at the same time, and ended up delaying my login feature. Today, I'm back on this work for a week.
My question is: What is your specific logic about?
I am also implementing provider-agnostic processing AND later common processing.
This is the process I start implementing:
User authentication.
Basically, calling torii default providers to get that OAuth2 token.
User info retrieval.
Getting canonical information from FB/GG/LI APIs, in order to create as few sessions as possible for a single user across different providers. This is thus API-agnotic.
➜ I'd then do: custom sub-providers calling this._super(), then doing this retrieval.
User session fetching or session updates via my API.
Using the previous canonical user info. This should then be the same for any provider.
➜ I'd then do: a single (application.js) torii adapter.
User session persistence against page refresh.
Theoretically, using simple-auth's session implementation is enough.
Maybe the only difference between our works is that I don't need any authorizer for the moment as my back-end is not yet secured (I still run local).
We can keep in touch about our respective progress: this is my week task, so don't hesitate!
I'm working with ember 1.13.
Hope it helped,
Enjoy coding! 8-)
I'm looking for either a reference or an answer to what I think is a very common problem that people who are current implementing JavaScript MVC frameworks (such as Angular, Ember or Backbone) would come across.
I am looking for a way or common pattern to externalize application properties that are accessible in the JS realm. Something that would allow the javascript to load server side properties such as endpoints, salts, etc. that are external to the application root. The issue that I'm coming across is that browsers do not typically have access to the file systems because it is a security concerns.
Therefore, what is the recommended approach for loading properties that are configurable outside of a deployable artifact if such a thing exists?
If not, what is currently being used or is in practice that is considered the recommended approach for this types of problem?
I am looking for a cross compatible answer (Google Chrome is awesome, I agree).
Data Driven Local Storage Pattern
Just came up with that!!
The idea is to load the configuration properties based on a naming over convention configuration where all properties are derived from the targeted hostname. That is, the hostname will derive a trusted endpoint and that endpoint will load the corresponding properties to the application. These application properties will contain information that is relative at runtime. The runtime information will be supplied to the integration parts which then communicate via property iteration on the bootstrapping start up.
To keep it simple, we'll just use two properties here:
This implementation is Ember JS specific but the general idea should be portable
I am currently narrowing the scope of this question to a specific technological perspective, that is Ember JS with the following remedy that is working properly for me and hope it will help any of you out there dealing with the same issue.
Ember.Application.initializer implementation in start up
initialize: function (container, application) {
var origin = window.location.origin;
var host = window.location.hostname;
var port = window.location.port;
var configurationEndPoint = '';
//local mode
if(host === 'localhost'){
//standalone using api stub on NODEJS
if(port === '8000'){
configurationEndPoint = '/api/local';
}//standalone UI app integrating with back end application on same machine, different port
else{
configurationEndPoint = '/services/env';
}
origin += configurationEndPoint;
}else{
throw Error('Unsupported Environment!!');
}
//load the configuration from a trusted resource and store it in local storage on start up
$.get(origin,
function( data ) {
//load all configurations as key value pairs and store in localStorage for access.
configuration = data.configuration;
for(var config in configuration){
debugger;
var objectProperty = localStorage + '.' + config.toString()
objectProperty = configuration[config];
}
}
);
}
Configurable Adapter
export default DS.RESTAdapter.extend({
host: localStorage.host,
namespace: localStorage.namespace
});
No later than yesterday morning i was tackling the same issue.
Basically, you have two options:
Use localStorage/indexedDB or any other client-side persistent storage. (But you have to put config there somehow).
Render your main template (the one that gets rendered always) with a hidden where you put config JSON.
Then in your app init code you get this config and use it. Plain and simple in theory, but lets get down to nasty practice (for second option).
First, client should get config before application loads. It is not easy sometimes. e.g. user should be logged in to see config. In my case i check if i can provide config on the first request, and if not redirect user to login page. This leads us to second limitation. Once you are ready to provide config, you have to reboot app completely so that configuration code run again (at least in Angular it is necessary, as you cannot access providers after the app bootstraps).
Another constraint, the second option is useless if you use static html and cannot change it somehow on server before sending to the client.
May be a better option would be to combine both variants. This should solve some problems for returning users, but first interaction will not be very pleasant anyway. I have not tried this yet.
I am building a system that will pull 2 variables from the url (variable1.domain.com/variable2).
I cannot find any documentation showing how to do anything with subdomains in backbone. Default_url is just passed as domain.com/api. I did find something called CORS (www.enable-cors.org) that enables cross domain calls, but it says nothing about dynamic domains.
Is something like this even possible with backbone? If not, does anyone know if ember.js or other backbone-like systems have this "feature"?
This is certainly possible but not within the scope of Backbone's default behavior. Assuming that all your subdomains utilize the same router code, you could hack up a solution that might look like this:
var Router = Backbone.Router.extend({
routes: {
'*variables': 'buildRoute'
},
subdomain: function() {
// This is probably not the prettiest/best way to get the subdomain
return window.location.hostname.split('.')[0];
},
buildRoute: function(variables) {
// `variables` are all your hash variables
// e.g., in the URL http://variable1.domain.com/#variable3=apples&variable4=oranges
// `variables` here would be the string 'variable3=apples&variable4=oranges'
// so you would have to parse that string into a JSON representation, but that's trivial
// Once you have the JSON, you can do something like:
myView.render(this.subdomain(), variablesJSON);
// Your view's `render` function then has the subdomain and all the variables from the URL,
// so it can use them appropriately.
}
});
One important caveat with this approach: it works fine for users navigating to URLs themselves, but will quickly become wonky when your application needs to perform a navigate call to the Router. Backbone will navigate only to hash portion of the URL, so it will not include subdomain. You would probably have to spin up a custom navigation function that sets window.location before doing anything else.
Obviously this is probably not something Backbone is well-suited for. I'm unsure if Ember or anything else has this functionality, but I would doubt it. Subdomains are meant to be distinct areas of your site, so you might not be using them correctly.