├── .gitignore ├── Dockerfile ├── Procfile ├── README.md ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html └── manifest.json └── src ├── App.css ├── App.js ├── App.test.js ├── Components ├── Attribute.jsx ├── BottomNavigation.jsx ├── Children.jsx ├── Condition.jsx ├── CurrentRules.jsx ├── CustomRowAction.jsx ├── MyModal.jsx ├── Property.jsx ├── Rule.jsx └── TopNavigation.jsx ├── data.js ├── index.css ├── index.js ├── logo.svg ├── pollyfills.js └── registerServiceWorker.js /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # build: 2 | 3 | FROM node:10 4 | 5 | # The Dockerfile's author 6 | LABEL Alec Maly 7 | 8 | # Create app directory 9 | WORKDIR /usr 10 | 11 | 12 | # Install app dependencies 13 | # A wildcard is used to ensure both package.json AND package-lock.json are copied 14 | # where available (npm@5+) 15 | COPY package.json ./ 16 | COPY ./public ./public 17 | COPY ./src ./src 18 | 19 | RUN npm install 20 | # If you are building your code for production 21 | # RUN npm ci --only=production 22 | 23 | # RUN npm run 24 | 25 | # Bundle app source 26 | # COPY . . 27 | 28 | EXPOSE 3000 29 | CMD [ "npm", "start" ] 30 | 31 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: npm start -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Website 2 | Site is hosted on [Heroku](http://sharepoint-json-helper.alecmaly.com/) 3 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sharepoint-helper", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "bootstrap": "^4.4.1", 7 | "react": "^16.4.1", 8 | "react-dom": "^16.13.1", 9 | "react-popper": "^1.0.0", 10 | "react-router-dom": "^4.3.1", 11 | "react-scripts": "1.1.4", 12 | "reactstrap": "^6.2.0" 13 | }, 14 | "scripts": { 15 | "start": "react-scripts start", 16 | "build": "react-scripts build", 17 | "test": "react-scripts test --env=jsdom", 18 | "eject": "react-scripts eject" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alecmaly/SharePoint-JSON-Helper/14f5103593494f973593cd5791c9119263b1e327/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 28 | 29 | 30 | 39 | SharePoint JSON Formatter 40 | 41 | 42 | 45 |
46 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | animation: App-logo-spin infinite 20s linear; 7 | height: 80px; 8 | } 9 | 10 | .App-header { 11 | background-color: #222; 12 | height: 150px; 13 | padding: 20px; 14 | color: white; 15 | } 16 | 17 | .App-title { 18 | font-size: 1.5em; 19 | } 20 | 21 | .App-intro { 22 | font-size: large; 23 | } 24 | 25 | .banner { 26 | padding-top: 3em; 27 | } 28 | 29 | body { 30 | padding-top: 5px; 31 | padding-bottom: 65px; 32 | background: linear-gradient(to bottom right, lightblue, lightseagreen) fixed; 33 | } 34 | 35 | .App { 36 | border-radius: 10px; 37 | background-color: #CBCBCB; 38 | background: linear-gradient(to bottom right, #CBCBCB, lightgrey); 39 | } 40 | 41 | .text-center { 42 | text-align: center; 43 | } 44 | 45 | .rule { 46 | cursor: pointer; 47 | padding-top: 3px; 48 | padding-bottom: 3px; 49 | border-style: solid; 50 | border-color: #606060; 51 | user-select: none; 52 | } 53 | 54 | .rules-container { 55 | border: 2px solid black; 56 | height: 150px; 57 | overflow: auto; 58 | } 59 | 60 | .delete-property { 61 | color: #ff6666; 62 | font-size: 200%; 63 | padding-right: .5em; 64 | } 65 | 66 | .delete-property:hover { 67 | color: #cc0000; 68 | } 69 | 70 | .operator { 71 | text-align: center; 72 | min-width: 5em; 73 | max-width: 5em; 74 | } 75 | 76 | .operand::placeholder { 77 | color: #b4b4b4; 78 | } 79 | 80 | .output { 81 | height: 22em; 82 | } 83 | 84 | .color { 85 | min-width: 6em; 86 | max-width: 10em; 87 | } 88 | 89 | .column { 90 | max-width: 5em; 91 | } 92 | 93 | .display-block { 94 | display: block; 95 | } 96 | 97 | .center-input { 98 | margin-left: auto; 99 | margin-right: auto; 100 | display: block; 101 | } 102 | 103 | .icon { 104 | cursor: pointer; 105 | user-select: none; 106 | } 107 | 108 | .new-property-link { 109 | font-size: 19px; 110 | color: #5078FF; 111 | text-decoration: underline; 112 | cursor: pointer; 113 | } 114 | 115 | .wrap-value { 116 | height: 2.5em; 117 | } 118 | 119 | .help-link { 120 | font-size: 19px; 121 | color: #5078FF; 122 | cursor: pointer; 123 | } 124 | 125 | .new-property-link:hover { 126 | color: blue; 127 | } 128 | 129 | .remove-text-highlighting { 130 | user-select: none; 131 | } 132 | 133 | .label { 134 | font-size: 13pt; 135 | padding-top: .5em; 136 | } 137 | 138 | .nav-bar { 139 | font-size: 15pt; 140 | text-decoration: none; 141 | color: grey; 142 | user-select: none; 143 | } 144 | 145 | 146 | .property:nth-child(odd) { 147 | background-color: lightgrey; 148 | } 149 | 150 | .property:nth-child(even) { 151 | background-color: #B8B8B8; 152 | } 153 | 154 | .nav-bar-top { 155 | padding-left: 1em; 156 | padding-right: 1em; 157 | font-size: 14pt; 158 | } 159 | 160 | .nav-button { 161 | background-color: #d3d3d3; 162 | border-radius: 10px; 163 | min-width: 7em; 164 | margin-left: 1em; 165 | margin-right: 1em; 166 | } 167 | 168 | .nav-button:hover { 169 | background-color: #B8B8B8; 170 | } 171 | 172 | .field-type { 173 | max-width: 15em; 174 | } 175 | 176 | .text-content { 177 | max-width: 15em; 178 | } 179 | 180 | .padded-row { 181 | padding: .75em .75em; 182 | } 183 | 184 | .add-remove-property-button { 185 | min-width: 12em; 186 | margin: 4px; 187 | } 188 | 189 | .condition-button { 190 | margin: 4px; 191 | margin-left: auto; 192 | margin-right: auto; 193 | display: block; 194 | } 195 | 196 | .modal-ok-button { 197 | width: 7em; 198 | } 199 | 200 | .fa { 201 | padding: .5em; 202 | font-size: 25px; 203 | width: 1.75em; 204 | height: 1.75em; 205 | text-align: center; 206 | text-decoration: none !important; 207 | margin: 4px; 208 | margin-bottom: 0px; 209 | border-radius: 50%; 210 | } 211 | 212 | .fa:hover { 213 | opacity: 0.7; 214 | } 215 | 216 | .fa-facebook { 217 | background: #3B5998; 218 | color: white; 219 | } 220 | 221 | .fa-twitter { 222 | background: #55ACEE; 223 | color: white; 224 | } 225 | 226 | .fa-linkedin { 227 | background: #007bb5; 228 | color: white; 229 | } 230 | 231 | .side-info { 232 | background-color: lightblue; 233 | width: 1.5em; 234 | min-height: 6em; 235 | position: relative; 236 | cursor: default; 237 | } 238 | 239 | .child-nav { 240 | border: 2px solid rgb(175, 144, 230); 241 | background-color: rgb(182, 161, 219); 242 | border-radius: 15px; 243 | width: 7em; 244 | 245 | } 246 | 247 | .move-button { 248 | width: 10em; 249 | border: 2px solid rgb(175, 144, 230); 250 | background-color: rgb(182, 161, 219); 251 | } 252 | 253 | .side-info span { 254 | /* Change to flexbox later */ 255 | top: 50%; 256 | left: .75em; 257 | transform-origin: left; 258 | position: absolute; 259 | text-overflow: hidden; 260 | white-space: nowrap; 261 | transform: rotate(-90deg) translate(-2em, 0); 262 | } 263 | 264 | .properties-color { 265 | border-radius: 5px; 266 | background-color: lightblue; 267 | } 268 | 269 | .attributes-color { 270 | border-radius: 5px; 271 | background-color: lightcoral; 272 | } 273 | 274 | .children-color { 275 | border-radius: 10px; 276 | background: linear-gradient(ivory, lightpink); 277 | background-color: lightpink; 278 | } 279 | 280 | 281 | .customRowActions-color { 282 | border-radius: 5px; 283 | background-color: lightgreen; 284 | } 285 | 286 | 287 | @media (min-width: 992px){ 288 | .nav-button { 289 | width: 15em; 290 | margin-left : 5em; 291 | margin-right : 5em; 292 | } 293 | } 294 | 295 | @media (max-width: 500px){ 296 | .nav-button { 297 | margin-left: .5em; 298 | margin-right: .5em; 299 | } 300 | 301 | .nav-button.copy { 302 | width: 9em; 303 | } 304 | } 305 | 306 | @keyframes App-logo-spin { 307 | from { transform: rotate(0deg); } 308 | to { transform: rotate(360deg); } 309 | } 310 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | import './App.css'; 4 | import TopNavigation from './Components/TopNavigation.jsx'; 5 | import BottomNavigation from './Components/BottomNavigation.jsx'; 6 | import CurrentRules from './Components/CurrentRules.jsx'; 7 | import Attribute from './Components/Attribute.jsx'; 8 | import Property from './Components/Property.jsx'; 9 | import Children from './Components/Children.jsx'; 10 | import CustomRowAction from './Components/CustomRowAction.jsx'; 11 | import Condition from './Components/Condition.jsx'; 12 | import MyModal from './Components/MyModal.jsx'; 13 | 14 | 15 | // polyfill for .repeat function in IE11 16 | import './pollyfills.js'; 17 | 18 | // data 19 | import data from './data.js'; 20 | 21 | import { 22 | Container, 23 | Jumbotron, 24 | Navbar, 25 | NavbarBrand, 26 | Nav, 27 | NavItem, 28 | Row, 29 | Col, 30 | Button, 31 | Input, 32 | Label, 33 | Modal, 34 | ModalHeader, 35 | ModalBody, 36 | ModalFooter 37 | } from 'reactstrap'; 38 | import { SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER } from 'constants'; 39 | 40 | class App extends Component { 41 | 42 | constructor(props) { 43 | super(props); 44 | this.navButtonClick = this.navButtonClick.bind(this); 45 | this.newKey = this.newKey.bind(this); 46 | this.updateKey = this.updateKey.bind(this); 47 | this.buildKey = this.buildKey.bind(this); 48 | this.deleteKey = this.deleteKey.bind(this); 49 | this.clearAllKeys = this.clearAllKeys.bind(this); 50 | this.handleInputChange = this.handleInputChange.bind(this); 51 | this.resetForm = this.resetForm.bind(this); 52 | this.toggleModal = this.toggleModal.bind(this); 53 | this.displayModal = this.displayModal.bind(this); 54 | this.moveChild = this.moveChild.bind(this); 55 | 56 | this.buildJSON = this.buildJSON.bind(this); 57 | // helper functions for buildJSON() 58 | this.buildValue = this.buildValue.bind(this); 59 | this.parseString = this.parseString.bind(this); 60 | this.parseFunctions = this.parseFunctions.bind(this); 61 | this.parseMathOperations = this.parseMathOperations.bind(this); 62 | this.validParen = this.validParen.bind(this); 63 | 64 | 65 | //temp 66 | this.updateAttribute = this.updateAttribute.bind(this); 67 | this.updateProperty = this.updateProperty.bind(this); 68 | this.updateCRA = this.updateCRA.bind(this); 69 | this.updateChildren = this.updateChildren.bind(this); 70 | // end temp 71 | 72 | 73 | this.state = { 74 | attributes: [], 75 | properties: [], 76 | customRowActions: [], 77 | children: [], 78 | JSON: '', 79 | elmType: 'div', 80 | fieldType: 'Choice', 81 | textContent: '@currentField', 82 | modal: false, 83 | modalHeader: '', 84 | modalBody: '', 85 | modalTab: '1', 86 | attributeChoices: data.Attributes, 87 | propertyChoices: data.CSSProperties, 88 | customRowActionChoices: data.customRowActions, 89 | colors: data.customColors 90 | }; 91 | } 92 | 93 | componentDidMount() { 94 | this.buildJSON(); 95 | } 96 | 97 | 98 | moveChild(ele, index, obj) { 99 | let arr = this.state.children.slice(); 100 | let temp = arr[index]; 101 | 102 | 103 | switch (ele.target.attributes.value.value) { 104 | case 'top': 105 | arr.splice(index, 1); 106 | arr.splice(0, 0, temp); 107 | break; 108 | case 'up': 109 | arr.splice(index, 1); 110 | arr.splice((index === 0 ? 0 : index-1), 0, temp); 111 | break; 112 | case 'down': 113 | arr.splice(index, 1); 114 | arr.splice(index+1, 0, temp); 115 | break; 116 | case 'bottom': 117 | arr.splice(index, 1); 118 | arr.splice(arr.length, 0, temp); 119 | break; 120 | } 121 | //arr.splice(index, 0, temp); 122 | 123 | this.setState({ 124 | children: arr 125 | }, this.buildJSON()) 126 | 127 | } 128 | 129 | newKey(key) { 130 | let arr = []; 131 | if (key === 'addChildren') { 132 | key = 'children'; 133 | arr = this.state[key].slice(); 134 | arr.push({'name':'', 'value':[]}); 135 | } 136 | else { 137 | arr = this.state[key].slice(); 138 | console.log(arr); 139 | arr.splice(0,0, {'name': '', 'value':[]}); 140 | } 141 | 142 | if (key === 'children') 143 | this.setState({ textContent: '' }); 144 | 145 | this.buildKey(key, arr); 146 | } 147 | 148 | updateKey(key, index, name, value) { 149 | let arr = this.state[key].slice(); 150 | arr[index] = ({'name': name, 'value': value}); 151 | this.setState({ 152 | [key]: arr 153 | }, () => { this.buildJSON() }); 154 | } 155 | 156 | clearAllKeys(key) { 157 | this.setState({ 158 | [key]: [] 159 | }, () => { this.buildJSON() } ); 160 | } 161 | 162 | // This function builds properties from arr input. 163 | // Needed to build this way for state to reset properly. 164 | updateAttribute(index, key, value) { 165 | this.updateKey('attributes', index, key, value); 166 | } 167 | 168 | updateProperty(index, key, value) { 169 | this.updateKey('properties', index, key, value); 170 | } 171 | 172 | updateCRA(index, key, value) { 173 | this.updateKey('customRowActions', index, key, value); 174 | } 175 | 176 | updateChildren(index, key, value) { 177 | this.updateKey('children', index, key, value); 178 | } 179 | 180 | 181 | buildKey(key, arr) { 182 | this.setState({ 183 | [key]: [] 184 | }, () => { 185 | this.setState({ 186 | [key]: arr 187 | }, () => { this.buildJSON() }) 188 | }); 189 | } 190 | 191 | deleteKey(key, index) { 192 | var arr = this.state[key].slice(); 193 | 194 | var deleteAtt = arr[index]; 195 | arr.splice(index, 1); 196 | this.setState({ 197 | [key]: [] 198 | }, () => { 199 | this.setState({ 200 | [key]: arr 201 | }, () => { this.buildJSON() }); 202 | }); 203 | } 204 | 205 | 206 | navButtonClick(event) { 207 | let arr = []; 208 | switch (event.target.name) { 209 | case 'copy to clipboard': 210 | var copyText = document.querySelector(".output"); 211 | copyText.select(); 212 | document.execCommand("copy"); 213 | this.setState({ 214 | modalHeader: 'Copy to Clipboard', 215 | modalBody: '
JSON has been copied to the clipboard!' 216 | }) 217 | this.toggleModal(); 218 | break; 219 | case 'reset': 220 | this.resetForm(); 221 | break; 222 | case 'attribute': 223 | arr = this.state.attributes.slice(); 224 | arr.splice(0, 0, {'name': event.target.value, 'value': event.target.title}); 225 | this.buildKey('attributes', arr); 226 | break; 227 | case 'property': 228 | arr = this.state.properties.slice(); 229 | arr.splice(0, 0, {'name': event.target.value, 'value': event.target.title}); 230 | this.buildKey('properties', arr); 231 | break; 232 | case 'CRA': 233 | arr = this.state.customRowActions.slice(); 234 | arr.splice(0, 0, {'name': "actionParams", 'value': "{\\\"id\\\": \\\"FLOW_ID\\\"}"}); 235 | arr.splice(0, 0, {'name': "action", 'value': "executeFlow"}); 236 | this.buildKey('customRowActions', arr); 237 | break; 238 | 239 | 240 | 241 | case 'template': 242 | switch (event.target.value) { 243 | case 'Completed/In Progress/Late': 244 | this.resetForm(); 245 | arr = data.template_completedInProgressLate; 246 | this.buildKey('properties', arr.properties); 247 | break; 248 | case 'Data Bars 1': 249 | this.resetForm(); 250 | arr = data.template_dataBars_one; 251 | 252 | this.buildKey('properties', arr.properties); 253 | this.buildKey('attributes', arr.attributes); 254 | break; 255 | case 'Data Bars 100': 256 | this.resetForm(); 257 | arr = data.template_dataBars_hundred; 258 | 259 | this.buildKey('properties', arr.properties); 260 | this.buildKey('attributes', arr.attributes); 261 | break; 262 | case 'Button with link + icon': 263 | this.resetForm(); 264 | arr = data.template_buttonWithLinkandIcon; 265 | 266 | this.setState({'elmType': 'button', textContent: ''}) 267 | this.buildKey('properties', arr.properties); 268 | this.buildKey('children', arr.children) 269 | break; 270 | } 271 | } 272 | } 273 | 274 | resetForm() { 275 | this.setState({ 276 | elmType: 'div', 277 | fieldType: 'Choice', 278 | textContent: '@currentField' 279 | }); 280 | this.clearAllKeys('attributes'); 281 | this.clearAllKeys('properties'); 282 | this.clearAllKeys('customRowActions'); 283 | this.clearAllKeys('children'); 284 | window.scrollTo(0, 0); // scroll to top of screen 285 | } 286 | 287 | 288 | parseMathOperations(operator, str, indent) { 289 | let value = str; 290 | switch (operator) { 291 | case '//': 292 | if (str.includes('https://') || str.includes('http://')) 293 | break; 294 | default: 295 | if (str.toString().includes(operator)) { 296 | let temp_value = '\t'.repeat(++indent) + '"operator": "' + operator.charAt(1) + '",\n' + '\t'.repeat(++indent) + ' "operands": [\n'; 297 | indent++; 298 | str.split(operator).forEach((val, i) => { temp_value = temp_value + (this.state.fieldType !== 'Number' ? '\t'.repeat(indent) + this.parseString(val, indent) : this.parseString(val, indent).slice(1, -1)) + ',\n' }); 299 | indent--; 300 | // remove , from last item in list and add closing brackets 301 | value = '{\n' + temp_value.slice(0,-2) + (str.toString().split(operator.charAt(1)).length === 1 ? ',\n' + '\t'.repeat(indent + 1) + '""' : '') + '\n' + '\t'.repeat(indent) + ']\n}'; 302 | } 303 | } 304 | 305 | return value; 306 | } 307 | 308 | parseFunctions(str, indent) { 309 | let f = ''; 310 | let value = str; 311 | let acceptedFunctions = ['toString', 'Number', 'Date', 'cos', 'sin', 'toLocaleString', 'toLocaleDateString', 'toLocaleTimeString']; 312 | 313 | // set index to innermost '(' 314 | let index = str.lastIndexOf('('); 315 | 316 | // before(inner)after 317 | let before = str.substring(0, index - f.length + 1); 318 | let inner = str.substring(index + 1, str.indexOf(')', index)); 319 | let after = str.substring(str.indexOf(')', index) + 1, str.length); 320 | 321 | // check what function is being called, remove function name from 'before' variable 322 | for (var i = 0; i < acceptedFunctions.length; i++) { 323 | if (before.slice(-acceptedFunctions[i].length - 1, -1) === acceptedFunctions[i]) { 324 | f = acceptedFunctions[i]; 325 | before = before.slice(0, -acceptedFunctions[i].length - 1); 326 | break; 327 | } 328 | } 329 | 330 | // check what function is being called - f === '' is no funciton - () order of operations 331 | if (f === '') { 332 | before = before.slice(0, -1); 333 | str = this.parseString(inner, indent);//this.parseString(inner, index) + after; 334 | 335 | value = this.parseString(before + '~' + after, indent).replace('~', str); 336 | //value = value.slice(1, -1); 337 | } else { // order of operations w/ () - not a function call 338 | let prefix = '\t'.repeat(++indent) + '"operator": "' + f + '()",\n' + '\t'.repeat(++indent) + ' "operands": [\n'; 339 | 340 | str = this.parseString(inner, indent);//this.parseString(inner, index) + after; 341 | str = prefix + str; 342 | 343 | value = '{\n' + '\t'.repeat(++indent) + str + '\n' + '\t'.repeat(indent) + ']\n}'; 344 | value = this.parseString(before + '~' + after, indent).replace('~', value); 345 | } 346 | 347 | return value; 348 | } 349 | 350 | // validate proper function (prevents stack overflow) 351 | validParen(str) { 352 | let arr = []; 353 | for (let i = 0; i < str.length; i++) { 354 | if (str.charAt(i) === '(') 355 | arr.push('('); 356 | else if (str.charAt(i) === ')') 357 | arr.pop(); 358 | } 359 | return (arr.length === 0 ? true : false); 360 | } 361 | 362 | parseString(str, indent) { 363 | 364 | // replace '%5F' with '_' inside variable names 365 | if (str !== undefined) { 366 | str = str.replace(/\[\$(.*?)%5F(.*?)\]/g, (match) => 367 | { 368 | return match.replace(/%5F/g, '_') 369 | }); 370 | } 371 | 372 | let value = '"' + str + '"'; 373 | 374 | // parses functions and () order of operations 375 | if (this.validParen(str) && str.indexOf('(') >= 0) { 376 | value = value.replace(str, this.parseFunctions(str, indent)); 377 | } 378 | value = value.replace(str, this.parseMathOperations('--', str, indent)); 379 | value = value.replace(str, this.parseMathOperations('++', str, indent)); 380 | value = value.replace(str, this.parseMathOperations('//', str, indent)); 381 | value = value.replace(str, this.parseMathOperations('**', str, indent)); 382 | 383 | 384 | // backslashes for Flow actionParameters command 385 | if ( value.charAt(1) === '{' || value === '"~"') { 386 | value = value.slice(1, -1); 387 | } 388 | 389 | 390 | // add " back to Flow Parameters 391 | if (value.includes("{\\\"id\\\": \\\"")) 392 | value = '"' + value + '"'; 393 | 394 | return value; 395 | } 396 | 397 | buildValue(type, obj, indent) { 398 | let output = ''; 399 | let key = ''; 400 | switch (type) { 401 | case 'attribute': 402 | output = '\t'.repeat(indent) + '"attributes": {'; 403 | break; 404 | case 'property': 405 | output = '\t'.repeat(indent) + '"style": {'; 406 | break; 407 | case 'customRowActions': 408 | output = output = '\t'.repeat(indent) + '"customRowAction": {'; 409 | break; 410 | case 'children': 411 | output = output = '\t'.repeat(indent) + '"children": ['; 412 | this.state[type].forEach((obj) => { 413 | output = output + obj.value + ', '; 414 | }); 415 | if (this.state[type].length > 0) 416 | output = output.slice(0, -2); 417 | output = output + ']'; 418 | break; 419 | } 420 | 421 | if (type !== 'children') { 422 | 423 | obj.forEach((ele, i) => { 424 | // NEED TO CHECK IF RULES/CONDITIONS ARE APPLIED OR NOT (FIX HERE) 425 | // craft value 426 | let value = ''; 427 | 428 | 429 | if (typeof ele.value === 'string') { 430 | if (!(ele.name === 'class' || ele.name === 'iconName')) { 431 | value = this.parseString(ele.value, indent); 432 | } else { 433 | value = '"' + ele.value + '"'; 434 | } 435 | } 436 | 437 | // craft value 438 | output = output + ` 439 | "` + ele.name + '": ' + value; 440 | 441 | 442 | if (typeof ele.value === 'object') { 443 | ele.value.forEach( (condition) => { 444 | output = output + `\n 445 | { 446 | "operator": "?", 447 | "operands": [ 448 | { 449 | "operator": "` + condition.operator + `", 450 | "operands": [ 451 | ` + (this.state.fieldType !== 'Number' ? this.parseString(condition.operand, indent) : this.parseString(condition.operand, indent).slice(1, -1)) + `, 452 | ` + (this.state.fieldType !== 'Number' ? this.parseString(condition.operand2, indent) : this.parseString(condition.operand2, indent).slice(1, -1)) + ` 453 | ] 454 | }, 455 | ` + this.parseString(condition.value, indent) + `, ` 456 | }) 457 | } 458 | 459 | // 460 | output = output + '""'.repeat( (typeof ele.value === 'object' ? 1 : 0) ); 461 | 462 | // add closing brackets based on number of properties being evaluated 463 | output = output + `\n\t] 464 | }`.repeat( (typeof ele.value === 'string' ? 0 : ele.value.length) ); 465 | 466 | // add commas for all properties until the last one 467 | output = output + ','.repeat( (i !== obj.length - 1 ? 1 : 0) ); 468 | }); 469 | 470 | output = output + '\n' + '\t'.repeat(indent) + '}'; 471 | } 472 | 473 | return output; 474 | } 475 | 476 | 477 | buildJSON() { 478 | // console.log(this.state) 479 | let indent = 0; 480 | var JSON_Body = ``; 481 | var JSON_Header = 482 | `{ 483 | "elmType": "` + this.state.elmType + `", 484 | "txtContent": ` + this.parseString(this.state.textContent, indent) + `, 485 | `; 486 | var JSON_Footer = ``; 487 | var JSON_Properties = ''; 488 | var JSON_Attributes = ''; 489 | var JSON_CustomRowActions = ''; 490 | var JSON_Children = ''; 491 | 492 | // BUILD JSON HERE in forEach loop for ATTRIBUTES 493 | indent++; 494 | 495 | JSON_Properties = JSON_Properties + this.buildValue('property', this.state.properties, indent); 496 | JSON_Attributes = JSON_Attributes + this.buildValue('attribute', this.state.attributes, indent); 497 | JSON_CustomRowActions = JSON_CustomRowActions + this.buildValue('customRowActions', this.state.customRowActions, indent); 498 | JSON_Children = JSON_Children + this.buildValue('children', this.state.children, indent); 499 | 500 | // JSON Footer 501 | JSON_Footer = `\n}`; 502 | 503 | // build body of properties and attributes 504 | JSON_Body = JSON_Attributes + ",\n" + JSON_Properties + ",\n" + JSON_CustomRowActions + ",\n" + JSON_Children ; 505 | 506 | // Set Output 507 | this.setState({ 508 | JSON: JSON_Header + JSON_Body + JSON_Footer 509 | }); 510 | } 511 | 512 | handleInputChange(event) { 513 | const target = event.target; 514 | const value = target.type === 'checkbox' ? target.checked : target.value; 515 | const name = target.name; 516 | this.setState({ 517 | [name]: value 518 | }, () => { this.buildJSON() }); 519 | } 520 | 521 | toggleModal() { 522 | this.setState({ 523 | modal: !this.state.modal 524 | }); 525 | } 526 | 527 | displayModal(event) { 528 | switch(event.target.attributes.value.value) { 529 | case 'text content help': 530 | this.setState({ 531 | modalHeader: 'Text Context Help', 532 | modalBody: '
This value will be displayed in each cell', 533 | modalTab: '1' 534 | }, () => { this.toggleModal() } ) 535 | break; 536 | case 'iconName': 537 | this.setState({ 538 | modalHeader: 'iconName Help', 539 | modalBody: `
540 | Use SharePoint Fabric Icons.
541 |
542 | Don't forget to check the left nav for more icons!
543 | Brand icons
544 | Localization`, 545 | modalTab: '1' 546 | }, () => { this.toggleModal() } ) 547 | break; 548 | case 'class': 549 | this.setState({ 550 | modalHeader: 'class Help', 551 | modalBody: `
552 | Here you can use SharePoint Framework classes.
553 | Don't forget about Animations and 554 | Typography!
555 |
`, 556 | modalTab: '1' 557 | }, () => { this.toggleModal() } ) 558 | break; 559 | case 'Operand': 560 | this.setState({ 561 | modalHeader: 'Operand Help', 562 | modalBody: '
This value will be compared with Operand2, uses Value when true.', 563 | modalTab: '1' 564 | }, () => { this.toggleModal() } ) 565 | break; 566 | case 'Operand2': 567 | this.setState({ 568 | modalHeader: 'Operand Help', 569 | modalBody: '
This value will be compared with Operand, uses Value when true.', 570 | modalTab: '1' 571 | }, () => { this.toggleModal() } ) 572 | break; 573 | 574 | default: 575 | this.setState({ 576 | modalHeader: 'Value Help', 577 | modalBody: `
Enter the value to use.`, 578 | modalTab: '1' 579 | }, () => { this.toggleModal() } ) 580 | break; 581 | 582 | } 583 | } 584 | 585 | render() { 586 | return ( 587 | 588 |
589 | 590 | 591 | 592 | 593 |

SharePoint Helper

594 |

Used for building conditional formatting JSON - YouTube Tutorial
595 | Note: Rules are read from top to bottom. 596 |

597 | 598 | 599 | 605 | 606 |
607 |
608 |
609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | {/* */} 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | {/* 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | */} 642 | 643 | {/* */} 644 | 645 | 646 | 647 | 648 | 649 |
650 | 651 | {/* Attributes */} 652 | 653 |
654 | Attributes 655 |
656 | 657 | 658 |
659 | 660 |
661 |
662 | 663 |
664 | 665 |
666 | 667 | {Object.keys(this.state.attributes).map((key, i) => { 668 | return () 669 | })} 670 | 671 | 672 | 673 |
674 | 675 | {/* Properties */} 676 | 677 |
678 | Properties 679 |
680 | 681 | 682 |
683 | 684 |
685 |
686 | 687 |
688 | 689 |
690 | 691 | {Object.keys(this.state.properties).map((key, i) => { 692 | return () 693 | })} 694 | 695 | 696 | 697 |
698 | 699 | {/* Custom Row Action */} 700 | 701 |
702 | Row Actions 703 |
704 | 705 | 706 |
707 | 708 |
709 |
710 | 711 |
712 | 713 |
714 | 715 | {Object.keys(this.state.customRowActions).map((key, i) => { 716 | return ( 717 | ) 718 | })} 719 | 720 | 721 |
722 | 723 | 724 | {/* Children */} 725 | 726 | 727 | {this.state.children.length !== 0 ? 728 | 729 |
730 | 731 |
732 |
733 | 734 |
735 | 736 |
737 | : '' } 738 | 739 | 740 | 741 | {Object.keys(this.state.children).map((key, i) => { 742 | return () 743 | })} 744 | 745 | 746 | 747 | 748 |
749 | 750 |
751 |
752 | 753 |
754 |
755 | 756 | 757 |
758 | 759 | 760 | 761 | 762 | 763 | 764 | 765 | 766 | 767 |
768 | 769 | ); 770 | } 771 | } 772 | 773 | export default App; 774 | -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /src/Components/Attribute.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { 3 | Row, 4 | Col, 5 | Label, 6 | Input, 7 | InputGroup, 8 | InputGroupButtonDropdown, 9 | DropdownMenu, 10 | DropdownToggle, 11 | DropdownItem, 12 | Container, 13 | Button 14 | } from 'reactstrap'; 15 | import Condition from './Condition.jsx'; 16 | 17 | 18 | 19 | class Attribute extends Component { 20 | constructor(props) { 21 | super(props); 22 | this.setValue = this.setValue.bind(this); 23 | this.handleInputChange = this.handleInputChange.bind(this); 24 | this.toggleAttributeDropdownOpen = this.toggleAttributeDropdownOpen.bind(this); 25 | this.toggleValueDropdownOpen = this.toggleValueDropdownOpen.bind(this); 26 | this.toggleConditionals = this.toggleConditionals.bind(this); 27 | this.moveRule = this.moveRule.bind(this); 28 | this.state = { 29 | key: this.props.attributes[this.props.index].name, 30 | value: this.props.attributes[this.props.index].value, 31 | conditionalFlag: (typeof this.props.attributes[this.props.index].value === 'object' && this.props.attributes[this.props.index].value.length ? true : false), // True if rules have been defined, otherwise false 32 | keyDropdownOpen: false, 33 | valueDropdownOpen: false, 34 | prevIndex: this.props.index 35 | }; 36 | } 37 | 38 | componentDidUpdate() { 39 | if (this.state.prevIndex !== this.props.index) { 40 | this.setState({ 41 | key: this.props.attributes[this.props.index].name, 42 | value: this.props.attributes[this.props.index].value, 43 | conditionalFlag: (typeof this.props.attributes[this.props.index].value === 'object' && this.props.attributes[this.props.index].value.length ? true : false), // True if rules have been defined, otherwise false 44 | keyDropdownOpen: false, 45 | valueDropdownOpen: false, 46 | prevIndex: this.props.index 47 | }, () => { this.props.buildJSON() }); 48 | } 49 | } 50 | 51 | // called inside Condition component 52 | moveRule(ele, index) { 53 | let arr = this.state.value.slice(); 54 | let temp = arr[index]; 55 | 56 | console.log(arr); 57 | switch(ele.target.attributes.value.value) { 58 | case 'up': 59 | arr.splice(index, 1); 60 | arr.splice((index === 0 ? 0 : index-1), 0, temp); 61 | break; 62 | case 'down': 63 | arr.splice(index, 1); 64 | arr.splice(index+1, 0, temp); 65 | break; 66 | } 67 | 68 | 69 | this.setState({ 70 | value: arr 71 | }, () => { this.props.updateAttribute(this.props.index, this.state.key, this.state.value) }); 72 | } 73 | 74 | 75 | setValue(obj) { 76 | this.setState({ 77 | value: obj 78 | }) 79 | } 80 | 81 | 82 | toggleConditionals() { 83 | this.setState({ 84 | conditionalFlag: !this.state.conditionalFlag, 85 | value: [] 86 | }, () => { this.props.updateAttribute(this.props.index, this.state.key, this.state.value) }); 87 | } 88 | 89 | handleInputChange(event) { 90 | const target = event.target; 91 | const name = target.name; 92 | 93 | let resetValue = ''; 94 | 95 | if (name === 'key') { 96 | let value = target.type === 'button' ? target.innerHTML : target.value; 97 | resetValue = []; 98 | this.setState({ 99 | [name]: value, 100 | value: resetValue 101 | }, () => { this.props.updateAttribute(this.props.index, value, '') }) 102 | 103 | } else { 104 | let value = target.type === 'button' ? ( this.state.key === 'background-color' ? this.props.colors[target.innerHTML] : target.innerHTML) : target.value; 105 | resetValue = value; 106 | this.setState({ 107 | [name]: value, 108 | value: resetValue 109 | }, () => { this.props.updateAttribute(this.props.index, this.state.key, this.state.value) }) 110 | } 111 | 112 | } 113 | 114 | setConditionalValue(eleValue) { 115 | this.setState({ 116 | value: eleValue 117 | }); 118 | } 119 | 120 | toggleAttributeDropdownOpen() { 121 | this.setState({ 122 | value: '', 123 | keyDropdownOpen: !this.state.keyDropdownOpen 124 | }); 125 | } 126 | 127 | toggleValueDropdownOpen() { 128 | this.setState({ 129 | valueDropdownOpen: !this.state.valueDropdownOpen 130 | }); 131 | } 132 | 133 | render() { 134 | return ( 135 | 136 | 137 | 138 | 139 |
140 | this.props.deleteKey('attributes', this.props.index)}>X 141 |
142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | {Object.keys(this.props.attributeChoices).map((key, i) => { 151 | return ({key}); 152 | })} 153 | 154 | 155 | 156 | 157 | 158 | 159 | {!this.state.conditionalFlag ? 160 |
161 | 162 | 163 | 164 | 166 | 167 | 168 | { (this.props.attributeChoices[this.state.key] !== undefined && this.props.attributeChoices[this.state.key].options !== undefined 169 | ? this.props.attributeChoices[this.state.key].options.split(',').map((key, i) => { 170 | return ({key}); 171 | }) : (this.state.key === 'background-color' ? Object.keys(this.props.colors).map((key, i) => { 172 | return ({key}); 173 | }) 174 | : '') )} 175 | 176 | 177 | 178 |
179 | : ''} 180 | 181 |
182 | 183 |
184 | 185 |
186 |
187 | 188 | {this.state.conditionalFlag ? : ''} 189 | 190 |
191 | ) 192 | } 193 | } 194 | 195 | export default Attribute; -------------------------------------------------------------------------------- /src/Components/BottomNavigation.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { 3 | Navbar, 4 | NavbarBrand, 5 | Nav, 6 | NavItem, 7 | NavLink, 8 | DropdownToggle, 9 | DropdownMenu, 10 | DropdownItem, 11 | UncontrolledDropdown, 12 | Button 13 | } from 'reactstrap'; 14 | import '../App.css'; 15 | 16 | class BotttomNavigation extends Component { 17 | render() { 18 | return ( 19 |
20 | 21 | 30 | 31 |
32 | ) 33 | } 34 | 35 | } 36 | 37 | export default BotttomNavigation; -------------------------------------------------------------------------------- /src/Components/Children.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | import '../App.css'; 4 | import TopNavigation from './TopNavigation.jsx'; 5 | import BottomNavigation from './BottomNavigation.jsx'; 6 | import CurrentRules from './CurrentRules.jsx'; 7 | import Property from './Property.jsx'; 8 | import Attribute from './Attribute.jsx'; 9 | import CustomRowAction from './CustomRowAction.jsx'; 10 | import Condition from './Condition.jsx'; 11 | import MyModal from './MyModal.jsx'; 12 | 13 | 14 | // polyfill for .repeat function in IE11 15 | import '../pollyfills.js'; 16 | 17 | // data 18 | import data from '../data.js'; 19 | 20 | import { 21 | Container, 22 | Jumbotron, 23 | Navbar, 24 | NavbarBrand, 25 | Nav, 26 | NavItem, 27 | Row, 28 | Col, 29 | Button, 30 | Input, 31 | Label, 32 | Modal, 33 | ModalHeader, 34 | ModalBody, 35 | ModalFooter 36 | } from 'reactstrap'; 37 | import { SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER } from 'constants'; 38 | 39 | class Children extends Component { 40 | 41 | constructor(props) { 42 | super(props); 43 | this.newKey = this.newKey.bind(this); 44 | this.updateKey = this.updateKey.bind(this); 45 | this.buildKey = this.buildKey.bind(this); 46 | this.deleteKey = this.deleteKey.bind(this); 47 | this.clearAllKeys = this.clearAllKeys.bind(this); 48 | this.handleInputChange = this.handleInputChange.bind(this); 49 | this.resetForm = this.resetForm.bind(this); 50 | 51 | this.buildJSON = this.buildJSON.bind(this); 52 | // helper functions for buildJSON() 53 | this.buildValue = this.buildValue.bind(this); 54 | this.parseString = this.parseString.bind(this); 55 | this.parseFunctions = this.parseFunctions.bind(this); 56 | this.parseMathOperations = this.parseMathOperations.bind(this); 57 | this.validParen = this.validParen.bind(this); 58 | 59 | //temp 60 | this.updateAttribute = this.updateAttribute.bind(this); 61 | this.updateProperty = this.updateProperty.bind(this); 62 | this.updateCRA = this.updateCRA.bind(this); 63 | // end temp 64 | 65 | this.state = { 66 | attributes: this.props.children[this.props.index].name.attributes || [], 67 | properties: this.props.children[this.props.index].name.properties || [], 68 | customRowActions: this.props.children[this.props.index].name.customRowActions || [], 69 | JSON: this.props.children[this.props.index].name.JSON || '{}', 70 | elmType: this.props.children[this.props.index].name.elmType || 'span', 71 | fieldType: this.props.children[this.props.index].name.fieldType || 'Choice', 72 | textContent: this.props.children[this.props.index].name.textContent, 73 | resetChildren: this.props.resetChildren, 74 | modal: false, 75 | modalHeader: '', 76 | modalBody: '', 77 | modalTab: '1', 78 | attributeChoices: data.Attributes, 79 | propertyChoices: data.CSSProperties, 80 | customRowActionChoices: data.customRowActions, 81 | colors: data.customColors, 82 | prevIndex: this.props.index 83 | }; 84 | } 85 | 86 | // FIX - Children not moving up and down - textContent not resetting properly 87 | 88 | componentDidMount() { 89 | this.buildJSON(); 90 | } 91 | 92 | 93 | componentDidUpdate() { 94 | // build JSON for updated children 95 | if (this.props.children[this.props.index].name.length === 0) 96 | this.buildJSON(); 97 | 98 | 99 | if (this.state.JSON !== this.props.children[this.props.index].value) { 100 | this.setState({ 101 | attributes: this.props.children[this.props.index].name.attributes || [], 102 | properties: this.props.children[this.props.index].name.properties || [], 103 | customRowActions: this.props.children[this.props.index].name.customRowActions || [], 104 | JSON: this.props.children[this.props.index].name.JSON || '{}', 105 | elmType: this.props.children[this.props.index].name.elmType || 'span', 106 | fieldType: this.props.children[this.props.index].name.fieldType || 'Choice', 107 | textContent: this.props.children[this.props.index].name.textContent || (this.props.children[this.props.index].name.textContent === undefined ? '@currentField' : ''), 108 | 109 | prevIndex: this.props.index 110 | }, () => { this.props.buildJSON();}); 111 | } 112 | 113 | 114 | } 115 | 116 | 117 | 118 | newKey(key) { 119 | var arr = this.state[key].slice(); 120 | 121 | arr.splice(0,0, {'name': '', 'value':[]}); 122 | this.buildKey(key, arr); 123 | } 124 | 125 | updateKey(key, index, name, value) { 126 | var arr = this.state[key].slice(); 127 | arr[index] = ({'name': name, 'value': value}); 128 | this.setState({ 129 | [key]: arr 130 | }, () => { this.buildJSON() }); 131 | } 132 | 133 | clearAllKeys(key) { 134 | this.setState({ 135 | [key]: [] 136 | }, () => { this.buildJSON() } ); 137 | } 138 | 139 | // This function builds properties from arr input. 140 | // Needed to build this way for state to reset properly. 141 | updateAttribute(index, key, value) { 142 | this.updateKey('attributes', index, key, value); 143 | } 144 | 145 | updateProperty(index, key, value) { 146 | this.updateKey('properties', index, key, value); 147 | } 148 | 149 | updateCRA(index, prop, value) { 150 | this.updateKey('customRowActions', index, prop, value); 151 | } 152 | 153 | 154 | buildKey(key, arr) { 155 | this.setState({ 156 | [key]: [] 157 | }, () => { 158 | this.setState({ 159 | [key]: arr 160 | }, () => { this.buildJSON() }) 161 | }); 162 | } 163 | 164 | deleteKey(key, index) { 165 | var arr = this.state[key].slice(); 166 | 167 | var deleteAtt = arr[index]; 168 | arr.splice(index, 1); 169 | this.setState({ 170 | [key]: [] 171 | }, () => { 172 | this.setState({ 173 | [key]: arr 174 | }, () => { this.buildJSON() }); 175 | }); 176 | } 177 | 178 | resetForm() { 179 | this.setState({ 180 | elmType: 'div', 181 | fieldType: 'Choice', 182 | textContent: '@currentField' 183 | }); 184 | this.clearAllKeys('attributes'); 185 | this.clearAllKeys('properties'); 186 | this.clearAllKeys('customRowActions'); 187 | window.scrollTo(0, 0); // scroll to top of screen 188 | } 189 | 190 | 191 | parseMathOperations(operator, str, indent) { 192 | let value = str; 193 | switch (operator) { 194 | case '//': 195 | if (str.includes('https://') || str.includes('http://')) 196 | break; 197 | default: 198 | if (str.toString().includes(operator)) { 199 | let temp_value = '\t'.repeat(++indent) + '"operator": "' + operator.charAt(1) + '",\n' + '\t'.repeat(++indent) + ' "operands": [\n'; 200 | indent++; 201 | str.split(operator).forEach((val, i) => { temp_value = temp_value + (this.state.fieldType !== 'Number' ? '\t'.repeat(indent) + this.parseString(val, indent) : this.parseString(val, indent).slice(1, -1)) + ',\n' }); 202 | indent--; 203 | // remove , from last item in list and add closing brackets 204 | value = '{\n' + temp_value.slice(0,-2) + (str.toString().split(operator.charAt(1)).length === 1 ? ',\n' + '\t'.repeat(indent + 1) + '""' : '') + '\n' + '\t'.repeat(indent) + ']\n}'; 205 | } 206 | } 207 | 208 | return value; 209 | } 210 | 211 | parseFunctions(str, indent) { 212 | let f = ''; 213 | let value = str; 214 | let acceptedFunctions = ['toString', 'Number', 'Date', 'cos', 'sin', 'toLocaleString', 'toLocaleDateString', 'toLocaleTimeString']; 215 | 216 | // set index to innermost '(' 217 | let index = str.lastIndexOf('('); 218 | 219 | // before(inner)after 220 | let before = str.substring(0, index - f.length + 1); 221 | let inner = str.substring(index + 1, str.indexOf(')', index)); 222 | let after = str.substring(str.indexOf(')', index) + 1, str.length); 223 | 224 | // check what function is being called, remove function name from 'before' variable 225 | for (var i = 0; i < acceptedFunctions.length; i++) { 226 | if (before.slice(-acceptedFunctions[i].length - 1, -1) === acceptedFunctions[i]) { 227 | f = acceptedFunctions[i]; 228 | before = before.slice(0, -acceptedFunctions[i].length - 1); 229 | break; 230 | } 231 | } 232 | 233 | // check what function is being called - f === '' is no funciton - () order of operations 234 | if (f === '') { 235 | before = before.slice(0, -1); 236 | str = this.parseString(inner, indent);//this.parseString(inner, index) + after; 237 | 238 | value = this.parseString(before + '~' + after, indent).replace('~', str); 239 | //value = value.slice(1, -1); 240 | } else { // order of operations w/ () - not a function call 241 | let prefix = '\t'.repeat(++indent) + '"operator": "' + f + '()",\n' + '\t'.repeat(++indent) + ' "operands": [\n'; 242 | 243 | str = this.parseString(inner, indent);//this.parseString(inner, index) + after; 244 | str = prefix + str; 245 | 246 | value = '{\n' + '\t'.repeat(++indent) + str + '\n' + '\t'.repeat(indent) + ']\n}'; 247 | value = this.parseString(before + '~' + after, indent).replace('~', value); 248 | } 249 | 250 | return value; 251 | } 252 | 253 | // validate proper function (prevents stack overflow) 254 | validParen(str) { 255 | let arr = []; 256 | for (let i = 0; i < str.length; i++) { 257 | if (str.charAt(i) === '(') 258 | arr.push('('); 259 | else if (str.charAt(i) === ')') 260 | arr.pop(); 261 | } 262 | return (arr.length === 0 ? true : false); 263 | } 264 | 265 | parseString(str, indent) { 266 | if (str === undefined) 267 | str = ''; 268 | // replace '%5F' with '_' inside variable names 269 | str = str.replace(/\[\$(.*?)%5F(.*?)\]/g, (match) => 270 | { 271 | return match.replace(/%5F/g, '_') 272 | }); 273 | 274 | 275 | 276 | let value = '"' + str + '"'; 277 | 278 | // parses functions and () order of operations 279 | if (this.validParen(str) && str.indexOf('(') >= 0) { 280 | value = value.replace(str, this.parseFunctions(str, indent)); 281 | } 282 | value = value.replace(str, this.parseMathOperations('--', str, indent)); 283 | value = value.replace(str, this.parseMathOperations('++', str, indent)); 284 | value = value.replace(str, this.parseMathOperations('//', str, indent)); 285 | value = value.replace(str, this.parseMathOperations('**', str, indent)); 286 | 287 | 288 | // backslashes for Flow actionParameters command 289 | if ( value.charAt(1) === '{' || value === '"~"') { 290 | value = value.slice(1, -1); 291 | } 292 | 293 | 294 | // add " back to Flow Parameters 295 | if (value.includes("{\\\"id\\\": \\\"")) 296 | value = '"' + value + '"'; 297 | 298 | return value; 299 | } 300 | 301 | buildValue(type, obj, indent) { 302 | let output = ''; 303 | let key = ''; 304 | switch (type) { 305 | case 'attribute': 306 | output = '\t'.repeat(indent) + '"attributes": {'; 307 | break; 308 | case 'property': 309 | output = '\t'.repeat(indent) + '"style": {'; 310 | break; 311 | case 'customRowActions': 312 | output = output = '\t'.repeat(indent) + '"customRowAction": {'; 313 | break; 314 | } 315 | 316 | obj.forEach((ele, i) => { 317 | // NEED TO CHECK IF RULES/CONDITIONS ARE APPLIED OR NOT (FIX HERE) 318 | // craft value 319 | let value = ''; 320 | 321 | // FIX: CHANGE ele.attribute => ele.name when changing 322 | if (typeof ele.value === 'string') { 323 | if (!(ele.name === 'class' || ele.name === 'iconName')) { 324 | value = this.parseString(ele.value, indent); 325 | } else { 326 | value = '"' + ele.value + '"'; 327 | } 328 | } 329 | // craft value 330 | output = output + ` 331 | "` + ele.name + '": ' + value; 332 | 333 | if (typeof ele.value === 'object') { 334 | // false 335 | ele.value.forEach( (condition) => { 336 | output = output + `\n 337 | { 338 | "operator": "?", 339 | "operands": [ 340 | { 341 | "operator": "` + condition.operator + `", 342 | "operands": [ 343 | ` + (this.state.fieldType !== 'Number' ? this.parseString(condition.operand, indent) : this.parseString(condition.operand, indent).slice(1, -1)) + `, 344 | ` + (this.state.fieldType !== 'Number' ? this.parseString(condition.operand2, indent) : this.parseString(condition.operand2, indent).slice(1, -1)) + ` 345 | ] 346 | }, 347 | ` + this.parseString(condition.value, indent) + `, ` 348 | }) 349 | // end false 350 | } 351 | 352 | // 353 | output = output + '""'.repeat( (typeof ele.value === 'object' ? 1 : 0) ); 354 | 355 | // add closing brackets based on number of properties being evaluated 356 | output = output + `\n\t] 357 | }`.repeat( (typeof ele.value === 'string' ? 0 : ele.value.length) ); 358 | 359 | // add commas for all properties until the last one 360 | output = output + ','.repeat( (i !== obj.length - 1 ? 1 : 0) ); 361 | }); 362 | 363 | output = output + '\n' + '\t'.repeat(indent) + '}'; 364 | 365 | return output; 366 | } 367 | 368 | 369 | buildJSON() { 370 | let indent = 0; 371 | var JSON_Body = ``; 372 | var JSON_Header = 373 | `{ 374 | "elmType": "` + this.state.elmType + `", 375 | "txtContent": ` + this.parseString(this.state.textContent, indent) + `, 376 | `; 377 | var JSON_Footer = ``; 378 | var JSON_Properties = ''; 379 | var JSON_Attributes = ''; 380 | var JSON_CustomRowActions = ''; 381 | 382 | 383 | // BUILD JSON HERE in forEach loop for ATTRIBUTES 384 | indent++; 385 | 386 | JSON_Properties = JSON_Properties + this.buildValue('property', this.state.properties, indent); 387 | JSON_Attributes = JSON_Attributes + this.buildValue('attribute', this.state.attributes, indent); 388 | JSON_CustomRowActions = JSON_CustomRowActions + this.buildValue('customRowActions', this.state.customRowActions, indent); 389 | 390 | // JSON Footer 391 | JSON_Footer = `\n}`; 392 | 393 | // build body of properties and attributes 394 | JSON_Body = JSON_Attributes + ",\n" + JSON_Properties + ",\n" + JSON_CustomRowActions ; 395 | 396 | // Set Output 397 | this.setState({ 398 | JSON: JSON_Header + JSON_Body + JSON_Footer 399 | }, () => { this.props.updateChildren(this.props.index, this.state, this.state.JSON); this.props.buildJSON() }); 400 | 401 | } 402 | 403 | handleInputChange(event) { 404 | const target = event.target; 405 | const value = target.type === 'checkbox' ? target.checked : target.value; 406 | const name = target.name; 407 | this.setState({ 408 | [name]: value 409 | }, () => { this.buildJSON() }); 410 | } 411 | 412 | 413 | 414 | 415 | render() { 416 | return ( 417 | 418 | 419 |
420 | 421 |
422 | Child {this.props.index + 1} 423 |
424 | 425 | 426 | 427 |
428 | this.props.deleteKey('children', this.props.index)}>X 429 |
430 | 431 | 432 |
433 | this.props.moveChild(ele, this.props.index, this) }>To Top 434 |
435 | 436 | 437 |
438 | this.props.moveChild(ele, this.props.index, this)}>Move Up 439 |
440 | 441 | 442 |

