Advertisement
beelzebielsk

mutually-exclusive-groups-list.js

Dec 15th, 2017
118
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. import React, { Component } from 'react';
  2. import logo from './logo.svg';
  3. import './App.css';
  4.  
  5.  
  6. function CheckMark(props) {
  7.     return (
  8.         <input
  9.             type="checkbox"
  10.             checked={props.checked}
  11.             onChange={props.onChange}/>
  12.     );
  13. }
  14.  
  15. /* Now I'm going to look at the problem as having groups of check
  16.  * marks, where you can't have a check in one group and a check in
  17.  * another group. All of the checks have to be in exactly one of the
  18.  * groups. This is a slightly more general version of having a list
  19.  * of choices whose length can change, and having just one option that
  20.  * unchecks all of the other options.
  21.  *
  22.  * You can see the animal options as belonging to one group, and the
  23.  * disliking animals option as belonging to a second group.
  24.  */
  25.  
  26. /* Takes an object whose keys are labels of list items, and whose
  27.  * values are objects of the form:
  28.  *      {content:string, checked:boolean, onChange:function}.
  29.  * So, the whole object's structure is:
  30.  * {
  31.  *      ...entries,
  32.  *      entryName : {
  33.  *         content: string,
  34.  *         checked: boolean,
  35.  *         onChange: function,
  36.  *      }
  37.  *      ...more entries,
  38.  * }
  39.  */
  40. function CheckList(props) {
  41.     return (
  42.         <ul>
  43.             {
  44.                 Object.keys(props.entries).map(label => {
  45.                     return (<li key={label}>
  46.                         {props.entries[label].content}
  47.                         <CheckMark
  48.                             checked={props.entries[label].checked}
  49.                             onChange={props.entries[label].onChange}/>
  50.                     </li>);
  51.                            
  52.                 })
  53.             }
  54.         </ul>
  55.     );
  56. }
  57.  
  58. function makeFalseDict(keys) {
  59.     let dict = {};
  60.     for (let key of keys) {
  61.         dict[key] = false;
  62.     }
  63.     return dict;
  64. }
  65.  
  66. /* Takes an object which may have nested objects or arrays as values
  67.  * and returns a 'flattened' object, which is an object whose values
  68.  * are only simple values, like strings, or numbers.
  69.  */
  70. function makeFlatDict(object) {
  71.     let joinChar = '/';
  72.     function isSimpleValue(value) {
  73.         let simpleTypes = ['string', 'boolean', 'number', 'undefined'];
  74.         let isNull = value === null;
  75.         return simpleTypes.includes(typeof value) || isNull;
  76.     }
  77.  
  78.     function flatten(toFlatten, objectSoFar, keyPrefix) {
  79.         for (let key in toFlatten) {
  80.             let value = toFlatten[key];
  81.             if (isSimpleValue(value)) {
  82.                 objectSoFar[`${keyPrefix}${key}`] = value;
  83.             } else {
  84.                 let newPrefix = `${keyPrefix}${key}${joinChar}`;
  85.                 flatten(value, objectSoFar, newPrefix);
  86.             }
  87.         }
  88.     }
  89.     let flattenedObject = {};
  90.     flatten(object, flattenedObject, '');
  91.     return flattenedObject;
  92. }
  93.  
  94. function accessFlatDict(object, ...keys) {
  95.     let joinChar = '/';
  96.     let realKey = keys.join(joinChar);
  97.     return object[realKey];
  98. }
  99.  
  100. /* Takes an object whose structure is like:
  101.  * {
  102.  *      ...groups,
  103.  *      groupName : {
  104.  *          ...entries,
  105.  *          entryLabel : content
  106.  *          ...more entries,
  107.  *      }
  108.  *      ...more groups,
  109.  * }
  110.  * Takes renders each group in a CheckList. All of the groups are
  111.  * mutually exclusive with one another. When an option in a group is
  112.  * checked off, all options in every other group are shut off.
  113.  */
  114. class MutuallyExclusiveChoices extends Component {
  115.     constructor(props) {
  116.         super(props);
  117.  
  118.         /* Unfortunately, the implementation of this component has
  119.          * been made a little muddy by the fact that React doesn't
  120.          * work well when you make its state a nested object (an
  121.          * object that contains objects as values). To get around
  122.          * this, I've had to 'flatten' the state object by turning
  123.          * every reference like:
  124.          *      state[group][entry]
  125.          * into:
  126.          *      state[`${group}/${entry}`]
  127.          * It's an ugly hack, but at the very least, the semantics of
  128.          * accessing this dictionary aren't too different, since I
  129.          * made functions for flattening the object, and accessing the
  130.          * flattened object. Together, they create an 'interface' to
  131.          * the flattened object, such that code that depends on the
  132.          * flattened object doesn't have to understand that it's
  133.          * flattened. It's just a detail, now.
  134.          */
  135.         let state = {};
  136.         for (let groupLabel in props.entries) {
  137.             state[groupLabel] = {};
  138.             for (let entryLabel in props.entries[groupLabel]) {
  139.                 state[groupLabel][entryLabel] = false;
  140.             }
  141.         }
  142.         this.state = makeFlatDict(state);
  143.  
  144.         this.checkEntry = this.checkEntry.bind(this);
  145.     }
  146.  
  147.     checkEntry(group, label) {
  148.         console.log(`Check ${label} of ${group}.`);
  149.         let state = {};
  150.         for (let groupLabel in this.props.entries) {
  151.             let currentGroup = this.props.entries[groupLabel];
  152.             if (groupLabel === group) {
  153.                 state[group] = {};
  154.                 state[group][label] =
  155.                     !accessFlatDict(this.state, group, label);
  156.             } else {
  157.                 state[groupLabel] = makeFalseDict(Object.keys(currentGroup));
  158.             }
  159.         }
  160.         this.setState(makeFlatDict(state));
  161.     }
  162.  
  163.     render() {
  164.         console.log(this.state);
  165.         /* Make a copy of entries, since we're going to modify the
  166.          * entries slightly in order to comply with CheckList.
  167.          */
  168.         let lists = {};
  169.         for (let groupLabel in this.props.entries) {
  170.             lists[groupLabel] = {};
  171.             let group = this.props.entries[groupLabel];
  172.             for (let entryLabel in group) {
  173.                 let content = group[entryLabel];
  174.                 lists[groupLabel][entryLabel] = {
  175.                     content: content,
  176.                     checked : accessFlatDict(this.state, groupLabel,
  177.                                              entryLabel),
  178.                     onChange : () => this.checkEntry(groupLabel, entryLabel)
  179.                 };
  180.             }
  181.         }
  182.  
  183.         console.log(lists);
  184.  
  185.         let checkLists = Object.keys(lists).map(groupLabel => {
  186.             return <CheckList key={groupLabel} entries={lists[groupLabel]}/>;
  187.         })
  188.  
  189.         return <div>{checkLists}</div>;
  190.     }
  191. }
  192.  
  193. let entries = {
  194.     'like' : {
  195.         cat    : "I like cats (fuck that).",
  196.         dog    : "I like dogs (I'm a compulsive liar).",
  197.         iguana : "Iguanas are okay.",
  198.     },
  199.     'neutral' : {
  200.         mildLike    : "I'm not bothered by animals.",
  201.         trueNeutral : "I don't care about animals.",
  202.         mildDislike : "I can tolerate animals.",
  203.     },
  204.     'hate' : {
  205.         mildHate : "I don't like animals.",
  206.         hate     : "I'd be happy if I never had to see an anmial again.",
  207.         trueHate : "Lemme clean the puppy blood off my hands first " +
  208.                    "then I'll answer your question.",
  209.     }
  210. }
  211.  
  212. class App extends Component {
  213.   render() {
  214.       return <MutuallyExclusiveChoices entries={entries}/>;
  215.   }
  216. }
  217.  
  218. export default App;
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement