Uibuilder with Vue-router

Hi,

I would like to start by congratulating the Node-red development team for this excelent tool
and @TotallyInformation for developing uibuilder that helps as unlock the full potential of javascript for frontend development with Node-red and building custom dashboards.

I have been experimenting with Vue, bootstrap-vue and Vue-router and everything is fine but there is a small issue when refreshing the page when im on a sub route and using history mode with vue router .
we get a 404 cannot get error

From what i read this happens with Single Page Applications because in this case the underlying express router? doesnt know about the subroute and further configuration is needed as documented in vue-router docs

The vue-router docs suggest using a connect-history-api-fallback middleware to fix this but i dont know how to implement this.

Is this something that can be easily fixed ?

router.js sample

import Vue from "vue";
import VueRouter from "vue-router";
import App from "../components/App";
import Home from "../components/Home";
import Table from "../components/Table";
import Chart from "../components/Chart";
import ChartVolts from "../components/ChartVolts";

//import store from './store';

Vue.use(VueRouter);

const router = new VueRouter({
  mode: "history",
   base: "/test",
  linkActiveClass: "active",
  routes: [
    {
      path: "/",
      component: Home
    },
    {
      path: "/table",
      component: Table
    },
    {
      path: "/chart",
      component: Chart
    },
    {
      path: "/chart_volts",
      component: ChartVolts
    },
	//{
	//path: '*',
	//redirect: '/'
//}
  ]
});

export default router;

1 Like

Thanks for raising this. Seems like a sensible enhancement request.

It doesn't look too hard to include this assuming the module works. But of course, it will need testing and I'll probably add it as an advanced option (off by default).

Not sure when I'll be able to do this though. I'm on hols shortly so not sure if I will have any time then. It may be a couple of weeks.

In the meantime, could I please ask that you take this post and put it into an issue on the uibuilder GitHub issues log? You may also want to learn how to install a node manually from GitHub rather than npmjs so that you can install the test version.

I did this on a test machine (somewhere - possibly at home) & it does work. but i never got around to raising a PR (sorry Julian)

Hi Julian,

I was messing around lines 425 of uibuilder.js where you apply the middleware and at some point i got it working but i 'broke' websockets :slight_smile: this is definitely beyond my limited programming skills

No rush for a fix since this more like an aesthetic issue. (not having that # in front of the routes)

ps. i dont have a github account .. maybe i can msg you after the holidays.

Thanks Andy. I can certainly recommend getting a GitHub account, it is very handy.

Otherwise, if you can give me a nudge at the start of September if you haven't heard anything before then.

The middleware section of uibuilder does need some tidying. If you look at the security branch code (on GitHub of course) you will see that things have been tidied up a bit but it is still a bit messy. That's what happens when you "evolve" your code of course :slight_smile:

Hi Julian,

I found the modifications I did to UIBuilder to permit history mode....

In file uibuilder.js...

Around line 30, add the line...

const history = require('connect-history-api-fallback')

Around line 429, modify the app.use statement to include a call to history()...

        /** Apply all of the middleware functions to the current instance url 
         * Must be applied in the right order with the most important first */
        app.use( tilib.urlJoin(node.url), history(), httpMiddleware, masterMiddleware, customStatic, masterStatic )

Hope that helps.

@UnborN would you be able to test this on your local install & feedback if it works correctly?

Hi @Steve-Mcl,

Really appreciated that you took the time to look into it .. considering the super detailed solutions you provide in your replies in the forum .. (with animated gifs and all ) :wink:
and in parallel updating cron-plus node .. i check the update and its truly amazing .. i was using it and now its even more easy.

Regarding Vue router .. i tested the changes .. and although the subroutes refresh with history() we lose connection with websockets

Maybe a different direction should be used and simply do a redirect for all subpaths back to the uibuilder node path ?
i was trying to read a few things about express but my programming skills are beyond this.
I found this if it helps .. link
In this example he has connect-history-api-fallback in the dependancies but he doesnt use it

I was messing around and I think i may have found a workaround by adding a 'fallback' ?! on all * GET requests after all the middleware gets applied, that redirects to the original base path of uibuilder.
Im not sure if this is good way to handle it but it seems to work. needs more testing

uibuilder.js

 /** Apply all of the middleware functions to the current instance url 
         * Must be applied in the right order with the most important first */
        // history({verbose: true}),
        app.use( tilib.urlJoin(node.url), httpMiddleware, masterMiddleware, customStatic, masterStatic ) 
        
        /** If enabled, allow for directory listing of the custom instance folder */
        if ( node.showfolder === true ) {
            app.use( tilib.urlJoin(node.url, 'idx'), 
            serveIndex( node.customFolder, {'icons':true, 'view':'details'} ), 
            serveStatic( node.customFolder ) 
            )
        }
        /** Make the uibuilder static common folder available */
        app.use( tilib.urlJoin(node.url, uib.commonFolderName), commonStatic )
    
        app.use( tilib.urlJoin(uib.moduleName, uib.commonFolderName), commonStatic )

        // Added line //////////////////////////////////////////////////////////////////////////////////////
        app.get( tilib.urlJoin(node.url) + '*', function (req, res) {
        console.log('node.url', node.url)
        res.redirect("/" + node.url)
        });

I haven't had any time to try testing this but I think it may remove the ability to use other actual pages? We need to be careful not to break anything existing here.

We need to find out why this is happening. Do you get a websocket connection in the first place?

Hi Julian,

Hope you had a nice holiday.

Yes. websockets works with this.

and yes .. you are right .. when im on an existing vue router subpath (that exists) and i refresh it redirects to back to base. But i thought .. tough luck .. they refreshed :wink: .. restart the vue app. I couldnt figure out a solution since we are basically on a sub sub route.

[EDIT]

To clarify
uibuilder url path : test
vue router path 1 : table
vue router path 2 : chart

if i refresh on uibuilder url path OK
if i refresh on table : with the changes redirects to uibuilder url path no more 404
if i refresh on chart: with the changes redirects to uibuilder url path
when i click on links in the Vue SPA all works OK

Hi. Yes thanks. Didn't turn my PC on once! :sunglasses:

You really want to avoid doing a reload of the page when coding a Single Page App. Since that will reset everything. So redirecting isn't a good idea at all I'm afraid. You need to handle the history in the front end and not in the Node-RED back end.

So if you try to use the HTML5 History Mode in vue router, you will always hit problems as soon as you rely on one of its pseudo URL's. Clicking such a URL is always going to reload the page even if you are re-directed back to the same root (real) URL.

In order to deal with this, you will have to make sure that your cache contains EVERYTHING that is needed to get back to the right view/template in the front-end OR that you take care to cache information in the front-end to the same effect.

Only then will it be possible to use something like Steve's suggestion.

However, even with Steve's suggestion, you will still need a method of providing an exceptions list if you want to be able to serve up >1 actual SPA with a single uibuilder instance (which you can right now). Remember that uibuilder does not force you to build an SPA! You can use it to build a more traditional interface if you want to.

The easiest approach is to avoid history mode and avoid page reloads as far as possible.

If you can fulfil the other requirements that would allow a page reload to reconstitute the SPA fully, you can already provide your own work-around by utilising the existing middleware call-out - you don't need to hack the uibuilder.js code.

Too late .. Vue is real easy and the only reason i started reading about it is because you had it as a default template in uibuilder :wink:

I was looking at a codesandbox example and i just cant get my head around it ..
how is this guy doing it in his example.

If you have time go in router.js in this example and change
base: process.env.BASE_URL to base: 'test'

now if you delete all subpaths, refresh with the little icon of the side browser and type something like test/blahblah vue router picks that up and knows it doesnt exist and redirects to test/brand

now if we type for example test/make that does exist in vue-router in actually goes to test/make
while in my tests and hacks :wink: it doesnt

I have no idea whats going on. Anyone? .. I know its unrelated to Node-red but that's the beauty of this forum. that it brings together people of so many and different background knowledge

This might work for you but it would break some other people's code as I know there are people (myself included) who use other statically defined pages, not just index.html. Because of that, Express would not be able to differentiate between a static file name and an invalid endpoint. In otherwords, this does not work for everyone. Something that is noted in the vue-router docs.

We are getting into a lot of complexity for solving a problem for just a few people. Not saying it shouldn't be done but we have to keep things in perspective. As I say, you should be able to achieve this using the existing middleware function capability of uibuilder. With that, you create a file that exports an ExpressJS middleware function. uibuilder picks it up automatically and inserts it in between the other middleware functions already in use.

A better way to achieve what you want is to create a list of endpoints that you want to handle. Rather than trying to handle all unknown endpoints. Since you are unlikely to ever have more than maybe a dozen endpoints.

You are right. I didnt think about that. You speak from experience and thinking all the angles.

True. it was more trying to solve this out of stubbornness now that anything else :wink:

I'll follow your suggestion and move this 'half-fix' code into the middleware.
This is enough for my use and i least i dont get that 404 anymore.

Thanks for your time and for developing this great tool :+1:

1 Like

That's why they pay me the big bucks ... oh :disappointed_relieved:

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.

For an example of successfully using Vue-Router with uibuilder, please see:

The answer given by @afelix is also reproduced in the WIKI:

2 Likes