Child {this.props.index + 1}

443 | 444 | 445 |
446 | this.props.moveChild(ele, this.props.index, this)}>Move Down 447 |
448 | 449 | 450 |
451 | this.props.moveChild(ele, this.props.index, this)}>To Bottom 452 |
453 | 454 | 455 |
456 |
457 | 458 | 459 | {/* */} 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | {/* */} 474 | 475 | 476 | 477 | 478 | 479 |
480 | 481 | {/* Attributes */} 482 | 483 |
484 | Attributes 485 |
486 | 487 | 488 |
489 | 490 |
491 |
492 | 493 |
494 | 495 |
496 | 497 | {Object.keys(this.state.attributes).map((key, i) => { 498 | return () 499 | })} 500 | 501 | 502 | 503 |
504 | 505 | {/* Properties */} 506 | 507 |
508 | Properties 509 |
510 | 511 | 512 | 513 |
514 | 515 |
516 |
517 | 518 |
519 | 520 |
521 | 522 | {Object.keys(this.state.properties).map((key, i) => { 523 | return () 524 | })} 525 | 526 | 527 | 528 |
529 | 530 | {/* Custom Row Action */} 531 | 532 |
533 | Row Actions 534 |
535 | 536 | 537 |
538 | 539 |
540 |
541 | 542 |
543 | 544 |
545 | 546 | {Object.keys(this.state.customRowActions).map((key, i) => { 547 | return () 548 | })} 549 | 550 | 551 |
552 | 553 |
554 |
555 | 556 | ); 557 | } 558 | } 559 | 560 | export default Children; 561 | -------------------------------------------------------------------------------- /src/Components/Condition.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { 3 | Container, 4 | Col, 5 | Row, 6 | Button, 7 | Input, 8 | Label, 9 | InputGroup, 10 | DropdownMenu, 11 | InputGroupButtonDropdown, 12 | DropdownToggle, 13 | DropdownItem 14 | } from 'reactstrap'; 15 | import CurrentRules from './CurrentRules.jsx'; 16 | 17 | 18 | class Condition extends Component { 19 | constructor(props) { 20 | super(props); 21 | this.handleInputChange = this.handleInputChange.bind(this); 22 | this.newRule = this.newRule.bind(this); 23 | this.editRule = this.editRule.bind(this); 24 | this.selectRule = this.selectRule.bind(this); 25 | this.deleteRule = this.deleteRule.bind(this); 26 | this.clearRules = this.clearRules.bind(this); 27 | this.toggleValueDropdownOpen = this.toggleValueDropdownOpen.bind(this); 28 | this.state = { 29 | operator: '==', 30 | operand: '@currentField', 31 | operand2: '', 32 | value: '', 33 | selectedRule: '', 34 | rules: this.props.rules, 35 | valueDropdownOpen: false, 36 | reset: this.props.reset 37 | }; 38 | } 39 | 40 | componentDidUpdate() { 41 | if (this.props.resetRules) 42 | this.setState({ 43 | rules: this.props.rules 44 | }) 45 | 46 | if (this.state.reset) 47 | this.clearRules; 48 | 49 | 50 | } 51 | 52 | 53 | newRule() { 54 | var arr = this.state.rules.slice(); 55 | arr.length > 0 ? '' : arr = []; 56 | arr.push({operator: this.state.operator, operand: this.state.operand, operand2: this.state.operand2, value: this.state.value}); 57 | this.setState({ 58 | rules: arr, 59 | value: '' 60 | }, () => { this.props.updateKey(this.props.index, this.props.name, this.state.rules) }); 61 | this.props.setValue(arr); 62 | } 63 | 64 | selectRule(index) { 65 | switch (index) { 66 | case this.state.selectedRule: 67 | this.setState({ 68 | selectedRule: '', 69 | operand: '' 70 | }); 71 | break; 72 | 73 | default: 74 | var rule = this.state.rules[index]; 75 | this.setState({ 76 | selectedRule: index, 77 | operator: rule.operator, 78 | operand: rule.operand, 79 | operand2: rule.operand2, 80 | value: rule.value 81 | }); 82 | 83 | } 84 | 85 | } 86 | 87 | editRule() { 88 | var arr = this.state.rules.slice(); 89 | arr[this.state.selectedRule].operator = this.state.operator; 90 | arr[this.state.selectedRule].operand = this.state.operand; 91 | arr[this.state.selectedRule].operand2 = this.state.operand2; 92 | arr[this.state.selectedRule].value = this.state.value; 93 | this.props.setValue(arr); 94 | this.setState({ 95 | rules: arr 96 | }, () => { this.props.updateKey(this.props.index, this.props.name, this.state.rules) }); 97 | 98 | } 99 | 100 | deleteRule() { 101 | if (this.state.selectedRule !== '') { 102 | var arr = this.state.rules; 103 | var deleted_Value = arr[this.state.selectedRule].value; 104 | arr.splice(this.state.selectedRule, 1); 105 | this.props.setValue(arr); 106 | this.setState({ 107 | rules: arr, 108 | operand: '@currentField', 109 | operand2: '', 110 | value: deleted_Value, 111 | selectedRule: '' 112 | }, () => { this.props.updateKey(this.props.index, this.props.name, this.state.rules) }); 113 | } 114 | } 115 | 116 | clearRules() { 117 | this.props.setValue([]); 118 | this.setState({ 119 | rules: [], 120 | operand: '@currentField', 121 | operand2: '', 122 | selectedRule: '' 123 | }, () => { this.props.updateKey(this.props.index, this.props.name, this.state.rules) }); 124 | } 125 | 126 | 127 | 128 | handleInputChange(event) { 129 | const target = event.target; 130 | let value = target.type === 'button' ? ( this.props.name === 'background-color' ? this.props.colors[target.innerHTML] : target.innerHTML) : target.value; 131 | const name = target.name; 132 | this.setState({ 133 | [name]: value, 134 | }); 135 | 136 | 137 | } 138 | 139 | toggleValueDropdownOpen() { 140 | this.setState({ 141 | valueDropdownOpen: !this.state.valueDropdownOpen 142 | }); 143 | } 144 | 145 | render() { 146 | return ( 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 173 | 175 | 176 | 177 | 178 | { (this.props.nameChoices[this.props.name] !== undefined && this.props.nameChoices[this.props.name].options !== undefined 179 | ? this.props.nameChoices[this.props.name].options.split(',').map((key, i) => { 180 | return ({key}); 181 | }) : (this.props.name === 'background-color' ? Object.keys(this.props.colors).map((key, i) => { 182 | return ({key}); 183 | }) 184 | : '') )} 185 | 186 | 187 | 188 | 189 | 190 |
191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | ); 221 | } 222 | } 223 | 224 | export default Condition; -------------------------------------------------------------------------------- /src/Components/CurrentRules.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import Rule from './Rule.jsx'; 3 | 4 | class CurrentRules extends Component { 5 | 6 | render() { 7 | return ( 8 |
9 | {/* Only check if rules exist, print if they do */} 10 | {typeof this.props.rules === 'object' && this.props.rules.length > 0 ? this.props.rules.map((ele, i) => { 11 | var text = ele.operand + ' ' + ele.operator + ' ' + ele.operand2 + ' (' + ele.value + ')'; 12 | return ( 13 | ) 14 | }) : ''} 15 |
16 | ) 17 | } 18 | } 19 | 20 | 21 | export default CurrentRules; -------------------------------------------------------------------------------- /src/Components/CustomRowAction.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { 3 | Row, 4 | Col, 5 | Label, 6 | Input, 7 | InputGroup, 8 | InputGroupButtonDropdown, 9 | DropdownMenu, 10 | DropdownToggle, 11 | DropdownItem, 12 | Container, 13 | Button 14 | } from 'reactstrap'; 15 | import Condition from './Condition.jsx'; 16 | 17 | 18 | 19 | class customRowAction extends Component { 20 | constructor(props) { 21 | super(props); 22 | this.setValue = this.setValue.bind(this); 23 | this.handleInputChange = this.handleInputChange.bind(this); 24 | this.toggleAttributeDropdownOpen = this.toggleAttributeDropdownOpen.bind(this); 25 | this.toggleValueDropdownOpen = this.toggleValueDropdownOpen.bind(this); 26 | this.toggleConditionals = this.toggleConditionals.bind(this); 27 | this.moveRule = this.moveRule.bind(this); 28 | this.state = { 29 | key: this.props.customRowActions[this.props.index].name, 30 | value: this.props.customRowActions[this.props.index].value, 31 | conditionalFlag: (typeof this.props.customRowActions[this.props.index].value === 'object' && this.props.customRowActions[this.props.index].value.length ? true : false), // True if rules have been defined, otherwise false 32 | keyDropdownOpen: false, 33 | valueDropdownOpen: false, 34 | prevIndex: this.props.index, 35 | reset: this.props.reset 36 | }; 37 | } 38 | 39 | componentDidUpdate() { 40 | if (this.state.prevIndex !== this.props.index) { 41 | this.setState({ 42 | key: this.props.customRowActions[this.props.index].name, 43 | value: this.props.customRowActions[this.props.index].value, 44 | conditionalFlag: (typeof this.props.customRowActions[this.props.index].value === 'object' && this.props.customRowActions[this.props.index].value.length ? true : false), // True if rules have been defined, otherwise false 45 | keyDropdownOpen: false, 46 | valueDropdownOpen: false, 47 | prevIndex: this.props.index 48 | }, () => { this.props.buildJSON() }); 49 | } 50 | 51 | 52 | } 53 | 54 | 55 | setValue(obj) { 56 | this.setState({ 57 | value: obj 58 | }) 59 | } 60 | 61 | // called inside Condition component 62 | moveRule(ele, index) { 63 | let arr = this.state.value.slice(); 64 | let temp = arr[index]; 65 | 66 | console.log(arr); 67 | switch(ele.target.attributes.value.value) { 68 | case 'up': 69 | arr.splice(index, 1); 70 | arr.splice((index === 0 ? 0 : index-1), 0, temp); 71 | break; 72 | case 'down': 73 | arr.splice(index, 1); 74 | arr.splice(index+1, 0, temp); 75 | break; 76 | } 77 | 78 | 79 | this.setState({ 80 | value: arr 81 | }, () => { this.props.updateCRA(this.props.index, this.state.key, this.state.value) }); 82 | } 83 | 84 | 85 | 86 | toggleConditionals() { 87 | this.setState({ 88 | conditionalFlag: !this.state.conditionalFlag, 89 | value: [] 90 | }, () => { this.props.updateCRA(this.props.index, this.state.key, this.state.value) }); 91 | } 92 | 93 | handleInputChange(event) { 94 | const target = event.target; 95 | const name = target.name; 96 | 97 | let resetValue = ''; 98 | if (name === 'key') { 99 | let value = target.type === 'button' ? target.innerHTML : target.value; 100 | resetValue = []; 101 | this.setState({ 102 | [name]: value, 103 | value: resetValue 104 | }, () => { this.props.updateCRA(this.props.index, value, '') }) 105 | 106 | } else { 107 | let value = target.type === 'button' ? ( this.state.key === 'background-color' ? this.props.colors[target.innerHTML] : target.innerHTML) : target.value; 108 | resetValue = value; 109 | this.setState({ 110 | [name]: value, 111 | value: resetValue 112 | }, () => { this.props.updateCRA(this.props.index, this.state.key, this.state.value) }) 113 | } 114 | 115 | } 116 | 117 | setConditionalValue(eleValue) { 118 | this.setState({ 119 | value: eleValue 120 | }); 121 | } 122 | 123 | toggleAttributeDropdownOpen() { 124 | this.setState({ 125 | value: '', 126 | keyDropdownOpen: !this.state.keyDropdownOpen 127 | }); 128 | } 129 | 130 | toggleValueDropdownOpen() { 131 | this.setState({ 132 | valueDropdownOpen: !this.state.valueDropdownOpen 133 | }); 134 | } 135 | 136 | render() { 137 | return ( 138 | 139 | 140 | 141 | 142 |
143 | this.props.deleteKey('customRowActions', this.props.index)}>X 144 |
145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | {Object.keys(this.props.customRowActionChoices).map((key, i) => { 154 | return ({key}); 155 | })} 156 | 157 | 158 | 159 | 160 | 161 | 162 | {!this.state.conditionalFlag ? 163 |
164 | 165 | 166 | 167 | 169 | 170 | 171 | { (this.props.customRowActionChoices[this.state.key] !== undefined && this.props.customRowActionChoices[this.state.key].options !== undefined 172 | ? this.props.customRowActionChoices[this.state.key].options.split(',').map((key, i) => { 173 | return ({key}); 174 | }) : (this.state.key === 'background-color' ? Object.keys(this.props.colors).map((key, i) => { 175 | return ({key}); 176 | }) 177 | : '') )} 178 | 179 | 180 | 181 |
182 | : ''} 183 | 184 |
185 | 186 |
187 | 188 |
189 |
190 | 191 | {this.state.conditionalFlag ? : ''} 192 | 193 |
194 | ) 195 | } 196 | } 197 | 198 | export default customRowAction; 199 | -------------------------------------------------------------------------------- /src/Components/MyModal.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { 3 | Modal, 4 | ModalHeader, 5 | ModalBody, 6 | ModalFooter, 7 | Button, 8 | TabContent, 9 | TabPane, 10 | Nav, 11 | NavItem, 12 | NavLink 13 | 14 | } from 'reactstrap'; 15 | 16 | import classnames from 'classnames'; 17 | 18 | class MyModal extends Component { 19 | constructor(props) { 20 | super(props); 21 | this.handleKeyDown = this.handleKeyDown.bind(this); 22 | this.toggle = this.toggle.bind(this); 23 | this.state = { 24 | activeTab: this.props.modalTab 25 | } 26 | } 27 | 28 | componentWillReceiveProps() { 29 | this.setState({ 30 | activeTab: this.props.modalTab 31 | }); 32 | } 33 | 34 | handleKeyDown(key) { 35 | if (key.keyCode === 13 || key.keyCode === 27) { 36 | this.props.toggleModal(); 37 | } 38 | } 39 | 40 | toggle(tab) { 41 | if (this.state.activeTab !== tab) { 42 | this.setState({ 43 | activeTab: tab 44 | }); 45 | } 46 | } 47 | 48 | 49 | render() { 50 | return ( 51 |
52 | 53 | 54 | {this.props.modalHeader} 55 | 56 | 57 | 79 | 80 | 81 |
82 | 83 | 84 |
85 |
86 | @currentField - refers to text in current field.
87 | @currentField.title - Person fields are represented in the system as objects, and a person’s display name is contained within that object’s title property
88 | @currentField.lookupValue - Lookup fields are also represented as objects; the display text is stored in the lookupValue property
89 | @now - current date/time
90 | @me - current user's email
91 | [$FieldName] - refers to value in field on same row
92 | [$PeoplePicker.email] - refers to email of the person in a people picker field
93 |
94 | People picker field properties: id, title, email, sip, picture 95 | 96 |
97 |
98 |
99 | 100 |
101 |
102 | toString() - Convert value to string
103 | Number() - Convert value to number
104 | Date() - Convert value to date
105 | cos() - Trig cos function
106 | sin() - Trig sin function
107 | toLocaleString() - Displays a date type fully expanded with date and time
108 | toLocaleDateString() - Displays a date type with just the date
109 | toLocaleTimeString() - Displays a date type with just the time 110 |
111 | 112 |
113 |
114 | 115 |
116 |
117 | ** - multiply
118 | // - divide
119 | ++ - add / concatenate strings
120 | -- - subtract
121 |
122 | () - parentheses are also supported to specify order of operations
123 | 124 |
125 |
126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 |
138 | ) 139 | } 140 | } 141 | 142 | export default MyModal; 143 | 144 | 145 | -------------------------------------------------------------------------------- /src/Components/Property.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { 3 | Row, 4 | Col, 5 | Label, 6 | Input, 7 | InputGroup, 8 | InputGroupButtonDropdown, 9 | InputGroupAddon, 10 | InputGroupText, 11 | DropdownMenu, 12 | DropdownToggle, 13 | DropdownItem, 14 | Container, 15 | Button 16 | } from 'reactstrap'; 17 | import Condition from './Condition.jsx'; 18 | 19 | 20 | 21 | class Property extends Component { 22 | constructor(props) { 23 | super(props); 24 | this.setValue = this.setValue.bind(this); 25 | this.handleInputChange = this.handleInputChange.bind(this); 26 | this.togglekeyDropdownOpen = this.togglekeyDropdownOpen.bind(this); 27 | this.toggleValueDropdownOpen = this.toggleValueDropdownOpen.bind(this); 28 | this.toggleConditionals = this.toggleConditionals.bind(this); 29 | this.moveRule = this.moveRule.bind(this); 30 | this.state = { 31 | key: this.props.properties[this.props.index].name, 32 | value: this.props.properties[this.props.index].value, 33 | conditionalFlag: (typeof this.props.properties[this.props.index].value === 'object' && this.props.properties[this.props.index].value.length ? true : false), // True if rules have been defined, otherwise false 34 | keyDropdownOpen: false, 35 | valueDropdownOpen: false, 36 | prevIndex: this.props.index, 37 | reset: false 38 | }; 39 | } 40 | 41 | componentDidUpdate() { 42 | if (this.state.prevIndex !== this.props.index) { 43 | this.setState({ 44 | key: this.props.properties[this.props.index].name, 45 | value: this.props.properties[this.props.index].value, 46 | conditionalFlag: (typeof this.props.properties[this.props.index].value === 'object' && this.props.properties[this.props.index].value.length ? true : false), // True if rules have been defined, otherwise false 47 | keyDropdownOpen: false, 48 | valueDropdownOpen: false, 49 | prevIndex: this.props.index 50 | }, () => { this.props.buildJSON() }); 51 | } 52 | } 53 | 54 | 55 | 56 | setValue(obj) { 57 | this.setState({ 58 | value: obj 59 | }) 60 | } 61 | 62 | // called inside Condition component 63 | moveRule(ele, index) { 64 | let arr = this.state.value.slice(); 65 | let temp = arr[index]; 66 | 67 | console.log(arr); 68 | switch(ele.target.attributes.value.value) { 69 | case 'up': 70 | arr.splice(index, 1); 71 | arr.splice((index === 0 ? 0 : index-1), 0, temp); 72 | break; 73 | case 'down': 74 | arr.splice(index, 1); 75 | arr.splice(index+1, 0, temp); 76 | break; 77 | } 78 | 79 | 80 | this.setState({ 81 | value: arr 82 | }, () => { this.props.updateProperty(this.props.index, this.state.key, this.state.value); }); 83 | } 84 | 85 | 86 | 87 | toggleConditionals() { 88 | this.setState({ 89 | conditionalFlag: !this.state.conditionalFlag, 90 | value: [] 91 | }, () => { this.props.updateProperty( this.props.index, this.state.key, this.state.value) }); 92 | } 93 | 94 | handleInputChange(event) { 95 | const target = event.target; 96 | const name = target.name; 97 | 98 | let resetValue = ''; 99 | 100 | if (name === 'key') { 101 | let value = target.type === 'button' ? target.innerHTML : target.value; 102 | resetValue = []; 103 | this.setState({ 104 | [name]: value, 105 | value: resetValue 106 | }, () => { this.props.updateProperty( this.props.index, value, '') }) 107 | 108 | } else { 109 | let value = target.type === 'button' ? ( this.state.key === 'background-color' ? this.props.colors[target.innerHTML] : target.innerHTML) : target.value; 110 | resetValue = value; 111 | this.setState({ 112 | [name]: value, 113 | value: resetValue 114 | }, () => { this.props.updateProperty( this.props.index, this.state.key, this.state.value) }) 115 | } 116 | 117 | 118 | console.log(this.props.properties[this.props.index].value); 119 | 120 | } 121 | 122 | setConditionalValue(eleValue) { 123 | this.setState({ 124 | value: eleValue 125 | }); 126 | } 127 | 128 | togglekeyDropdownOpen() { 129 | this.setState({ 130 | value: '', 131 | keyDropdownOpen: !this.state.keyDropdownOpen 132 | }); 133 | } 134 | 135 | toggleValueDropdownOpen() { 136 | this.setState({ 137 | valueDropdownOpen: !this.state.valueDropdownOpen 138 | }); 139 | } 140 | 141 | render() { 142 | return ( 143 | 144 | 145 | 146 | 147 |
148 | this.props.deleteKey('properties', this.props.index)}>X 149 |
150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | {Object.keys(this.props.nameChoices).map((key, i) => { 159 | return ({key}); 160 | })} 161 | 162 | 163 | 164 | 165 | 166 | 167 | {!this.state.conditionalFlag ? 168 |
169 | 170 | 171 | 172 | 174 | 175 | 176 | { (this.props.nameChoices[this.state.key] !== undefined && this.props.nameChoices[this.state.key].options !== undefined 177 | ? this.props.nameChoices[this.state.key].options.split(',').map((key, i) => { 178 | return ({key}); 179 | }) : (this.state.key === 'background-color' ? Object.keys(this.props.colors).map((key, i) => { 180 | return ({key}); 181 | }) 182 | : '') )} 183 | 184 | 185 | 186 |
187 | : ''} 188 | 189 |
190 | 191 |
192 | 193 |
194 |
195 | 196 | {this.state.conditionalFlag ? : ''} 197 | 198 |
199 | ) 200 | } 201 | } 202 | 203 | export default Property; -------------------------------------------------------------------------------- /src/Components/Rule.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | class Rule extends Component { 4 | 5 | render() { 6 | return ( 7 |
{this.props.selectRule(this.props.index)}} 8 | style={ { [this.props.name] : this.props.value, 'borderWidth': (this.props.index === this.props.selectedRule ? '3px' : '0px') }}> 9 | {this.props.text} 10 |
11 | ); 12 | } 13 | } 14 | 15 | 16 | export default Rule; -------------------------------------------------------------------------------- /src/Components/TopNavigation.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { 3 | Navbar, 4 | NavbarBrand, 5 | Nav, 6 | Collapse, 7 | NavbarToggler, 8 | NavItem, 9 | NavLink, 10 | DropdownToggle, 11 | DropdownMenu, 12 | DropdownItem, 13 | UncontrolledDropdown 14 | } from 'reactstrap'; 15 | import '../App.css'; 16 | 17 | class TopNavigation extends Component { 18 | constructor(props) { 19 | super(props); 20 | this.toggleNavbar = this.toggleNavbar.bind(this); 21 | this.state = { 22 | isOpen: false 23 | }; 24 | } 25 | 26 | toggleNavbar() { 27 | this.setState({ 28 | isOpen: !this.state.isOpen 29 | }); 30 | } 31 | 32 | render() { 33 | return ( 34 |
35 | 36 | {/* Left Nav Buttons */} 37 | 94 | 95 | {/* Right Nav Buttons */} 96 | 131 | 132 | 133 | 134 | 135 |
136 | ) 137 | } 138 | } 139 | 140 | 141 | // Verbose Schema 142 | // https://gist.github.com/thechriskent/2e09be14a4b491cfae256220cfca6310 143 | 144 | export default TopNavigation; 145 | -------------------------------------------------------------------------------- /src/data.js: -------------------------------------------------------------------------------- 1 | 2 | export default { 3 | Attributes: { 4 | 'href': { 5 | 'placeholder': 'URL' 6 | }, 7 | 'target': { 8 | 'options': '_blank,_self,_parent,_top' 9 | } 10 | , 11 | 'class': { 12 | 'options': 'sp-field-customFormatBackground,sp-field-severity--good,sp-field-severity--low,sp-field-severity--warning,sp-field-severity--blocked,sp-field-dataBars,sp-field-trending--up,sp-field-trending--down,sp-field-quickAction' 13 | }, 14 | 'iconName': {} 15 | }, 16 | CSSProperties: { 17 | 'background-color': { 18 | 'placeholder': '#hex -- color' 19 | }, 20 | 'font-size': { 21 | 'placeholder': '18px -- 150% ' 22 | }, 23 | 'text-align': { 24 | 'options': 'left,right,center,justify' 25 | }, 26 | 'border': { 27 | 'placeholder': '4px solid black' 28 | }, 29 | 'border-radius': {}, 30 | 'font-weight': { 31 | 'options': 'bold,semibold' 32 | }, 33 | 34 | 'color': { 35 | 'placeholder': '#hex -- color' 36 | }, 37 | 'width': {}, 38 | 'max-height': {}, 39 | 'overflow': { 40 | 'options': 'scroll,hidden,auto,visible' 41 | } 42 | }, 43 | customRowActions: { 44 | 'action': { 45 | 'options': 'executeFlow,share,defaultClick' 46 | }, 47 | 'actionParams': { 48 | 'options': '{\\"id\\": \\"FLOW_ID\\"}' 49 | } 50 | }, 51 | customColors: { 52 | 'Transparent': 'transparent', 53 | 'Green': '#98FB98', 54 | 'Yellow': '#FFFF66', 55 | 'Orange': '#FFA450', 56 | 'Red': '#FF6A6A', 57 | 'Blue': '#5078FF', 58 | 'Purple': '#B350FF', 59 | 'Custom': '#' 60 | }, 61 | template_completedInProgressLate: { 62 | properties: [ 63 | { 64 | name: 'background-color', 65 | value: [ 66 | { 67 | operator: '==', 68 | operand: '@currentField', 69 | operand2: 'Completed', 70 | value: '#98fb98' 71 | }, 72 | { 73 | operator: '==', 74 | operand: '@currentField', 75 | operand2: 'In Progress', 76 | value: '#FFFF66' 77 | }, 78 | { 79 | operator: '==', 80 | operand: '@currentField', 81 | operand2: 'Late', 82 | value: '#ff6a6a' 83 | } 84 | ] 85 | } 86 | ] 87 | }, 88 | template_dataBars_one: { 89 | attributes: [ 90 | { 91 | name: 'class', 92 | value: 'sp-field-dataBars' 93 | } 94 | ], 95 | properties: [ 96 | { 97 | name: 'width', 98 | value: 'Number(@currentField)**Number(100)++%' 99 | } 100 | ] 101 | }, 102 | template_dataBars_hundred: { 103 | attributes: [ 104 | { 105 | name: 'class', 106 | value: 'sp-field-dataBars' 107 | } 108 | ], 109 | properties: [ 110 | { 111 | name: 'width', 112 | value: '@currentField++%' 113 | } 114 | ] 115 | }, 116 | template_buttonWithLinkandIcon: { 117 | // button 118 | // currentText = " ++@currentField 119 | properties: [ 120 | { 121 | name: 'display', 122 | value: 'block' 123 | }, 124 | { 125 | name: 'width', 126 | value: '9em' 127 | }, 128 | { 129 | name: 'background-color', 130 | value: '#FFA450' 131 | }, 132 | { 133 | name: 'border', 134 | value: '2px solid black' 135 | }, 136 | { 137 | name: "border-radius", 138 | value: "15px" 139 | }, 140 | ], 141 | children: [ 142 | { 143 | name: { 144 | elmType: "a", 145 | textContent: " ++@currentField", 146 | properties: [ 147 | { 148 | name: "text-decoration", 149 | value: "none" 150 | } 151 | ], 152 | attributes: [ 153 | { 154 | name: "target", 155 | value: "_blank" 156 | }, 157 | { 158 | name: "href", 159 | value: "https://www.google.com?ID=++[$ID]" 160 | }, 161 | { 162 | name: "iconName", 163 | value: "Link" 164 | } 165 | ] 166 | }, 167 | value: '' 168 | }, 169 | 170 | ] 171 | // children: [ 172 | // { 173 | 174 | // ] 175 | 176 | // child: a 177 | // target = _blank 178 | // href = 179 | 180 | }, 181 | 182 | 183 | 184 | modalBody_TextContentHelp: ` 185 | @currentField - refers to text in current field.
186 | @currentField.title - Person fields are represented in the system as objects, and a person’s display name is contained within that object’s title property
187 | @currentField.lookupValue - Lookup fields are also represented as objects; the display text is stored in the lookupValue property
188 | @now - current date/time
189 | @me - current user's email
190 | [$FieldName] - refers to value in field on same row
191 | [$PeoplePicker.email] - refers to email of the person in a people picker field
192 | People picker field properties: id, title, email, sip, picture 193 |

