Preact-Router - Handle routes from sub directory - router

I'm currently creating a SPA that can be included and run from any route.
Currently any <Link /> component I create redirects the client back to the root of the domain it's on plus the intended path.
In react-router there's a property to set a starting base path; basename.
This doesn't seem to be present in preact-router and I'd really rather not switch to react-router as it's significantly larger and I won't be using many of the additional features.
A simple example of the routes:
<Router>
<Route
path="/"
component={Home}
/>
<Route
path="/:slug"
component={Merchant}
/>
</Router>
I've seen a couple of post around the internet implying this is possible but with such little documentation it's a little tricky to piece together.
Any help is much appreciated.
Thanks.

I've ended up wrapping the preact-router Link and Router with my own components. From there I can prefix the path property value with my apps base route, e.g:
<MyRoute
path="/"
component={Home}
/>
Then somewhere within <MyRoute />:
const route = 'my/app/base/path';
let result = (route || '') + this.props.path;
result = result.replace(/([^:]\/)\/+/g, '$1');
Then render the preact-router default component with the result value, <Route path={result} />

If you're looking for hash routing, here it is: https://github.com/developit/preact-router#custom-history.

jhdevuk's Answer pointed me to the right direction.
The following Router class will do the trick (this is in TypeScript):
class SubfolderRouter extends preactRouter.Router {
render(props: preactRouter.RouterProps, state: any) {
if (state.url.indexOf(MY_FOLDER) == 0) {
state = {
...state,
url: state.url.substr(MY_FOLDER.length),
};
}
return super.render(props, state);
}
}
If your app lives in the folder const MY_FOLDER = "/myfolder", then this Router will ignore the folder of the URL. If the user navigates to
/myfolder/home/index
then the Router will look up the URL /home/index, because this is the actual route.

Related

How to redirect in React Router without changing the url

How can I implement maybeGoToBar such that - depending on the name - I show the Foo component or the Bar component without changing the url?
function maybeGoToBar(nextState, replace, callback) {
const name = nextState.params.name;
if (name === "Bob") {
// do something to route to /bar
// but keep showing /bob/foo as the current location
// in the browser address bar
}
callback();
}
<Route path=":name/foo" onEnter={maybeGoToBar} component={Foo} >
<Route path="bar" component={Bar} >
You need to use a memory history.
Link

How to start XML preprocessor without return a view component

I'm trying to create a launchpad for a set of applications that we use here. One of my problems is that I need to add different tiles in a tile container (slide,custom,standard, etc) and what I think that may be a solution is use XML templating to do that. What I want to achieve is something like that:
<TileContainer id="tileList"
allowAdd="true"
tileDelete="onDelete"
tiles="{path:'Atalhos>/' ,sorter:{path:'Atalhos>TileText',group:false}}">
<template:if test="{path:'Atalhos>TileCode', operator:'EQ',value1:'teste1'}">
<template:then>
<core:Fragment fragmentName="pelissari.soficom.launchpad.view.StandardTile" type="XML"/>
</template:then>
<template:else>
<core:Fragment fragmentName="pelissari.soficom.launchpad.view.StandardTile" type="XML"/>
</template:else>
</template:if>
</TileContainer>
but the problem is that I'm having this error when I try to do that.
UIComponent-dbg.js:52 Uncaught Error: failed to load
'http://schemas/sap/com/sapui5/extension/sap/ui/core/template/1/if.js'
from
resources/http://schemas/sap/com/sapui5/extension/sap/ui/core/template/1/if.js:
404 - Not found
I know that I need to start the preprocessor to use preprocessing instructions but all the examples that I found makes me more confuse that I was before.
My project is based on the sapui5 tutorial "WalkThrough where I have a component that starts an app view configured in the manifest and this view is navigate to the launchpad view by routing configuration again in the mainfest. all the examples create a view in the component CreateComponent function or in some controller function that loads other view. I just need to start the preprocessor for the list of tiles that I load from the entity set "/TileSet".
I found another way to do what I want. Now I'm using factory function to create the tiles as I need.
tileFactory: function(sId, oContext) {
var atalho = oContext.getProperty(oContext.sPath)
var oUIControl = null;
if (atalho.TileCode == 'teste2') {
oUIControl = new sap.m.StandardTile(sId, {
title: atalho.TileText
});
oUIControl.addStyleClass('tileSize3');
} else {
oUIControl = new sap.m.StandardTile(sId, {
title: atalho.TileText
});
oUIControl.addStyleClass('tileSize1');
}
oUIControl.attachPress(this.onPress, this);
oUIControl.addStyleClass('tile');
return oUIControl;
}
<Page id="tileGroup" showHeader="true"
content="{path:'Atalhos>/' ,sorter:{path:'Atalhos>TileOrder',group:false},factory:'.tileFactory'}">

