I am creating a sample Polymer 3 app with a login and dashboard page. My backend is a simple JX-RS REST API. Once I get a response from the web service, I want to go to a new HTML file (lets says dashboard.html) rather than route to a different element within the same page. When I go through the
official website, I could not really understand how to proceed as I am a beginner in JS itself.
In my index.html, I have <my-login>, after the response, handlePositiveResponse is called. This is the place where I am changing the route. Please find the method below.
Following is my code:
login.js
class MyLogin extends PolymerElement {
static get template() {
return html`
<h1>Hello World..!!</h1>
<iron-form id="loginUserForm" on-iron-form-response="handlePositiveResponse" on-iron-form-error="handleNegativeResponse">
<form id="innerLoginUserForm" content-type="application/json" method="post">
<paper-input id="email" name="email" label="E-Mail Address"></paper-input>
<paper-input id="password" name="password" label="Password" type="password"></paper-input>
<paper-button disabled="{{activeRequest}}" raised id="loginButton" on-tap="submitLogin">Sign In</paper-button>
</form>
</iron-form>
`;
}
static get properties() {
return {
page: {
type: String,
reflectToAttribute: true,
observer: '_pageChanged'
},
routeData: Object,
subroute: Object
};
}
static get observers() {
return [
'_routePageChanged(routeData.page)'
];
}
_routePageChanged(page) {
// Show the corresponding page according to the route.
//
// If no page was found in the route data, page will be an empty string.
// Show 'view1' in that case. And if the page doesn't exist, show 'view404'.
if (!page) {
this.page = 'login';
} else if (['view1', 'view2', 'view3', 'my-dashboard'].indexOf(page) !== -1) {
this.page = page;
} else {
this.page = 'view404';
}
// Close a non-persistent drawer when the page & route are changed.
// if (!this.$.drawer.persistent) {
// this.$.drawer.close();
// }
}
_pageChanged(page) {
// Import the page component on demand.
//
// Note: `polymer build` doesn't like string concatenation in the import
// statement, so break it up.
switch (page) {
case 'view1':
import('./my-view1.js');
break;
case 'view2':
import('./my-view2.js');
break;
case 'view3':
import('./my-view3.js');
break;
case 'my-dashboard':
import('./my-dashboard.js');
break;
case 'view404':
import('./my-view404.js');
break;
}
}
submitLogin(e) {
var form = this.shadowRoot.querySelector('#loginUserForm');
this.shadowRoot.querySelector('#innerLoginUserForm').action = 'http://localhost:8080/PolymerJavaBackend/rest/login'
form.submit();
}
handlePositiveResponse(response) {
this._routePageChanged(null);
this._routePageChanged('my-dashboard');
console.log(response);
}
handleNegativeResponse(error) {
console.log(error);
}
Would appreciate, if you could advise, how to route to a new html page.
Is that your top-level element? Is in your top-level element, where you should have the iron-pages element with all the pages you have in your app, and where you must add the new page.
There you should have the _routePageChanged to check the route data changes. And _pageChanged to import the page.
If you want to navigate to your new page, not from menu, but when you'd get response from the server, in handlePositiveResponse(response), you can use the two-way databinding or this.set to update the route value: this.set('route.path', '/my-dashboard');
Related
I am using vercel for NextJS and this is my setup in getStaticPaths
const paths = posts.map((post) => ({
params: { player: post.player, id: post.id },
}))
return { paths, fallback: true }
When I set the fallback to true, I have got this error in vercel:
21:55:01.736 info - Generating static pages (1752/1752)
21:55:01.736 > Build error occurred 21:55:01.739 Error: Export
encountered errors on following paths: 21:55:01.739
/clip/[player]/[id]
It is ok when fallback is set to false but I really like to set fallback set to true so that pages can be updated frequently. Any help will be greatly appreciated...
Inside your /clip/[player]/[id].js file, you need to handle the fallback state when that page is being requested on-demand.
// pages/posts/[id].js
import { useRouter } from 'next/router'
function Post({ post }) {
const router = useRouter()
// If the page is not yet generated, this will be displayed
// initially until getStaticProps() finishes running
if (router.isFallback) {
return <div>Loading...</div>
}
// Render post...
}
// This function gets called at build time
export async function getStaticPaths() {
return {
// Only `/posts/1` and `/posts/2` are generated at build time
paths: [{ params: { id: '1' } }, { params: { id: '2' } }],
// Enable statically generating additional pages
// For example: `/posts/3`
fallback: true,
}
}
// This also gets called at build time
export async function getStaticProps({ params }) {
// params contains the post `id`.
// If the route is like /posts/1, then params.id is 1
const res = await fetch(`https://.../posts/${params.id}`)
const post = await res.json()
// Pass post data to the page via props
return {
props: { post },
// Re-generate the post at most once per second
// if a request comes in
revalidate: 1,
}
}
export default Post
https://nextjs.org/docs/basic-features/data-fetching#fallback-true
What I did was conditionally render my component. So, my component receives the object data and if I need to use a value from data, such as "title", I will do...
data?.title
Also, for my entire return component I will conditionally render it. For example...
{data !== undefined ? (
<div className ='main-content'>
<p> This is the content that I want rendered if data is defined </p>
</div>
) : (
<div className = 'fallback-content'>
<p> This shows if data == undefined </p>
</div>
)
I want to bookmark some pages in my application. I am adding the pages by their name and url into bookmark. It is working fine. When i click the bookmark button, it is filled with color by showing that it is bookmarked. There are many pages.
I want to use mixins for toggling that bookmark button( if it is bookmarked or not).So, i can use that all other pages. I need suggestions on this mixin part.
Current code of button and method
<vu-button
v-if="bookmark"
size="reduced"
color="secondary"
class="blue"
#click="addToBookmark"
>
<svg-icon :name="bookmarker"/>
</vu-button>
method where i am adding that page as a bookmark
addToBookmark() {
this.bookmarkSelected = !this.bookmarkSelected;
if (this.bookmarkSelected) {
this.bookmarker = 'BookmarkFilled';
this.addUserFavourites({
name: this.getFavourites,
url: this.$route.path
});
} else {
this.bookmarker = 'Bookmark';
}
}
Any suggestion regarding mixin for toggling button will be helpful or how to create mixin for changing color to show added bookmark?
I think you need to expand your logic here. I'm assuming that you are saving bookmarks through an API, so, I can advise you the following tips:
Create an end-point called pageBookmarked(this.$route.path) that returns an boolean, in this way you can check if the current page it's bookmarked or not.
Create another end-point called addToBookmarks({ data: extraData, url: this.$route.path }).
Create a last end-point called removeFromBookmarks(this.$route.path) to remove current page from bookmarks.
So, now you can create a unique Vue component to manage bookmars:
<template>
<div>
<vu-button
v-if="!loading"
size="reduced"
color="secondary"
class="blue"
:class="{ 'bookmark-active': bookmarked }"
#click="addToBookmark"
>
<svg-icon :name="bookmarked ? 'BookmarkFilled' : 'Bookmark'"/>
</vu-button>
<span v-else>Loading</div>
</div>
</template>
<script>
export default {
data() {
return {
loading: true,
bookmarked: false
};
},
async mounted() {
// Check if page it's already bookmarked
this.loading = true;
this.bookmarked = await this.$api.pageBookmarked(this.$route.path);
this.loading = false;
},
methods: {
async addToBookmark() {
this.loading = true;
if (this.bookmarked) {
this.bookmarked = false;
await this.$api.removeFromBookmarks(this.$route.path);
} else {
this.bookmarked = true;
await this.$api.addToBookmarks({ data: { name: "My Bookmark" }, url: this.$route.path })
}
this.loading = false;
}
}
};
</script>
I don't think you need to create a mixin for this, just register this component globally and you can access it from every component in your project.
I hope it can helps 😄
I have a navbar where I only show certain menu items based off the user's role.
Here is the HTML (I removed all the menu items except one to simplify):
<v-speed-dial class="speed-dial-container contextual-text-menu" v-if="user && user.emailVerified" fixed top right
direction="bottom" transition="slide-y-transition">
<v-icon v-if="checkAuthorization(['superAdmin', 'admin', 'pastor', 'member']) === true" class="mt-2"
#click="sendComponent({ component: 'Dashboard' })">$admin</v-icon>
</v-speed-dial>
My method:
async checkAuthorization(permissions) {
if (permissions.length > 0) {
const temp = await this.$store.dispatch('UserData/isAuthorized', permissions)
return temp
}
},
Vuex store:
isAuthorized({
state
}, permissions) {
const userRoles = state.roles
if (permissions && userRoles) {
const found = userRoles.some(r => permissions.includes(r))
return found
}
},
All of my console logs show the correct values but the HTML is not responding accordingly.
Example: in this line of code checkAuthorization(['superAdmin', 'admin', 'pastor', 'member']) === true I added 'member' and I am logged in as a user that ONLY has the 'member' role. When looking through the console logs everything returns true so I should see this menu item but it does not show.
As someone pointed out in the comments, checkAuthorization is an async function and will return a Promise, so you cannot check for promise === true.
That aside, I would change isAuthorized to be a vuex getter and not an action, e.g.
getters: {
// ...
isAuthorized: (state) => (permissions) => {
if (permissions && state.roles) {
return state.roles.some(r => permissions.includes(r))
}
return false;
}
}
And update checkAuthorization to not return a promise e.g.
function checkAuthorization(permissions) {
if (permissions.length > 0) {
return this.$store.getters.isAuthorized(permissions);
}
return false;
}
What I usually do :
I add another user state as Unknown and make it the default state.
In main main.js (or main.ts) I call state.initialize(), which determines user's state.
And, the key thing is to use navigation guards. Instead of checking routing guards on router-link (or url or anywhere in this step), you should define it in the router. There is a router.beforeEach function you can use, so that you can check if user is authorized to use that route, and redirect the user to 401 page if the user don't have permission.
https://router.vuejs.org/guide/advanced/navigation-guards.html
I have the following sequence happening:
Main screen
Loading screen
Results screen
On homepage, when someone clicks a button, I send them to the loading screen, thru:
this.$router.push({path: "/loading"});
And once their task finishes, they are sent to the results screen via
this.$router.push({path: "/results/xxxx"});
The problem is, usually they want to go from results back to the main screen, but when they click back, they're sent to loading again which sends them back to results, so they're stuck in an infinite loop & unable to go back to main screen.
Any ideas how to fix this? I'd ideally like if there was an option like:
this.$router.push({path: "/loading", addToHistory: false});
which would send them to the route without adding it to history.
This should have a real answer using this.$router.replace:
// On login page
// Use 'push' to go to the loading page.
// This will add the login page to the history stack.
this.$router.push({path: "/loading"});
// Wait for tasks to finish
// Use 'replace' to go to the results page.
// This will not add '/loading' to the history stack.
this.$router.replace({path: "/results/xxxx"});
For further reading the Vue Router is using History.pushState() and History.replaceState() behind the scenes.
There is a perfect way to handle this situation
You can use in-component guard to control the route in granule level
Make the following changes in your code
In main screen component
Add this beofreRouteLeave guard in component options, before leaving to 'result screen' you are setting the route to go only
through loading screen
beforeRouteLeave(to, from, next) {
if (to.path == "/result") {
next('/loading')
}
next();
},
In loading screen component
If the route go backs from result to loading then , it should not land
here and directly jump to main screen
beforeRouteEnter(to, from, next) {
if (from.path == "/result") {
next('/main')
}
next();
},
In loading screen, The beforeRouteEnter guard does NOT have access to
this, because the guard is called before the navigation is confirmed,
thus the new entering component has not even been created yet. So taking the advantage of this, you won't get the infinite calls fired when routing from results screen
In result screen component
if you use go back then it should not land in loading and directly
jump to main screen
beforeRouteLeave(to, from, next) {
if (to.path == "/loading") {
next('/')
}
next();
},
I have just created small vue application to reproduce the same issue. It works in my local as per your question. Hope it resolves your issue as well.
I guess router.replace is the way to go - but still some lines of thought (untested):
Basically on $router change it renders the loading-component until it emits load:stop, then it renders the router-view
import { Vue, Component, Watch, Prop } from "vue-property-decorator";
#Component<RouteLoader>({
render(h){
const rv = (this.$slots.default || [])
.find(
child => child.componentOptions
//#ts-ignore
&& child.componentOptions.Ctor.extendedOptions.name === "RouterView"
)
if(rv === undefined)
throw new Error("RouterView is missing - add <router-view> to default slot")
const loader = (this.$slots.default || [])
.find(
child => child.componentOptions
//#ts-ignore
&& child.componentOptions.Ctor.extendedOptions.name === this.loader
)
if(loader === undefined)
throw new Error("LoaderView is missing - add <loader-view> to default slot")
const _vm = this
const loaderNode = loader.componentOptions && h(
loader.componentOptions.Ctor,
{
on: {
// "load:start": () => this.loading = true,
"load:stop": () => _vm.loading = false
},
props: loader.componentOptions.propsData,
//#ts-ignore
attrs: loader.data.attrs
}
)
return this.loading && loaderNode || rv
}
})
export default class RouteLoader extends Vue {
loading: boolean = false
#Prop({default: "LoaderView"}) readonly loader!: string
#Watch("$route")
loads(nRoute: string, oRoute: string){
this.loading = true
}
}
#Component<Loader>({
name: "LoaderView",
async mounted(){
await console.log("async call")
this.$emit("load:stop")
// this.$destroy()
}
})
export class Loader extends Vue {}
This is a tough call considering how little we know about what's occurring in your loading route.
But...
I've never had a need to build a loading route, only ever loading component(s) that gets rendered on multiple routes during init/data gathering stage.
One argument for not having a loading route would be that a user could potentially navigate directly to this URL (accidentally) and then it seems like it wouldn't have enough context to know where to send the user or what action to take. Though this could mean that it falls through to an error route at this point. Overall, not a great experience.
Another is that if you simplify your routes, navigation between routes becomes much simpler and behaves as expected/desired without the use of $router.replace.
I understand this doesn't solve the question in the way you're asking. But I'd suggest rethinking this loading route.
App
<shell>
<router-view></router-view>
</shell>
const routes = [
{ path: '/', component: Main },
{ path: '/results', component: Results }
]
const router = new VueRouter({
routes,
})
const app = new Vue({
router
}).$mount('#app')
Shell
<div>
<header>
<nav>...</nav>
</header>
<main>
<slot></slot>
</main>
</div>
Main Page
<section>
<form>...</form>
</section>
{
methods: {
onSubmit() {
// ...
this.$router.push('/results')
}
}
}
Results Page
<section>
<error v-if="error" :error="error" />
<results v-if="!error" :loading="loading" :results="results" />
<loading v-if="loading" :percentage="loadingPercentage" />
</section>
{
components: {
error: Error,
results: Results,
},
data() {
return {
loading: false,
error: null,
results: null,
}
},
created () {
this.fetchData()
},
watch: {
'$route': 'fetchData'
},
methods: {
fetchData () {
this.error = this.results = null
this.loading = true
getResults((err, results) => {
this.loading = false
if (err) {
this.error = err.toString()
} else {
this.results = results
}
})
}
}
}
Results Component
Basically the exact same results component you already have, but if loading is true, or if results is null, however you prefer, you can create a fake dataset to iterate over and create skeleton versions, if you'd like to. Otherwise, you can just keep things the way you have it.
Another option is to use the History API.
Once you are in the Results screen, you can utilize the ReplaceState to replace the URL in history of the browser.
This can be done with the beforeEach hook of the router.
What you need to do is you must save a variable globally or in localStorage in the loading component when the data is loaded (before redirecting to the results component):
export default {
name: "results",
...
importantTask() {
// do the important work...
localStorage.setItem('DATA_LOADED', JSON.stringify(true));
this.$router.push({path: "/results/xxxx"});
}
}
And then you should check for this variable in the beforeEach hook and skip to the correct component:
// router.js...
router.beforeEach((to, from, next) => {
const dataLoaded = JSON.parse(localStorage.getItem('DATA_LOADED'));
if (to.name === "loading" && dataLoaded)
{
if (from.name === "results")
{
next({ name: "main"} );
}
if (from.name === "main")
{
next({ name: "results"} );
}
}
next();
});
Also, do remember to reset the value to false in your main component when the query button is clicked (before routing to the loading component):
export default {
name: "main",
...
queryButtonClicked() {
localStorage.setItem('DATA_LOADED', JSON.stringify(false));
this.$router.push({path: "/loading"});
}
}
Your loading screen should not be controlled by vue-router at all.
The best option is to use a modal/overlay for your loading screen, controlled by javascript. There are lots of examples around for vue. If you cant find anything then vue-bootstrap has some easy examples to implement.
We are integrating the Okta Sign-in Widget into our React-based webapp.
The example snippet:
var oktaSignIn = new OktaSignIn({baseUrl: baseUrl});
oktaSignIn.renderEl(...)
Works fine for us when rendering the widget for the first time, but after the user logs in and logs out again, the webapp renders the login component a second time and would attempt to execute the renderEl again to render the widget. This causes the following exception to be thrown:
Backbone.history has already been started
I have created this jsfiddle to demonstrate the problem. It just instantiates a signin widget twice (the second time after a wait). You can see that the second invocation causes the exception to be thrown.
https://jsfiddle.net/nudwcroo/6/
At the moment my workaround is to reload the entire webapp when going to the login component but that is undesirable for a single page app.
Is this a known issue? Is there any way to initialise the sign-in widget twice in a single javascript session?
Since the widget can only be instantiated once per page, it is best to hide/show the element for all Single Page Applications.
<div id="okta-login-container"></div>
<script type="text/javascript">
var oktaSignIn = new OktaSignIn(/* config */);
oktaSignIn.renderEl(
{ el: '#okta-login-container' },
function (res) {
if (res.status === 'SUCCESS') {
// Hide element
$("#okta-login-container").hide();
}
}
);
</script>
When you create your logout() function, make sure to show() the element instead of rendering it again.
function logout() {
$('#okta-login-container').show();
// Do more logic
}
For those experiencing similar problems after following the Okta example provided here: (https://github.com/okta/samples-js-react/blob/master/custom-login/src/Login.jsx)
The problem is with attempting to initialize the widget multiple times within a single page application. I fixed this by only initializing the widget once at the App level, and then rendering it and removing it from the DOM when a child component mounts/unmounts.
Example:
//App.js
class App extends Component {
constructor() {
super()
this.signIn = new OktaSignIn({...})
}
render() {
return <SignInPage widget={this.signIn} />
}
}
--
//SignInPage.js
...
componentDidMount() {
let { redirectUri } = this.state
let { widget } = this.props
widget.renderEl(
{ el: '#sign-in-widget' },
(response) => {
response.session.setCookieAndRedirect(redirectUri)
},
(error) => {
throw error;
},
);
}
componentWillUnmount() {
let { widget } = this.props
widget.remove()
}
render() {
return <div id="sign-in-widget"/></div>
}