194 | 195 | Basic math functions (parenthases () are also supported):
196 | multiply (**), divide (//), add (++), subtract (--)

197 | 198 | Functions:
199 | toString(), Number(), Date(), cos(), sin(),
200 | toLocaleString() [Displays a date type fully expanded with date and time],
201 | toLocaleDateString() [Displays a date type with just the date],
202 | toLocaleTimeString() [Displays a date type with just the time] 203 |

204 |
205 | Note: If you have spaces in the field name, those are defined as _x0020_. For example, a field named "Due Date" should be referenced as $Due_x0020_Date.

206 | Note2: Use ++ to concatenate 207 | ` 208 | 209 | } 210 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import 'bootstrap/dist/css/bootstrap.min.css'; 4 | import './index.css'; 5 | import App from './App'; 6 | import registerServiceWorker from './registerServiceWorker'; 7 | 8 | ReactDOM.render(, document.getElementById('root')); 9 | registerServiceWorker(); 10 | -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/pollyfills.js: -------------------------------------------------------------------------------- 1 | if (!String.prototype.repeat) { 2 | String.prototype.repeat = function(count) { 3 | 'use strict'; 4 | if (this == null) { 5 | throw new TypeError('can\'t convert ' + this + ' to object'); 6 | } 7 | var str = '' + this; 8 | count = +count; 9 | if (count != count) { 10 | count = 0; 11 | } 12 | if (count < 0) { 13 | throw new RangeError('repeat count must be non-negative'); 14 | } 15 | if (count == Infinity) { 16 | throw new RangeError('repeat count must be less than infinity'); 17 | } 18 | count = Math.floor(count); 19 | if (str.length == 0 || count == 0) { 20 | return ''; 21 | } 22 | // Ensuring count is a 31-bit integer allows us to heavily optimize the 23 | // main part. But anyway, most current (August 2014) browsers can't handle 24 | // strings 1 << 28 chars or longer, so: 25 | if (str.length * count >= 1 << 28) { 26 | throw new RangeError('repeat count must not overflow maximum string size'); 27 | } 28 | var rpt = ''; 29 | for (var i = 0; i < count; i++) { 30 | rpt += str; 31 | } 32 | return rpt; 33 | } 34 | } 35 | 36 | if (!String.prototype.includes) { 37 | String.prototype.includes = function(search, start) { 38 | 'use strict'; 39 | if (typeof start !== 'number') { 40 | start = 0; 41 | } 42 | 43 | if (start + search.length > this.length) { 44 | return false; 45 | } else { 46 | return this.indexOf(search, start) !== -1; 47 | } 48 | }; 49 | } -------------------------------------------------------------------------------- /src/registerServiceWorker.js: -------------------------------------------------------------------------------- 1 | // In production, we register a service worker to serve assets from local cache. 2 | 3 | // This lets the app load faster on subsequent visits in production, and gives 4 | // it offline capabilities. However, it also means that developers (and users) 5 | // will only see deployed updates on the "N+1" visit to a page, since previously 6 | // cached resources are updated in the background. 7 | 8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy. 9 | // This link also includes instructions on opting out of this behavior. 10 | 11 | const isLocalhost = Boolean( 12 | window.location.hostname === 'localhost' || 13 | // [::1] is the IPv6 localhost address. 14 | window.location.hostname === '[::1]' || 15 | // 127.0.0.1/8 is considered localhost for IPv4. 16 | window.location.hostname.match( 17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 18 | ) 19 | ); 20 | 21 | export default function register() { 22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 23 | // The URL constructor is available in all browsers that support SW. 24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location); 25 | if (publicUrl.origin !== window.location.origin) { 26 | // Our service worker won't work if PUBLIC_URL is on a different origin 27 | // from what our page is served on. This might happen if a CDN is used to 28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374 29 | return; 30 | } 31 | 32 | window.addEventListener('load', () => { 33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 34 | 35 | if (isLocalhost) { 36 | // This is running on localhost. Lets check if a service worker still exists or not. 37 | checkValidServiceWorker(swUrl); 38 | 39 | // Add some additional logging to localhost, pointing developers to the 40 | // service worker/PWA documentation. 41 | navigator.serviceWorker.ready.then(() => { 42 | console.log( 43 | 'This web app is being served cache-first by a service ' + 44 | 'worker. To learn more, visit https://goo.gl/SC7cgQ' 45 | ); 46 | }); 47 | } else { 48 | // Is not local host. Just register service worker 49 | registerValidSW(swUrl); 50 | } 51 | }); 52 | } 53 | } 54 | 55 | function registerValidSW(swUrl) { 56 | navigator.serviceWorker 57 | .register(swUrl) 58 | .then(registration => { 59 | registration.onupdatefound = () => { 60 | const installingWorker = registration.installing; 61 | installingWorker.onstatechange = () => { 62 | if (installingWorker.state === 'installed') { 63 | if (navigator.serviceWorker.controller) { 64 | // At this point, the old content will have been purged and 65 | // the fresh content will have been added to the cache. 66 | // It's the perfect time to display a "New content is 67 | // available; please refresh." message in your web app. 68 | console.log('New content is available; please refresh.'); 69 | } else { 70 | // At this point, everything has been precached. 71 | // It's the perfect time to display a 72 | // "Content is cached for offline use." message. 73 | console.log('Content is cached for offline use.'); 74 | } 75 | } 76 | }; 77 | }; 78 | }) 79 | .catch(error => { 80 | console.error('Error during service worker registration:', error); 81 | }); 82 | } 83 | 84 | function checkValidServiceWorker(swUrl) { 85 | // Check if the service worker can be found. If it can't reload the page. 86 | fetch(swUrl) 87 | .then(response => { 88 | // Ensure service worker exists, and that we really are getting a JS file. 89 | if ( 90 | response.status === 404 || 91 | response.headers.get('content-type').indexOf('javascript') === -1 92 | ) { 93 | // No service worker found. Probably a different app. Reload the page. 94 | navigator.serviceWorker.ready.then(registration => { 95 | registration.unregister().then(() => { 96 | window.location.reload(); 97 | }); 98 | }); 99 | } else { 100 | // Service worker found. Proceed as normal. 101 | registerValidSW(swUrl); 102 | } 103 | }) 104 | .catch(() => { 105 | console.log( 106 | 'No internet connection found. App is running in offline mode.' 107 | ); 108 | }); 109 | } 110 | 111 | export function unregister() { 112 | if ('serviceWorker' in navigator) { 113 | navigator.serviceWorker.ready.then(registration => { 114 | registration.unregister(); 115 | }); 116 | } 117 | } 118 | --------------------------------------------------------------------------------