diff --git a/cfg/defaults.js b/cfg/defaults.js index 67a4328..27a8cd8 100644 --- a/cfg/defaults.js +++ b/cfg/defaults.js @@ -62,3 +62,4 @@ module.exports = { port: dfltPort, getDefaultModules: getDefaultModules }; + diff --git a/package.json b/package.json index e82d046..a369268 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "release:patch": "npm version patch && npm publish && git push --follow-tags", "serve": "node server.js --env=dev", "serve:dist": "node server.js --env=dist", - "start": "node server.js --env=dev", + "start": "nodemon server.js --env=dev", "test": "karma start", "test:watch": "karma start --autoWatch=true --singleRun=false" }, @@ -38,7 +38,7 @@ "eslint-plugin-react": "^6.0.0", "file-loader": "^0.9.0", "glob": "^7.0.0", - "isparta-instrumenter-loader": "^1.0.0", + "isparta-loader": "^1.0.0", "karma": "^1.0.0", "karma-chai": "^0.1.0", "karma-coverage": "^1.0.0", @@ -49,6 +49,7 @@ "karma-webpack": "^1.7.0", "minimist": "^1.2.0", "mocha": "^3.0.0", + "nodemon": "^1.11.0", "null-loader": "^0.1.1", "open": "0.0.5", "phantomjs-prebuilt": "^2.0.0", diff --git a/readme.md b/readme.md index 15a092c..255e966 100644 --- a/readme.md +++ b/readme.md @@ -17,8 +17,33 @@ Routing functionality bneeds extra npm's specifically npm install -S react-router npm install -S history ----------------------------- -@spal23 - Added some very basic routing. + +---------------------------- June 2017 +@spal23 - Configured the topic based home page + +Added two components, both used on the home page. The first is Topics.jsx, the second is MembersList.jsx. They can be found in \src\components\Common + +Topics displays a group of href shortcuts loaded in three columns. The fun here was rendering all of the topics with a forced width css, hidden. Then, using refs get the heights of each topic element (while they are hidden - but after they were mounted). Using this height array the topics were re-rendered and placed based on a shortest column algorithm. Topics can be clicked through to generate a new tab in the browser. + +Memberslist uses CORS (which has to be enabled in the browser) to link to the Meetup API and request the group members of NottsJS. The members picture, bio and name are then loaded into a div to the right of the home page. + +Topics displayed on home page are loaded from the object array defined in \src\data\topiclist.js. + +From here ... +1. Could do with a database to hold topic info for fast retrieval, rather than using href links to populate the page. +Would also look at doing a cut and paste to copy other user specific shortcuts to the page automatically. Each home page would be user specific then. +2. Page format could be more automated by passing props in from the template to allow page structure to change programatically. Currently page width is hard coded. +3. CSS could be more optimised. Currently CSS is bound to the components as per Reacts guidelines however I am sure this could be optimised by a more experienced CSS programmer than myself. +4. The header picture could be higher res and thinner. It would be neat to have a slide show feature there as well. +5. Footer needs to be organised along the lines of the original web site. +6. CORS link in the Memberslist.jsx component needs extending to bring in pictures of all of the groups members. Currently this is limited to the first 200 records. Unsure as to why. + +Next ... +1. I will look at doing a page to automatically feed into meetup so that we can setup meetings from our own page outide of the meetup site. Basicaly getting into the meetup API a little bit more. + + +---------------------------- April 2017 +@spal23 - Added some very basic routing For info to those unsure about things (aka me) 1. npm start calls to server.js @@ -31,20 +56,9 @@ Note - If running with sublime and you want to add a .jsx extension beutifier 2. Open a .jsx file 3. Select View from the menu 4. Then Syntax -> Open all with current extension as.. -> Babel - Javascript (Babel) - -The routing method was based on the article from .. - https://scotch.io/tutorials/routing-react-apps-the-complete-guide +References are: +http://gunnariauvinen.com/getting-es6-syntax-highlighting-in-sublime-text/ +https://scotch.io/tutorials/routing-react-apps-the-complete-guide I have gone with browser history for now rather than hash history if someone knows what they are doing feel free to change that about. - -I have played around with props, passing back functions and booleans to the header page -to provide switched layouts just to see if it can be done. All of the trial and error work has been done on the /training route and mapped out to / and /about when I have go the hand of it all. - -Changes made to .... - -Components folder -index.jsx -package.json -readme.md - ------------------------------- \ No newline at end of file +------------------------------ diff --git a/src/components/Common/Footer.jsx b/src/components/Common/Footer.jsx index b112316..c5bd51c 100644 --- a/src/components/Common/Footer.jsx +++ b/src/components/Common/Footer.jsx @@ -1,10 +1,32 @@ import React from 'react'; +import { Link } from 'react-router'; export default class Footer extends React.Component { render() { + var navbar2 = { + paddingRight: '10px', + paddingLeft: '10px', + color: 'black' + } + var footerStyle = { + position: 'relative', + width: '800px', + marginLeft: 'auto', + marginRight: 'auto', + bottom: '0px', + height: '200px', + backgroundColor: 'pink' + } return ( - - ); +
+ +
+ ); } } \ No newline at end of file diff --git a/src/components/Common/Header.jsx b/src/components/Common/Header.jsx index fd99bfe..8375494 100644 --- a/src/components/Common/Header.jsx +++ b/src/components/Common/Header.jsx @@ -3,19 +3,69 @@ import React from 'react'; import { Link } from 'react-router'; export default class Header extends React.Component { + + /* static propTypes = { title: React.PropTypes.string.isRequired } + */ render() { - const { title } = this.props; + var headerStyle = { + position: 'relative', + width: '800px', + top: '0px', + marginLeft: 'auto', + marginRight: 'auto' + } + var navbar = { + position: 'absolute', + height: '20px', + bottom: '10px' + } + var navbar2 = { + paddingRight: '10px', + paddingLeft: '10px', + color: 'grey' + } + var logofloat = { + position: 'absolute', + top: '20px', + left: '20px', + backgroundColor: 'white' + } + var sponsorfloat = { + position: 'absolute', + top: '20px', + right: '20px', + backgroundColor: 'white' + } + var sponsorstack = { + position: 'absolute', + top: '70px', + right: '20px', + backgroundColor: 'white' + } + var sponsorstack2 = { + position: 'absolute', + top: '180px', + right: '20px', + backgroundColor: 'white' + } return ( -
-

Notts JS - {title}

- Home - About - Training -
+
+ + + + + +
+ Home + About + Training +
+
); } } + diff --git a/src/components/Common/MembersList.jsx b/src/components/Common/MembersList.jsx new file mode 100644 index 0000000..147e02e --- /dev/null +++ b/src/components/Common/MembersList.jsx @@ -0,0 +1,165 @@ +/* eslint no-console: 0*/ + +import React from 'react'; + +// Support Functions +function createCORSRequest(method, url) { + /* + Courtesy of ... + https://www.nczonline.net/blog/2010/05/25/cross-domain-ajax-with-cross-origin-resource-sharing/ + */ + var xhr = new XMLHttpRequest(); + if ('withCredentials' in xhr) { + + // Check if the XMLHttpRequest object has a "withCredentials" property. + // "withCredentials" only exists on XMLHT*-TPRequest2 objects. + xhr.open(method, url, true); + console.log('--- found withCredentials property') + + } else if (typeof XDomainRequest != 'undefined') { + // Otherwise, check if XDomainRequest. + // XDomainRequest only exists in IE, and is IE's way of making CORS requests. + xhr = new XDomainRequest(); + xhr.open(method, url); + + } else { + // Otherwise, CORS is not supported by the browser. + xhr = null; + + } + return xhr; +} + +// MEMBER COMPONENT ===================================== +class Member extends React.Component { + constructor(props){ + super(props); // Calls the constructor of the parent class. In this case Memberslist + } + + // MEMBER return method + render(){ + var pstyle = { + fontSize: '12px' + } + var spanstyle = { + color: 'green', + fontSize: '11px' + } + var imgStyle = { + borderRadius: '15px 15px 15px 15px' + } + var td = this.props.dataString + if ('photo' in td) { + return ( +
+ +

