routing of nested pages in Gatsby - url

I haven't found similar topic corresponding to the challange I have.
I have a gatsby project which creates kind of management portal of the projects.
I try to figure out how can I create properly routing among pages.
I have a main page which display list of projects. It should be available in URL path "/projects".
Every element of the list suppose to be a link to project's page. I would like to have it in path "/project-number" where number should be dynamic depends on of the project like e.g."/project-1", "/project-2".
Every project should contain 2 sub-pages: map and description with the path e.g."/project-1/map".
Both map and description sub-page uses the same Gatsby's Layout and different content depending on the displaying project.
The main page index.js where the path suppose to look "/projects":
import React from "react"
import Layout from "../components/layout"
import SEO from "../components/seo"
import ListOfContent from "../components/listOfContent";
import projects from "../data/projects";
const IndexPage = () => (
<Layout pageInfo={{pageName: "index"}}>
<SEO title="Home" />
<ListOfContent projects={projects}/>
</Layout>
);
export default IndexPage
listOfContent.js is a component injected in index.js
const ListOfContent = props => {
const { items } = props.projects;
const listOfProjects = items.map( Item =>
<ListGroup.Item key={Item.id} >
<Image src={Item.customerLogo} className={style.CustomerLogo} />
<Link to={`/${Item.projectUrlName}/projectPage`}>{Item.projectName}</Link>
</ListGroup.Item>
);
return (
<ListGroup>
{listOfProjects}
</ListGroup>
)
};
projects.js contains array of projects details
const projects = {
items: [
{
id: uuidv4(),
projectUrlName: "project-1",
projectName: "Project-1",
projectPicture: require('../images/building.png'),
projectDescription: "{customer_logo}",
customer: "Name",
projectPosition: "//www.arcgis.com/apps/Embed/index.html?webmap=...",
},
{...},
],
};
export default projects;
layout.js
const Layout = ({ children, pageInfo, data }) => {
const {items} = projects;
return (
<>
<Container fluid className="px-0 main">
<Navbar pageInfo={pageInfo} items={items}/>
<Row noGutters>
<Col>
<Container className="mt-5">
<main>{children}</main>
</Container>
</Col>
</Row>
</Container>
</>
)}
/>
)};
export default Layout
navbar.js contains dropdown with links to map and description sub-pages
<Navbar.Collapse id="basic-navbar-nav">
<Nav className="mr-auto" activeKey={props.pageInfo && pageName}>
<NavDropdown title={projectName} id="collapsible-nav-dropdown">
<NavDropdown.Item><Link to="map" activeClassName="active">map</Link></NavDropdown.Item>
<NavDropdown.Item><Link to="description" activeClassName="active">description</Link></NavDropdown.Item>
</Nav>
description.js is a sub-page of the a single project where the path suppose to look "/project-1/map"
const DescriptionPage = () => (
<Layout pageInfo={{ pageName: "description" }}>
<SEO title="Project description" />
<h1>project name</h1>
<p>description</p>
</Layout>
);
export default DescriptionPage
My challanges are:
How to create dynamic path which will include number of the project "/project-number"
How can I use components description.js and map.js (similar to description.js) as a templates where would be dynamicaly incjected content from projects.js. In other words, I want to have only one map.js and one description.js in the folder src/pages/map.js which will show different content depends on chosen project on the index.js.
map.js and description.js have to have path "/project-1/map", "/project-2/map", etc.

Related

Vue3 with ASP.NET MVC: Props not being passed in

I have an ASP.NET MVC application which I've inherited. The most recent new bits of functionality use custom Vue2 components. Each MVC view is effectively a Single Page Application as I understand it. Each view has its own index.js file e.g.
import Vue from "vue"
window.Vue = Vue
import Test from '#/components/test'
const vm = new Vue({
el: "#test",
components: {
Test
},
data() {
return {}
}
})
The top level component is a single file component e.g. Test.vue:
<template>
<div>
<h1>{{ message }}</h1>
</div>
</template>
<script>
export default {
name: 'Test',
props: {
message: {
type: String,
default: 'This has not worked'
}
}
}
</script>
The MVC view itself looks like:
#{
ViewBag.Title = "Test";
Layout = "~/Views/Shared/_MentorNet.cshtml";
}
<div id="test" v-cloak>
<test message="This has worked"></test>
</div>
#section scripts
{
<script src="#Url.Content("~/dist/js/test.js")" defer></script>
}
The /dist/js/test.js file is created as a result of running Webpack with vue-loader.
With Vue2 this works fine. In this example the text "This has worked" appears when the controller returns the view.
However I'm trying to upgrade to Vue3 and this isn't working. The index.js file now looks like:
import { createApp } from "vue"
import Test from '#/views/admin/test'
const app = createApp(Test);
app.mount("#test");
The component and the view are the same. The component is displayed but the props are not passed through - the view returns the text "This has not worked".
I've looked at the Vue2 migration guide and can't see any breaking changes that would affect this. Can anyone help please?
I've got the answer thanks to a response on the Vue Land Discord channel.
The problem was with the code in the index.js file:
const app = createApp(Test);
app.mount("#test");
When passing a component with a template/render function to createApp it completely overwrites the content of the mount target, in this case
<div id="test">
<test message="This has worked"></test>
</div>
so the
<test message="This has worked"></test>
isn't used. The correct way to do it is either to pass the props as the second argument of create App:
const app = createApp(Test, {
message: "This has worked"
});
or, and the way I'm going to be doing it:
const app = createApp({
components: { Test }
});

