From ae068ea68966988681469299f599d0663813c22d Mon Sep 17 00:00:00 2001 From: "Begerad, Stefan" Date: Mon, 27 Jun 2022 08:53:49 +0200 Subject: [PATCH] feat(api-with-paging): initial commit --- api-with-paging/.babelrc | 6 + api-with-paging/.gitignore | 108 ++++++++++++++++++ api-with-paging/README.md | 14 +++ api-with-paging/config/webpack.common.js | 36 ++++++ api-with-paging/config/webpack.dev.js | 14 +++ api-with-paging/config/webpack.prod.js | 8 ++ api-with-paging/package.json | 37 ++++++ api-with-paging/public/index.html | 10 ++ api-with-paging/src/components/api-paging.jsx | 73 ++++++++++++ api-with-paging/src/components/card.jsx | 42 +++++++ api-with-paging/src/components/cards.jsx | 65 +++++++++++ .../src/components/drop-down-select.jsx | 43 +++++++ api-with-paging/src/components/table-ary.jsx | 44 +++++++ .../src/components/table-entry.jsx | 23 ++++ api-with-paging/src/components/table-head.jsx | 14 +++ api-with-paging/src/index.jsx | 13 +++ api-with-paging/src/pages/home.jsx | 13 +++ 17 files changed, 563 insertions(+) create mode 100644 api-with-paging/.babelrc create mode 100644 api-with-paging/.gitignore create mode 100644 api-with-paging/README.md create mode 100644 api-with-paging/config/webpack.common.js create mode 100644 api-with-paging/config/webpack.dev.js create mode 100644 api-with-paging/config/webpack.prod.js create mode 100644 api-with-paging/package.json create mode 100644 api-with-paging/public/index.html create mode 100644 api-with-paging/src/components/api-paging.jsx create mode 100644 api-with-paging/src/components/card.jsx create mode 100644 api-with-paging/src/components/cards.jsx create mode 100644 api-with-paging/src/components/drop-down-select.jsx create mode 100644 api-with-paging/src/components/table-ary.jsx create mode 100644 api-with-paging/src/components/table-entry.jsx create mode 100644 api-with-paging/src/components/table-head.jsx create mode 100644 api-with-paging/src/index.jsx create mode 100644 api-with-paging/src/pages/home.jsx diff --git a/api-with-paging/.babelrc b/api-with-paging/.babelrc new file mode 100644 index 0000000..4f06b0c --- /dev/null +++ b/api-with-paging/.babelrc @@ -0,0 +1,6 @@ +{ + "presets": [ + "@babel/preset-env", + "@babel/preset-react" + ] +} diff --git a/api-with-paging/.gitignore b/api-with-paging/.gitignore new file mode 100644 index 0000000..f992571 --- /dev/null +++ b/api-with-paging/.gitignore @@ -0,0 +1,108 @@ +# Others +package-lock.json +build* + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test + +# parcel-bundler cache (https://parceljs.org/) +.cache + +# Next.js build output +.next + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and *not* Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port diff --git a/api-with-paging/README.md b/api-with-paging/README.md new file mode 100644 index 0000000..a259348 --- /dev/null +++ b/api-with-paging/README.md @@ -0,0 +1,14 @@ +# React.js Example + +## Table of Contents +0. [General](#general) +1. [Links](#links) + +# General + +# Links + +* [React setup with webpack for beginners](https://dev.to/deepanjangh/react-setup-with-webpack-for-beginners-2a8k) +* [Production](https://webpack.js.org/guides/production/) +* [Setup Development and Production Environment for React App](https://medium.com/freestoneinfotech/setup-development-and-production-environment-for-react-app-397c4cc9e382) +* [HtmlWebpackPlugin](https://webpack.js.org/plugins/html-webpack-plugin/) diff --git a/api-with-paging/config/webpack.common.js b/api-with-paging/config/webpack.common.js new file mode 100644 index 0000000..e54a06a --- /dev/null +++ b/api-with-paging/config/webpack.common.js @@ -0,0 +1,36 @@ +//generate a HTML5 file including all webpack bundles in the body using script tags +const HtmlWebpackPlugin = require('html-webpack-plugin'); +//path is used to resolve properly across the OS +const path = require('path'); +module.exports = { + //bundle *.js from this entry point + entry: path.resolve(__dirname, '../src/index.jsx'), + //create output file to be linked to index.html + output: { + filename: '[name].bundle.js', + path: path.resolve(__dirname, '../dist'), + clean: true, + }, + module: { + rules: [ + { + //test all *.js using babel-loader + //test all *.jsx (e.g. React.js) using babel-loader + test: /\.(js|jsx)$/, + exclude: /node_modules/, + include: path.resolve(__dirname, '../src'), + use: ['babel-loader'], + } + ] + }, + resolve: { + extensions: ['*', '.js', '.jsx'], + }, + plugins: [ + // create a plugin instance so that you can use it several times anywhere + new HtmlWebpackPlugin({ + title: 'Production', + template: path.resolve(__dirname, "../public/index.html") + }), + ], +}; diff --git a/api-with-paging/config/webpack.dev.js b/api-with-paging/config/webpack.dev.js new file mode 100644 index 0000000..844d354 --- /dev/null +++ b/api-with-paging/config/webpack.dev.js @@ -0,0 +1,14 @@ +//path is used to resolve properly across the OS +const path = require('path'); +const { merge } = require('webpack-merge'); +const common = require('./webpack.common.js'); +//merge() calls in the environment-specific configuration to include commons +module.exports = merge(common, { + //set development mode + mode: 'development', + //enable strong source mapping + devtool: 'inline-source-map', + devServer: { + static: path.resolve(__dirname, '../dist'), + }, +}); diff --git a/api-with-paging/config/webpack.prod.js b/api-with-paging/config/webpack.prod.js new file mode 100644 index 0000000..e47c24a --- /dev/null +++ b/api-with-paging/config/webpack.prod.js @@ -0,0 +1,8 @@ +const { merge } = require('webpack-merge'); +const common = require('./webpack.common.js'); +module.exports = merge(common, { + mode: 'production', + //source maps encouraged in production + //choose mapping with fairly quick build speed like source-map + devtool: 'source-map', +}); diff --git a/api-with-paging/package.json b/api-with-paging/package.json new file mode 100644 index 0000000..69ce5d6 --- /dev/null +++ b/api-with-paging/package.json @@ -0,0 +1,37 @@ +{ + "private": true, + "name": "react-example", + "description": "React.js example", + "version": "0.0.1", + "main": "index.js", + "keywords": [ + "react", + "webpack" + ], + "author": "Software Ingenieur Begerad ", + "license": "GPL-3.0-or-later", + "engines": { + "node": ">=10" + }, + "scripts": { + "start": "webpack serve --open --config config/webpack.dev.js", + "build": "webpack --config config/webpack.prod.js" + }, + "devDependencies": { + "@babel/core": "^7.18.2", + "@babel/preset-env": "^7.18.2", + "@babel/preset-react": "^7.17.12", + "babel-loader": "^8.2.5", + "html-webpack-plugin": "^5.5.0", + "webpack": "^5.73.0", + "webpack-cli": "^4.9.2", + "webpack-dev-server": "^4.9.2", + "webpack-merge": "^5.8.0" + }, + "dependencies": { + "axios": "^0.27.2", + "prop-types": "^15.8.1", + "react": "^18.1.0", + "react-dom": "^18.1.0" + } +} diff --git a/api-with-paging/public/index.html b/api-with-paging/public/index.html new file mode 100644 index 0000000..965d3b5 --- /dev/null +++ b/api-with-paging/public/index.html @@ -0,0 +1,10 @@ + + + + + React Example + + +
+ + diff --git a/api-with-paging/src/components/api-paging.jsx b/api-with-paging/src/components/api-paging.jsx new file mode 100644 index 0000000..be29339 --- /dev/null +++ b/api-with-paging/src/components/api-paging.jsx @@ -0,0 +1,73 @@ +import React, { useEffect, useState } from 'react'; +import axios from 'axios'; +import DropDownSelect from '../components/drop-down-select'; +const selectOptions=[10,25,50,100,250,500,1000,2500,5000,10000]; +import TableAry from '../components/table-ary'; +const ApiPaging = () => { + /*store and initialise data in function component state*/ + const [oset, setOset] = useState(1); + const [limit, setLimit] = useState(selectOptions[2]); + const [ary, setAry] = useState([]); + + /*fetch ary in a JavaScript function*/ + const fetch = async () => { + try { + /*TODO make route available using config*/ + /*TODO handle errors: https://www.valentinog.com/blog/await-react/*/ + const address=`https://www.v1gtfs.delfi.api.swingbe.de/shapes-oset-limit?oset=${oset}&limit=${limit}`; + const res = await axios.get(address); + + /*set state*/ + setAry(res.data); + } catch (err) { + console.log('err.message: ' + err.message); + } + }; + + /*this hook is run after a DOM update. Changing state migh result in an infinite loop*/ + useEffect(() => { + /*effect goes here*/ + + /*hook need to be placed in body of the function component in which it is used*/ + fetch(); + + /*use an empty dependency array to ensure the hook is running only once*/ + /*TODO study dependency array: https://reactjs.org/docs/hooks-effect.html*/ + }, [oset,limit]); + const handleClickPrev=()=>{ + setOset((oset)=>oset>1?--oset:oset); + }; + const handleClickNext=()=>{ + setOset((oset)=> ++oset); + }; + const handleChangeLimit = (event) => { + //console.log('event.target.value: '+event.target.value); + setLimit((limit)=>event.target.value); + }; + const select = ( + + ); + /*element representing user-defined React component*/ + const table = ; + + return ( + <> +

ApiPaging

+ + + {select} + {table} + + ); +}; +export default ApiPaging; diff --git a/api-with-paging/src/components/card.jsx b/api-with-paging/src/components/card.jsx new file mode 100644 index 0000000..3e82d07 --- /dev/null +++ b/api-with-paging/src/components/card.jsx @@ -0,0 +1,42 @@ +import React, { useEffect, useState } from 'react'; +import axios from 'axios'; +import PropTypes from 'prop-types'; +//destructure props +const Card = ({name}) => { + /*store count as array in function component state*/ + /*initialise as empty array*/ + const [count, setCount] = useState(null); + + /*fetch count in a JavaScript function*/ + const getCount = async () => { + try { + /*TODO make route available using config*/ + /*TODO handle errors: https://www.valentinog.com/blog/await-react/*/ + const address=`https://www.v1gtfs.delfi.api.swingbe.de/table-${name}-count`; + const count = await axios.get(address); + + /*set state*/ + setCount(count.data[0]['count']); + } catch (err) { + console.log('err.message: ' + err.message); + } + }; + + /*this hook is run after a DOM update. Changing state migh result in an infinite loop*/ + useEffect(() => { + /*effect goes here*/ + + /*hook need to be placed in body of the function component in which it is used*/ + getCount(); + + /*use an empty dependency array to ensure the hook is running only once*/ + /*TODO study dependency array: https://reactjs.org/docs/hooks-effect.html*/ + }, []); + return