"{td.bio}" - {td.name}

+
+ ); + } else { + return ( +
+

"{td.bio}" - {td.name}

+
+ ); + } + } +} + +// MEMBERSLIST COMPONENT ================================= +export default class MembersList extends React.Component { + constructor(props){ + super(props); // Calls the constructor of the parent class. In this case React.component + this.membersdata = getTestData(); + this.state = { showReceivedList: false }; + console.log('--- MembersList Constructor Loading ...'); + + // Kickoff the fetch of the members list' + var url = 'https://www.google.com' + var xhr = createCORSRequest('GET', url); + if (!xhr) { + console.log('--- Sadly CORS is not Supported') + throw new Error('CORS not supported'); + } else { + console.log('--- great news CORS is supported') + } + + /* + CORS NOTE. When running this code in Localhost on Chrome + you need to have the CORS Toggle extension installed and running. Otherwise + chrome kicks out errors to do with authentication. + */ + + // HTTP request to meetup for latest members list. + var xhr = new XMLHttpRequest(); + xhr.open('GET', 'https://api.meetup.com/NottsJS/members?&key=7121206f695127cd116f2743281550', true); + + // HTTP CORS event handlers + // xhr.onreadystatechange is replaced by xhr.onload as of HTML 5 (2014). + xhr.onload = this.processRequest; + xhr.onerror = () => { + console.log('--- CORS Event failed - There was an error!'); + }; + + // Kickoff HTTP async request + xhr.withCredentials = false; + xhr.send(); + console.log('--- Memberslist called for Asynchronously'); + } + + // MEMBERSLIST EVENT HANDLER (CORS) + // 'this' could have been configured using bind by placing the following in the constructor above. + // this.processRequest = this.processRequest.bind(this); + // Doing this would have meant that the arrow function need not have been used below. + processRequest = e => { + console.log(e); + var xhr = e.target; + if (xhr.readyState == 4 && xhr.status == 200) { + this.membersdata = JSON.parse(xhr.responseText); + console.log('--- Changing state to cause re-render with new list'); + this.setState({ showReceivedList: true }); // A function taking an object as the argument + } + console.log('--- ' + e.target.status + ' ' + this.state.showReceivedList); + } + + // MEMBERSLIST return method with styling + render(){ + var assideStyle = { + position: 'absolute', + float: 'right', + display: 'inline-block', + width: '100px', + height: '784px', + margin: '2px', + border: '1px solid black', + padding: '5px', + backgroundColor: 'pink', + overflow: 'scroll' + } + console.log('--- Rendering the members list'); + return( +
+ {this.membersdata.map((item, ix) => { + return ( + + )} + )} +
+ ); + } +} + +function getTestData() { + return( [{ + id: 2890627, + name: 'Nomad3k', + bio: 'Main organiser and React architect for the NottsJS group', + photo: { + id: 252816582, + thumb_link: 'https://secure.meetupstatic.com/photos/member/8/e/e/6/thumb_252816582.jpeg' + }, + link: 'https://www.meetup.com/NottsJS/members/2890627/' + }]); +} + + diff --git a/src/components/Common/Template.jsx b/src/components/Common/Template.jsx index 35da4fa..e152480 100644 --- a/src/components/Common/Template.jsx +++ b/src/components/Common/Template.jsx @@ -1,23 +1,33 @@ import React from 'react'; - -import Header from './header'; -import Footer from './footer'; +import Header from './Header'; +import Footer from './Footer'; export default class Template extends React.Component { + /* static propTypes = { title: React.PropTypes.string.isRequired } + */ + + constructor(props){ + super(props); + } + render() { - const { title } = this.props; - document.title = title + ' - NottsJS'; + var templateStyle = { + position: 'static', + width: '800px', + top: '0px', + height: '900px', + marginLeft: 'auto', + marginRight: 'auto' + } return ( -
-
-
- {this.props.children} -
+
+
+
{this.props.children}
); } -} +} \ No newline at end of file diff --git a/src/components/Common/Topics.jsx b/src/components/Common/Topics.jsx new file mode 100644 index 0000000..1fb67a9 --- /dev/null +++ b/src/components/Common/Topics.jsx @@ -0,0 +1,185 @@ +/* eslint no-console: 0*/ + +import React from 'react'; +import topicData from '../../data/topiclist.js'; + +// TOPIC COMPONENT ===================================== +class Topic extends React.Component { + constructor(props){ + super(props); + } + + // TOPIC return method + render(){ + var divStyle = { + position: 'absolute', + width: '220px', + border: '1px solid black', + fontSize: '12px', + borderRadius: '30px 30px 30px 30px', + backgroundColor: 'pink' + } + // Position the div's vertically and horizontally + // first time through they are placed on top of one another + // and hidden. Second and subsequent times through they are visible. + divStyle.top = this.props.top + 'px'; + divStyle.left = this.props.left + 'px'; + divStyle.visibility = this.props.showTopics; + + var centeredH2 = { + textAlign: 'center' + } + var centeredImg = { + margin: '0 auto', + display: 'block' + } + var centeredP = { + textAlign: 'center', + margin: '0 auto' + } + var divp = { + padding: '15px', + width: '180px', + marginLeft: 'auto', + marginRight: 'auto' + } + var ds = this.props.dataString; + return ( +
this.props.clicked(e,this.props.ident)} style={divStyle} ref={input => this.divRef = input}> +

{ds.title}

+ +
+

'{ds.note}'

+
+
+ ); + } +} + + +// TOPICS COMPONENT ================================= +export default class Topics extends React.Component { + constructor(props){ + super(props); // Calls the constructor of the parent class. In this case React.component + this.topicData = topicData; + this.topPos = [5,5,5]; + this.leftPos = [5,230,455]; + // first time through all topics are loaded to 5,5. From there we can get actual rendered sizes + // and re-position using state to cause the trigger + this.state = ({visibility: 'hidden'}); + // State is only ever local or it can be passed down as props. + this.topicRefs = []; + this.topicHeights = [0,0,0]; + // array to hold refs for the mounted topics + console.log('--- Topics Constructor Loading ...'); + + } + + componentDidMount(){ + if (this.state.visibility = 'hidden') { + console.log('--- All components mounted - first time load'); + console.log(this.topicRefs); + // Work through all topics to record their rendered height. + this.topicRefs.forEach((item,ix) => { + this.topicHeights[ix] = item.divRef.offsetHeight; + }); + this.setState({ visibility: 'visible' }); + /* + Note - Immediately after setting state, visibility is still hidden. + However by the time we get to re-rendering the Topics it shows correctly + as visible. + Changing state forces a remount but this time topics will be visible + and we know the rendered height of each topic. So we can position + it correctly. + */ + } else { + console.log('--- All components mounted - through again'); + } + } + + findMinIdx(anArray) { + var minVal = anArray[0]; // Keeps a running count of the smallest value so far + var minIdx = 0; // Will store the index of minVal + var idx=0; + for(idx=1; idx { + // Having clicked on the topic we need to take information from the event to + // jump to the next route. + e.preventDefault(); + console.log('--- Topic ' + ident + ' has been clicked'); + console.log('--- Jumping to ' + this.topicData[ident].href); + var win = window.open(this.topicData[ident].href); + win.focus(); + + } + + // MEMBERSLIST return method with styling + render(){ + var bodysection = { + position: 'relative', + float: 'left', + display: 'inline-block', + width: '670px', + height: '784px', + margin: '2px', + border: '1px solid black', + padding: '5px', + backgroundColor:' #ffcccc' + // overflow: 'scroll' + } + console.log('--- Rendering the topics. State is ' + this.state.visibility); + console.log('--- Topic Heights ' + this.topicHeights); + console.log('--- Topic Tops ' + this.topPos); + console.log('--- Topic Lefts ' + this.leftPos); + + + return( +
+ { + this.topicData.map((item, ix) => { + this.newtop = 0; + this.newleft = 0; + if (this.state.visibility != 'hidden'){ + this.col = this.findMinIdx(this.topPos) + this.newtop = this.topPos[this.col] + this.newleft = this.leftPos[this.col] + // ... finds the index of the column that is the least utilised + // this makes the column positioning dynamic based on the positioning of what + // has gone before. Only second time through though. First time through all + // topic components are loaded on top of each other at position 5,5 and they are hidden + /* console.log('--- The selected column is ' + this.col) + console.log('--- The left position is ' + this.leftPos[this.col]) + console.log('--- The topic height is ' + this.topicHeights[this.col]). */ + this.topPos[this.col] += this.topicHeights[ix] + 5 + } + return ( + this.topicRefs[ix] = el} + /> + )} + )} +
+ ); + } +} + +/* + + +*/ + diff --git a/src/components/Pages/About.jsx b/src/components/Pages/About.jsx index e4d7110..7297f4e 100644 --- a/src/components/Pages/About.jsx +++ b/src/components/Pages/About.jsx @@ -1,12 +1,15 @@ import React from 'react'; +import ContributorList from '../../controls/contributor-list'; +import contributors from '../../data/contributors'; import Template from '../Common/Template' -export default class PageComponent extends React.Component { +export default class AboutComponent extends React.Component { render() { return ( -