Changing the document title in React?

I'm trying to update the title of the document in a React app. I have very simple needs for this. The title is essentially used to put the Total component on display even when you're on a different tab.
This was my first instinct:
const React = require('react');
export default class Total extends React.Component {
shouldComponentUpdate(nextProps) {
//otherstuff
document.title = this.props.total.toString();
console.log("Document title: ", document.title);
return true;
}
render() {
document.title = this.props.total;
return (
<div className="text-center">
<h1>{this.props.total}</h1>
</div>
);
}
}
I thought this would just update the document.title every time this component was rendered, but it doesn't appear to do anything.
Not sure what I'm missing here. Probably something to do with how React runs this function - maybe somewhere that the document variable isn't available?
EDIT:
I'm starting a bounty for this question, as I still haven't found any solution. I've updated my code to a more recent version.
A weird development is that the console.log does print out the title I'm looking for. But for some reason, the actual title in the tab isn't updating. This issue is the same across Chrome, Safari, and Firefox.
I now use react-helmet for this purpose, as it allows to customize different meta tags and links, and it also supports SSR.
import { Helmet } from 'react-helmet'
const Total = () => (
<div className="text-center">
<Helmet>
<meta charSet="utf-8" />
<title>{this.props.total}</title>
</Helmet>
<h1>{this.props.total}</h1>
</div>
)
Original answer: there's actually a package by gaeron for this purpose, but in a declarative way:
import React, { Component } from 'react'
import DocumentTitle from 'react-document-title'
export default class Total extends Component {
render () {
return (
<DocumentTitle title={this.props.total}>
<div className='text-center'>
<h1>{this.props.total}</h1>
</div>
</DocumentTitle>
)
}
}
Inside your componentDidMount() function in App.js (or wherever), simply have:
componentDidMount() {
document.title = "Amazing Page";
}
The reason this works is anywhere in your react project you have access to the Js global scope. Go ahead and type window in your sites console. Basically everything there you will be able to access in your React project.
I think webpack-dev-server runs in an iframe mode by default:
https://webpack.github.io/docs/webpack-dev-server.html#iframe-mode
So that might be why your attempts to set the title are failing. Try setting the inline option to true on webpack-dev-server, if you haven't already.
If the react-document-title package isn't working for you, the quick'n'dirty way to do that would be in a lifecycle method, probably both componentDidMount and componentWillReceiveProps (you can read more about those here):
So you would do something like:
const React = require('react');
export default class Total extends React.Component {
// gets called whenever new props are assigned to the component
// but NOT during the initial mount/render
componentWillReceiveProps(nextProps) {
document.title = this.props.total;
}
// gets called during the initial mount/render
componentDidMount() {
document.title = this.props.total;
}
render() {
return (
<div className="text-center">
<h1>{this.props.total}</h1>
</div>
);
}
}
There is a better way of dynamically changing document title with react-helmet package.
As a matter of fact you can dynamically change anything inside <head> tag using react-helmet from inside your component.
const componentA = (props) => {
return (
<div>
<Helmet>
<title>Your dynamic document/page Title</title>
<meta name="description" content="Helmet application" />
</Helmet>
.....other component content
);
}
To change title, meta tags and favicon dynamically at run time react-helmet provides a simple solution. You can also do this in componentDidMount using the standard document interface. In the example below I am using the same code for multiple sites, so helmet is looking for favicon and title from an environment variable
import { Helmet } from "react-helmet";
import { getAppStyles } from '../relative-path';
import { env } from '../relative-path';
<Helmet>
<meta charSet="utf-8" />
<title>{pageTitle[env.app.NAME].title}</title>
<link rel="shortcut icon" href={appStyles.favicon} />
</Helmet>