{name}: {count?count:'loading...'}

; +}; + +Card.propTypes = { + name: PropTypes.string +}; + +export default Card; diff --git a/api-with-paging/src/components/cards.jsx b/api-with-paging/src/components/cards.jsx new file mode 100644 index 0000000..f65b87b --- /dev/null +++ b/api-with-paging/src/components/cards.jsx @@ -0,0 +1,65 @@ +import React, { useEffect, useState } from 'react'; +import axios from 'axios'; +import Card from './card'; +const Cards = () => { + /*store and initialise data in function component state*/ + const [tables, setTables] = useState([]); + /*fetch data in a JavaScript function*/ + const getTables = async () => { + try { + /*TODO make route available using config*/ + /*TODO handle errors: https://www.valentinog.com/blog/await-react/*/ + const tables = await axios.get( + 'https://www.v1gtfs.delfi.api.swingbe.de/table-names' + ); + + /*set state*/ + setTables(tables.data); + } catch (err) { + console.log('err.message: ' + err.message); + } + }; + + /*this hook is run after a DOM update. Changing state migh result in an infinite loop*/ + useEffect(() => { + /*effect goes here*/ + + /*hook need to be placed in body of the function component in which it is used*/ + getTables(); + + /*use an empty dependency array to ensure the hook is running only once*/ + /*TODO study dependency array: https://reactjs.org/docs/hooks-effect.html*/ + }, []); + if(tables){ + return( + <> +

GTFS tables

+ {tables.map((item,index)=>{ + return + }) + } + + ); + }else{ + return( + <> +

GTFS tables loading...

+ + ); + } +} + +export default Cards + + + + + + + + + + + + + diff --git a/api-with-paging/src/components/drop-down-select.jsx b/api-with-paging/src/components/drop-down-select.jsx new file mode 100644 index 0000000..6ea3067 --- /dev/null +++ b/api-with-paging/src/components/drop-down-select.jsx @@ -0,0 +1,43 @@ +import React from 'react' +import PropTypes from 'prop-types'; + +/*controlled component: input form value controlled by React*/ +const DropDownSelect = (props) => { + /*destructuring*/ + const {options,name,onChange,defaultValue}=props; + if(options){ + return( +
+ {name}: + +
+ ); + }else{ + return ( +
+

Select failed.

+
+ ); + } +}; + +export default DropDownSelect + +DropDownSelect.propTypes = { + name: PropTypes.string, + defaultValue: PropTypes.number, + onChange: PropTypes.func, + options: PropTypes.array, +}; diff --git a/api-with-paging/src/components/table-ary.jsx b/api-with-paging/src/components/table-ary.jsx new file mode 100644 index 0000000..749c52f --- /dev/null +++ b/api-with-paging/src/components/table-ary.jsx @@ -0,0 +1,44 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import Entry from './table-entry'; +import Head from './table-head'; + +/*the simplest way to define a component is to write a JavaScript function*/ +/*destructure props object*/ +function TableAry ({ aryData }) { + const handleAryData = () => { + if (aryData) { + //iterate over array + return aryData.map((item, index) => { + return ( + + ); + }); + } else { + console.log('aryData NOT available'); + return

loading...

; + } + }; + + /*return a React element*/ + return ( + <> + + + + + {handleAryData()} +
+ + ); +} +TableAry.propTypes = { + aryData: PropTypes.array +}; +export default TableAry; diff --git a/api-with-paging/src/components/table-entry.jsx b/api-with-paging/src/components/table-entry.jsx new file mode 100644 index 0000000..b1073fe --- /dev/null +++ b/api-with-paging/src/components/table-entry.jsx @@ -0,0 +1,23 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +/*destructure props object*/ +const TableEntry = ({ shape_id, shape_pt_lat, shape_pt_lon, shape_pt_sequence }) => { + return ( + + {shape_id} + {shape_pt_lat} + {shape_pt_lon} + {shape_pt_sequence} + + ); +}; + +TableEntry.propTypes = { + shape_id: PropTypes.string, + shape_pt_lat: PropTypes.number, + shape_pt_lon: PropTypes.number, + shape_pt_sequence: PropTypes.number +}; + +export default TableEntry; diff --git a/api-with-paging/src/components/table-head.jsx b/api-with-paging/src/components/table-head.jsx new file mode 100644 index 0000000..8bfeec6 --- /dev/null +++ b/api-with-paging/src/components/table-head.jsx @@ -0,0 +1,14 @@ +import React from 'react'; + +const TableHead = () => { + return ( + + shape_id + shape_pt_lat + shape_pt_lon + shape_pt_sequence + + ); +}; + +export default TableHead; diff --git a/api-with-paging/src/index.jsx b/api-with-paging/src/index.jsx new file mode 100644 index 0000000..2c507a4 --- /dev/null +++ b/api-with-paging/src/index.jsx @@ -0,0 +1,13 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import Home from './pages/home'; +//TODO remove debugging +if (process.env.NODE_ENV !== 'production') { + console.log('development mode'); +} +//since react 18 +import { createRoot } from 'react-dom/client'; +//create root container +const root = createRoot(document.getElementById("root")); +//render root app +root.render(); diff --git a/api-with-paging/src/pages/home.jsx b/api-with-paging/src/pages/home.jsx new file mode 100644 index 0000000..757293a --- /dev/null +++ b/api-with-paging/src/pages/home.jsx @@ -0,0 +1,13 @@ +import React from 'react'; +import ApiPaging from '../components/api-paging'; + +const Home = () => { + return ( + <> +

Home

+ + + ); +} + +export default Home