Quasar: how to display an image when using q-file to pick the image?

New to Quasar & Vue.
I am using q-file which allow pick file & drag to drop file.
However, how do i display the image for preview?
Q-uploader seems work but how do i change the ui of it?
Link to component from Quasar:
https://quasar.dev/vue-components/file-picker
In you template define a q-file and q-img element. Add a #change handler and updateFile function. The q-img will contain the picture you selected.
import { ref } from 'vue';
import { defineComponent } from 'vue';
<script lang="ts">
export default defineComponent({
name: 'Component Name',
components: {},
setup () {
const image = ref(null);
const image1Url = ref('')
return {
image,
image1Url,
updateFile() {
imageUrl.value = URL.createObjectURL(image.value);
}
}
}
})
</script>
<div>
<q-file
v-model="image"
label="Pick one file"
filled
style="max-width: 300px"
#change="updateFile()"
/>
</div>
<div>
<q-img
:src="imageUrl"
spinner-color="white"
style="height: 140px; max-width: 150px"
/>
</div>
Create an #change hook on q-file:
In the script set the url from the file passed in from q-file:

Should a component adapter wrap just the component or should it also wrap <Field>

Best concept for wrapping react-final-form components
Background
I'm using react-native and will probably start using react-final-form as soon as I have wrapped my head around it. I will build a component library based on react-native-paper.
All examples I've seen on wrapping 3:rd party components for use as react-final-form fields (i.e. <Field>), does not wrap the actual field component. Instead the wrapped component is injected via the component property of the Field component.
If I'm doing all the work wrapping the components, why not go all the way? Since I haven't found any examples of the "complete wrapping", I'm kind of worried that it's not a good idea.
Both solutions seems to work fine, btw. The code below is psuedo:ish.
Only wrapping the component
export default function App() {
const CheckboxAdapter = ({input: {onChange, value}, meta}) => (
<Checkbox
status={value}
onPress={() => {
onChange(value === 'checked' ? 'unchecked' : 'checked');
}}
errorText={meta.touched ? meta.error : ''}
/>
);
return (
<Form
..
render={({handleSubmit}) => {
return (
..
<Field
name="myFieldname"
component={CheckboxAdapter}
/>
)
}
/>
)
}
Wrapping the component inside of the <Field> component
export default function App() {
const MyCheckbox = ({name}) => (
<Field
name={name}
component={({input: {onChange, value}, meta, children, ...rest}) => (
<Checkbox
status={value}
onPress={() => {
onChange(value === 'checked' ? 'unchecked' : 'checked');
}}
errorText={meta.touched ? meta.error : ''}
/>
)};
/>
);
return (
<Form
..
render={({handleSubmit}) => {
return (
..
<MyCheckbox
name="myFieldname"
/>
)
}
/>
)
}
Not much interest in this question. I ended up wrapping the <Field>as well. This gives much more readable form code.
import React from 'react';
import TextInput from '../components/TextInput';
import {Field} from 'react-final-form';
const TextInputAdapter = ({input, meta, ...rest}) => {
const onChangeText = value => {
input.onChange(value);
};
return (
<TextInput
{...input}
{...rest}
onChangeText={onChangeText}
errorText={meta.touched ? meta.error : ''}
/>
);
};
const TextInputField = ({...rest}) => {
return <Field component={TextInputAdapter} {...rest} />;
};
export default TextInputField;

SAPUI5: How to add custom functionality to column menu of ui.table?

I'd like to give my users the chance to reformat table cells after data has been loaded into the table. I thought a neat way to do this, is adding the functionality to the column menu. So when clicking on a table column, the menu appends with the standard "filtering, sorting" but also has a row called "formatting" which provides a few options (E.g. format a numeric cell from 55555,55 to 55.555,55)
Unfortunately I was unable to find a way to add a new row to my column menu. My sorting and filtering is added like this:
oTable.bindColumns("/columns", function(index, context) {
var columnName = context.getObject().columnId;
return new sap.ui.table.Column({
label:columnName,
template: columnName,
sortProperty: columnName,
filterProperty: columnName,
});
});
How do I add new lines/functionalities to the column menu?
Update
This is how my table looks like in xml view:
<table:Table id="uploadData" visibleRowCountMode="Auto" rowSelectionChange="tableRowSelectionChange" enableCellFilter="true" fixedColumnCount="0" enableBusyIndicator="true" customData="{Type: 'sap.ui.core.CustomData', key:'table-style-type', value:'custom-styled', writeToDom: true }">
<table:extension>
<m:Button icon="sap-icon://download" press="onDataExportXLS" align="Right" />
</table:extension>
<table:columns>
<!-- Columns dynamically created in controller -->
</table:columns>
<table:rows>
<!-- Rows created in controller -->
</table:rows>
</table:Table>
The sap.ui.table.Column has an aggregation called menu just for this. It accepts any custom menu items which are shown when clicking on a Column. This aggregation takes sap.ui.unified.Menu control.
In the select function of the MenuItem aggregation, you can write the function to handle what needs to be done when the menu item is selected
sap.ui.table.Column documentation
sap.ui.unified.Menu documentation
Check it in this sample here and its code here, click on the Quantity column and you will see a My custom menu entry
A snippet of the code here in XML,
<Column id="quantity" width="6rem" hAlign="End" sortProperty="Quantity">
<m:Label text="Quantity" />
<template>
<m:Label text="{ path: 'Quantity', type: 'sap.ui.model.type.Integer'}" />
</template>
<menu>
<u:Menu ariaLabelledBy="quantity">
<u:items>
<u:MenuItem text="My custom menu entry" select="onQuantityCustomItemSelect" />
<u:MenuItem text="Sort" select="onQuantitySort" icon="sap-icon://sort" />
</u:items>
</u:Menu>
</menu>
</Column>
The code in JS,
var oColumn = new sap.ui.table.Column({
label: "Some column Name",
menu: new sap.ui.unified.Menu({
items: [new sap.ui.unified.MenuItem({
text: "My custom menu",
select: function (oEvent) {
pressEventOnItem(oEvent);
})
]
})
})

You have included the Google Maps JavaScript API multiple times on this page

how can I avoid “You have included the Google Maps JavaScript API multiple times on this page. This may cause unexpected errors.” if I am using google-map-react to display the map and react-places-autocomplete in another component to get the address and coordinates ?
//LocationMapPage component that displays the map box and pass the props to it
class LocationMapPage extends Component {
render() {
let {latLng,name,address} = this.props.location;
return (
<MapBox lat={latLng.lat} lng={latLng.lng} name={name} address={address}/>
)
}
}
//MapBox component
import React from "react";
import GoogleMapReact from 'google-map-react';
import apiKey from "../../configureMap";
const Marker = () => <i className="fa fa-map-marker fa-2x text-danger" />
const MapBox = ({lat,lng, name, address}) => {
const center = [lat,lng];
const zoom = 14;
return (
<div style={{ height: '300px', width: '100%' }}>
<GoogleMapReact
bootstrapURLKeys={{ key: apiKey }}
defaultCenter={center}
defaultZoom={zoom}
>
<Marker
lat={lat}
lng={lng}
text={`${name}, ${address}`}
/>
</GoogleMapReact>
</div>
);
}
export default MapBox;
Map is blank:
The Error in the console:You have included the Google Maps JavaScript API multiple times on this page. This may cause unexpected errors.
How to solve?
I am using google-map-react, react-places-autocomplete in the project.
AS temporary solution to my specific use case where I use the google map API's in two different components I have just added the script in the index.html:
<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=places"></script>
I did it in order to avoid that particular error as per of the documentation on the react-places-autocomplete GitHub page.
Unfortunately the link in the head of the index.html caused the same error. I found another workaround. Not the best solution, but works for now:
import React, { useEffect, useState } from 'react';
import GoogleMapReact from 'google-map-react';
export default () => {
const [mapActive, setMapActive] = useState(false);
useEffect(() => {
const t = setTimeout(() => {
setMapActive(true)
}, 100);
return () => {
window.clearTimeout(t);
};
}, [])
return (
<>
{ mapActive && <GoogleMapReact
bootstrapURLKeys={ {
key: ...,
language: ...
} }
defaultCenter={ ... }
defaultZoom={ ... }
>
</GoogleMapReact> }
</>
);
};
You could set a global variable and load the Google JavaScript only if the global variable is not set:
<script type="text/javascript">
if(document.isLoadingGoogleMapsApi===undefined) {
document.isLoadingGoogleMapsApi=true;
var script = document.createElement('script');
script.src='https://maps.googleapis.com/maps/api/js?key=[your-key]&callback=[yourInitMethodName]&v=weekly';
script.type='text/javascript';
script.defer=true;
document.getElementsByTagName('head')[0].appendChild(script);
}else{
[yourInitMethodName]();
}
</script>
In my case there is an arbitrary number of maps in a web application (starting at 0) and the user can add additional maps at runtime.
Most of the users do not use any map so loading it by default would cost unnecessarily loading time.

Resources