diff --git a/index.html b/index.html index b9bef462..59fb8a11 100644 --- a/index.html +++ b/index.html @@ -101,20 +101,30 @@ * ... * ] */ - function getPersonList() { - return new Promise((resolve, reject) => { - fetch('http://api.namegame.willowtreemobile.com/').then(function(response) { - if (response.status !== 200) { - reject(new Error("Error!")); - } - - response.json().then((imageList) => { - resolve(imageList); - }); - }); - }); - } + window.visiblePersonList = [] + + const getPersonList = () => { + fetch('http://api.namegame.willowtreemobile.com/').then(function(response) { + if (response.status !== 200) { + new Error("Error!"); + } + response.json().then((personList) => { + personList.map((person) => + window.visiblePersonList.push( + {name: person.name, + first: getFirstName(person.name), + last: getLastName(person.name), + url: person.url})) + ReactDOM.render( + React.createElement(App, {props: visiblePersonList}), + document.getElementById('app') + ); + ; + }); + }) + } + getPersonList() /*================================================== @@ -123,20 +133,20 @@ ***************************************************/ - function getLastName(fullName) { - return fullName.match(/\w+/g)[1]; - } - const getFirstName = (fullName) => { return fullName.match(/\w+/g)[0]; }; + const getLastName = (fullName) => { + return fullName.match(/\w+/g)[1]; + }; + /** * Fisher-Yates shuffle */ function shuffleList(list) { // Make a copy & don't mutate the passed in list - let result = list.slice(1); + let result = Object.assign([], list); let tmp, j, i = list.length - 1 @@ -150,17 +160,33 @@ return result; } + const renderShuffle = (event) => { + ReactDOM.render( + React.createElement(App, {props: shuffleList(visiblePersonList)}), + document.getElementById('app') + ); + + } /** * Remove any people that do not have the name we are * searching for. */ - function filterByName(searchForName, personList) { - return personList.filter((person) => { - return person.name === searchForName; + function filterByName(searchForName) { + var searchNoCase = searchForName.toLowerCase() + return visiblePersonList.filter((person) => { + return person.name.toLowerCase().includes(searchNoCase) }); } + const searches = (event) => { + ReactDOM.render( + React.createElement(App, {props: filterByName(event.target.value)}), + document.getElementById('app') + ); + } + + /** * Takes in a property of an object list, e.g. "name" below @@ -178,121 +204,73 @@ * > [{ name: 'Jon' }, { name: 'Kevin' }, { name: 'Sam' }] * */ + + const sortByFirstName = (event) => { + ReactDOM.render( + React.createElement(App, {props: sortObjListByProp('first')}), + document.getElementById('app') + ); + }; + + const sortByLastName = (event) => { + ReactDOM.render( + React.createElement(App, {props: sortObjListByProp('last')}), + document.getElementById('app') + ); + }; + function sortObjListByProp(prop) { - return function(objList) { // Make a copy & don't mutate the passed in list - let result = objList.slice(1); + let result = Object.assign([], visiblePersonList); result.sort((a, b) => { if (a[prop] < b[prop]) { return -1; } - if (a[prop] > b[prop]) { - return 1; - } - return 1; }); return result; }; - } - - const sortByFirstName = sortObjListByProp('name'); - - const sortByLastName = (personList) => sortByFirstName(personList).reverse(); - /*================================================== VIEW (React) - ***************************************************/ - - const Search = (props) => React.DOM.input({ - type: 'input', - onChange: props.onChange - }); - - const Thumbnail = (props) => React.DOM.img({ - className: 'image', - src: props.src - }); - - const ListRow = (props) => React.DOM.tr({ key: props.person.name }, [ - React.DOM.td({ key: 'thumb' }, React.createElement(Thumbnail, { src: props.person.url })), - React.DOM.td({ key: 'first' }, null, getFirstName(props.person.name)), - React.DOM.td({ key: 'last' }, null, getLastName(props.person.name)), - ]); - - const ListContainer = (props) => React.DOM.table({ className: 'list-container' }, [ - React.DOM.thead({ key: 'thead' }, React.DOM.tr({}, [ - React.DOM.th({ key: 'thumb-h' }, null, 'Thumbnail'), - React.DOM.th({ key: 'first-h' }, null, 'First Name'), - React.DOM.th({ key: 'last-h' }, null, 'Last Name') - ])), - React.DOM.tbody({ key: 'tbody' }, props.personList.map((person, i) => - React.createElement(ListRow, { key: `person-${i}`, person }))) - ]); - - const App = React.createClass({ - getInitialState() { - return { - personList: [], - visiblePersonList: [] - }; - }, - - componentDidMount() { - getPersonList().then((personList) => - this.setState({ - personList, - visiblePersonList: personList - })); - }, - - _shuffleList() { - this.setState({ - visiblePersonList: shuffleList(this.state.personList) - }); - }, - - _sortByFirst() { - this.setState({ - visiblePersonList: sortByFirstName(this.state.personList) - }); - }, - - _sortByLast() { - this.setState({ - visiblePersonList: sortByLastName(this.state.personList) - }); - }, - - _onSearch(e) { - this.setState({ - visiblePersonList: filterByName(e.target.value, this.state.personList) - }); - }, - - render() { - const { visiblePersonList } = this.state; - - return React.DOM.div({ className: 'app-container' }, [ - React.createElement(Search, { key: 'search', onChange: this._onSearch }), - React.DOM.button({ key: 'shuffle', onClick: this._shuffleList }, null, 'Shuffle'), - React.DOM.button({ key: 'sort-first', onClick: this._sortByFirst }, null, 'Sort (First Name)'), - React.DOM.button({ key: 'sort-last', onClick: this._sortByLast }, null, 'Sort (Last Name)'), - React.createElement(ListContainer, { key: 'list', personList: visiblePersonList }) - ]); - } - }); - - ReactDOM.render( - React.createElement(App), - document.getElementById('app') - ); + // **************************************************/ + + const App = ({props}) => { + + const Thumbnail = (props) => React.DOM.img({ + className: 'image', + src: props.src + }); + + const ListRow = (props) => React.DOM.tr({ key: props.person.name }, [ + React.DOM.td({ key: 'thumb' }, React.createElement(Thumbnail, { src: props.person.url })), + React.DOM.td({ key: 'first' }, props.person.first), + React.DOM.td({ key: 'last' }, props.person.last), + ]); + + const ListContainer = (props) => React.DOM.table({ className: 'list-container' }, [ + React.DOM.thead({ key: 'thead' }, React.DOM.tr({}, [ + React.DOM.th({ key: 'thumb-h' }, null, 'Thumbnail'), + React.DOM.th({ key: 'first-h' }, null, 'First Name'), + React.DOM.th({ key: 'last-h' }, null, 'Last Name') + ])), + React.DOM.tbody({ key: 'tbody' }, props.personList.map((person, i) => + React.createElement(ListRow, { key: `person-${i}`, person }))) + ]); + + return React.DOM.div({className: 'app-container' }, [ + React.DOM.input({ type: 'input', key: 'search', onChange: searches }), + React.DOM.button({ key: 'shuffle', onClick: renderShuffle }, null, 'Shuffle'), + React.DOM.button({ key: 'sort-first', onClick: sortByFirstName }, null, 'Sort (First Name)'), + React.DOM.button({ key: 'sort-last', onClick: sortByLastName }, null, 'Sort (Last Name)'), + React.createElement(ListContainer, { key: 'list', personList: props }) + ]); + } diff --git a/notes.md b/notes.md new file mode 100644 index 00000000..b62ae967 --- /dev/null +++ b/notes.md @@ -0,0 +1,24 @@ +##Stateless components + +Before doing this exercise, I had read in a few blogs that stateless React components are desirable because they cut down on the WET code you might be writing, it makes it easier for programmers unfamiliar with React to read your code, and it cuts down on a lot of the confusion with "this" and what "this" refers to at specific points in the code, among other reasons. As I looked at the code, I did notice there seemed to be a lot of code smells with the event listeners. I decided this was going to be the first thing I was going to work on because it was likely to be the most major change, and I figured that once I got the main functionality working, I could rebuild the features around the main stateless App component. +- I wanted to first make sure that in the stateless component, I would be able to retrieve the json data as props in some sort of form that I would be able to manipulate and render on the page. +- From my reading on stateless components, I tried to identify some marked features: no state, no constructor or super, whatever props you have are passed through as a main variable, no render, no "this" or "bind", and components are functions, not classes. +- I ultimately decided I didn't need a promise for a stateless React app. In the original code, I noticed that one of the benefits was that it allowed the DOM's initial state to load without the list of the people, and then once the DOM loaded, the code resolved the promise and returned the values. In my stateless component, I just wanted the data to render on initialize, so I deleted it. +- The reason why I have one larger commit message/less commit messages for each step was because I think it's important to only push code that produces some sort of functionality with a given change/set of changes, so as to not confuse the people you're working with, and that was more difficult to do with this particular task I was doing. + +##Rebuilding the table +- I decided that for now, I would just copy and paste all of the functions that you identified as contributing to VIEWS within the main App component. These functions (listContainer, listRow, Thumbnail) belong in the App component because they directly take information rendered from the props variable and map it on to the Reactified HTML. In React, you can only render one React component directly onto the DOM (in this case App) and all of your other HTML elements must be organized as children of this parent in some manner. In order to just do this, I only needed to make sure that personList (line 271) was taking in the props array rendered from the json data (a bit more on this later). + +##Features + +###Sorting by first and last names +- I noticed that not only was the last name sort function not working correctly but that the logic seemed a little confusing. The functions were sorting the list of people via the present keys listed within the array of objects (name and url). The first name function was working because it was using the "name" key to sort, but that's more of a false positive and doesn't allow the function to access the last name without some contorted logic. On that note, the last name sort function wasn't working because the logic was completely wrong - it only was producing a reversed order of the first name sort. I decided that I would solve this by using the getFirstName and getLastName functions to parse through the names as I was getting them from the API and just push them into the array I would be using so that I would have individual first and last names available to me. I would then be able to sort by "first" and "last" keys, which are more representative of the process, rather than use some contorted logic to parse out last name in the last name sort. I also changed the formatting of the getLastName function to ES6 just to make it look consistent and neat with the getFirstName function. + + - I also had problems with getting the sorted lists to re-render on the page - since I deleted state, I didn't have that setState feature listening for change and re-rendering when the state changed. I did some research on Google and found on a React forum (monitored by the Facebook React team) that the recommended way was to just write ReactDOM.render and then re-render my main component. So I did that, but was frustrated that it still seemed to not be working. I did some more research and some people suggested that the problem was that a different "key" attribute needed to be rendered each time you want to re-render the page, so I tried that and not only did it not work, but I noticed from inspecting the elements (data-reactid) that React was producing a different version number each time I was re-rendering (.0 -> .1) and yet nothing changed. That's when I realized that I was making a silly mistake - the array I was passing through was the original array from the API request (visiblePersonList), rather than the dynamic props argument (props) being passed through the App component. I changed that and it worked finally. + +###Shuffle +- I realized that the array was being copied incorrectly; the array was leaving out the person at index 0 in the array (in this case, Abby Cook) before shuffling. I fixed it by writing code that didn't slice. I made sure to copy the array as a new array because I wanted to make sure I was following the directions ("make a copy and don't mutate the passed in list"). Another small problem I was having was that the DOM kept shuffling the data on initial load, even though I hadn't clicked anything. I solved this by adding another function that passed the event through as a variable. + +###Search +- The main problem with this seemed to be that the searches were case sensitive, which isn't really user friendly, and it required a user to type in the full name of the person to get a result, which also isn't user friendly. I changed the method to not be case sensitive and to allow for substring matches, rather than entire string matches. +- At this point, the search function seemed to be redundant and caused me more problems. I had two onChange event listeners and when the page re-rendered, it deleted whatever previous input the user entered, which isn't helpful. I didn't think it was necessary to make "Search" its own unique element since it is an input anyways, so I edited/combined the code for that input element, and that seemed to make the search function work.