Trouble with helpers and db calls

Just started to learn a Meteor and stuck with a silly problem. I have a collection "Images" and i trying to get one random image from her. But in the browser's console it's says that "Cannot read property 'url' of undefined", but if type a db's "findOne" method in the console there is a record that i wanted.
A client code:
Template.main.helpers({
img: function () {
return Images.findOne({rand: {$gte: Math.random()}}).url;
}
});
Collection:
Images = Meteor.Collection('images');
On a server's side i've got simple fixtures for initial tests
if(Images.find().count() === 0){
Images.insert({url: "http://domain.com/test1.jpg", rand: Math.random()});
Images.insert({url: "http://domain.com/test2.jpg", rand: Math.random()});
Images.insert({url: "http://domain.com/test3.jpg", rand: Math.random()});
}
And simple template:
<head>
<title>Test</title>
</head>
<body>
{{> main}}
</body>
<template name="main">
{{img}}
</template>
P.S. I'm working under the Win 8.
First, I'm going to assume that the images are published to the client. You should verify that Images.find().count() > 0 is true in your browser console (not in your template code).
Next, you should read about how to add guards to your template code to fix the error you are seeing. This article should explain what you need to know.
Finally, unless you actually need rand on your documents, there are better ways to accomplish a random selection. Your question says you are looking for one image, but it's possible that 1 or 0 could be returned with your code. Instead you could use the built-in Random package. Give this a try:
Template.main.helpers({
img: function() {
var image = Random.choice(Images.find().fetch());
return image && image.url;
}
});
Although, I think it makes more sense to return the image object:
Template.main.helpers({
image: function() {
return Random.choice(Images.find().fetch());
}
});
And then select the url in your template (no guards are needed in this case):
<template name="main">
<img src="{{image.url}}">
</template>
You need to be careful with your assumptions when you load a template. When your web page has loaded on the web browser, it won't necessarily have any data in it (your Images.findOne() query could return null)
You just need to take account of this possibility
var image = Images.findOne({rand: {$gte: Math.random()}});
return image && image.url

Jira-Servlet-Plugin using TemplateRenderer

I'm trying to develop a simple HTTP-Servlet, to render a Velocity Template.
My Servlet:
Map<String, Object> context = Maps.newHashMap();
resp.setContentType("text/html;charset=utf-8");
templateRenderer.render("/templates/test/input.vm", context, httpRespnse.getWriter());
atlassian-plugin.xml
<webwork1 key="newactions1" name="New actions1" class="java.lang.Object">
<actions>
<action name="test.ActionAlpha" alias="FirstNewAction">
<view name="success">/templates/test/input.vm</view>
<view name="error">/templates/test/input.vm</view>
<view name="input">/templates/test/input.vm</view>
</action>
</actions>
</webwork1>
(See: https://developer.atlassian.com/display/JIRADEV/Plugin+Tutorial+-+Internationalising+Your+Plugin)
Everything works fine so far, but after the page is rendered the menu-bar on the left is missing (The other Menu-webitems in the websection)
If I call the URL in my browser by hand, with "!default" behind the action-name, the sidebar is displayed.
http://host:port/jira/secure/FirstNewAction!default.jspa
But if I call the URL without "!default" the output is the same as the servlet produces. Is there a possibility for the TemplateRenderer to add the "!default" term?
I guess
Map<String, Object> context = Maps.newHashMap();
templateRenderer.render("/templates/test/input.vm", context, httpRespnse.getWriter());
passes an empty map to the render method, if you omit the context parameter the default context should be passed which includes the webResources. Another idea is to add something like
meta name="decorator" content="atl.admin"
to the heads section