├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── src ├── components │ ├── app-button │ │ ├── index.marko │ │ ├── marko-tag.json │ │ └── style.less │ ├── app-checkbox │ │ ├── images │ │ │ ├── checked.svg │ │ │ └── unchecked.svg │ │ ├── index.marko │ │ ├── marko-tag.json │ │ └── style.less │ ├── app-fetch-data │ │ ├── index.marko │ │ ├── loading.svg │ │ └── style.less │ ├── app-layout │ │ ├── index.marko │ │ └── style.less │ ├── app-main │ │ ├── component.js │ │ ├── index.marko │ │ └── style.less │ ├── app-markdown │ │ ├── code-generator.js │ │ └── marko-tag.json │ ├── app-notification │ │ ├── index.marko │ │ └── style.less │ ├── app-notifications │ │ ├── component.js │ │ └── index.marko │ ├── app-number-spinner │ │ ├── index.marko │ │ └── style.less │ ├── app-overlay │ │ ├── component.js │ │ ├── index.marko │ │ ├── marko-tag.json │ │ └── style.less │ ├── app-progress-bar │ │ ├── component.js │ │ ├── index.marko │ │ ├── marko-tag.json │ │ └── style.less │ ├── app-sections │ │ ├── index.marko │ │ ├── marko-tag.json │ │ └── style.less │ ├── app-state-select │ │ ├── CA.png │ │ ├── CO.png │ │ └── index.marko │ └── app-tabs │ │ ├── index.marko │ │ ├── marko-tag.json │ │ └── style.less ├── global-style │ ├── index.less │ └── variables.less ├── index.js ├── pages │ └── home │ │ ├── index.js │ │ └── template.marko └── services │ ├── mock-users-data.json │ ├── package.json │ ├── routes.js │ ├── users-browser.js │ └── users.js └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | *.seed 2 | *.log 3 | *.csv 4 | *.dat 5 | *.out 6 | *.pid 7 | *.gz 8 | *.marko.js 9 | *.sublime-workspace 10 | *.sublime-project 11 | *.original 12 | *.vscode 13 | 14 | /lib-cov 15 | /pids 16 | /logs 17 | /results 18 | /dist 19 | /build 20 | /.cache 21 | /node_modules 22 | 23 | npm-debug.log 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Marko Widgets: UI Components Playground w/ Webpack 2 | ================================== 3 | 4 | ## Getting Started 5 | 6 | ```bash 7 | git clone https://github.com/marko-js-samples/marko-webpack.git 8 | cd marko-webpack 9 | npm install 10 | npm run start:dev 11 | ``` 12 | 13 | ## Production Build 14 | ```bash 15 | npm run build 16 | npm start 17 | ``` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "marko-webpack", 3 | "description": "Sample app that demonstrates the power of building UI components using Marko and Webpack", 4 | "version": "1.0.0", 5 | "author": "Patrick Steele-idem ", 6 | "bugs": { 7 | "url": "https://github.com/marko-js-samples/marko-webpack/issues" 8 | }, 9 | "dependencies": { 10 | "compression": "^1.7.4", 11 | "express": "^4.17.1", 12 | "marked": "^0.7.0", 13 | "marko": "^4.18.10", 14 | "purecss": "^1.0.1", 15 | "raptor-pubsub": "^1.0.5", 16 | "raptor-util": "^3.1.0", 17 | "serve-static": "^1.14.1", 18 | "whatwg-fetch": "^3.0.0" 19 | }, 20 | "devDependencies": { 21 | "@marko/webpack": "^1.2.0", 22 | "clean-webpack-plugin": "^3.0.0", 23 | "css-loader": "^3.0.0", 24 | "file-loader": "^4.0.0", 25 | "ignore-emit-webpack-plugin": "^2.0.2", 26 | "less": "^3.9.0", 27 | "less-loader": "^5.0.0", 28 | "mini-css-extract-plugin": "^0.8.0", 29 | "optimize-css-assets-webpack-plugin": "^5.0.3", 30 | "spawn-server-webpack-plugin": "^4.0.0", 31 | "svg-url-loader": "^3.0.0", 32 | "webpack": "^4.36.1", 33 | "webpack-cli": "^3.3.6", 34 | "webpack-dev-server": "^3.7.2" 35 | }, 36 | "homepage": "https://github.com/marko-js-samples/marko-webpack", 37 | "license": "MIT", 38 | "main": "dist/server/main.js", 39 | "repository": { 40 | "type": "git", 41 | "url": "https://github.com/marko-js-samples/marko-webpack.git" 42 | }, 43 | "scripts": { 44 | "build": "NODE_ENV=production webpack --progress", 45 | "start:dev": "webpack-dev-server", 46 | "start": "NODE_ENV=production node dist/server/main.js" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/components/app-button/index.marko: -------------------------------------------------------------------------------- 1 | class { 2 | handleClick(event) { 3 | // Every Widget instance is also an EventEmitter instance. 4 | // We will emit a custom "click" event when a DOM click event 5 | // is triggered 6 | this.emit("click", { 7 | event: event // Pass along the DOM event in case it is helpful to others 8 | }); 9 | } 10 | } 11 | 12 | $ const size = input.size || "normal"; 13 | $ const variant = input.variant || "primary"; 14 | $ const variantClassName = variant !== "primary" && "app-button-" + variant; 15 | $ const sizeClassName = size !== "normal" && "app-button-" + size; 16 | 27 | -------------------------------------------------------------------------------- /src/components/app-button/marko-tag.json: -------------------------------------------------------------------------------- 1 | { 2 | "@label": "string", 3 | "@href": "string", 4 | "@variant": { 5 | "type": "string", 6 | "enum": ["primary", "secondary"] 7 | }, 8 | "@size": { 9 | "type": "string", 10 | "enum": ["small", "normal", "large"] 11 | }, 12 | "@class": { 13 | "type": "string", 14 | "description": "Additional CSS class names" 15 | }, 16 | "@*": "string" 17 | } 18 | -------------------------------------------------------------------------------- /src/components/app-button/style.less: -------------------------------------------------------------------------------- 1 | @import "../../global-style/variables.less"; 2 | 3 | .app-button { 4 | display: inline-block; 5 | border-radius: 8px; 6 | padding: 12px; 7 | background-color: @button-bg; 8 | color: @button-fg; 9 | transition-property: background-color; 10 | transition-duration: 0.3s; 11 | transition-timing-function: ease-out; 12 | } 13 | 14 | 15 | .app-button:hover { 16 | background-color: lighten(@button-bg, 10%); 17 | } 18 | 19 | /* Variants */ 20 | .app-button-secondary { 21 | background-color: @button-secondary-bg; 22 | color: @button-secondary-fg; 23 | } 24 | 25 | .app-button-secondary:hover { 26 | background-color: lighten(@button-secondary-bg, 10%); 27 | } 28 | 29 | 30 | 31 | /* Sizes */ 32 | .app-button-small { 33 | font-size: 0.9em; 34 | padding-top: 6px; 35 | padding-bottom: 6px; 36 | } 37 | 38 | .app-button-large { 39 | font-size: 1.2em; 40 | font-weight: 100; 41 | } 42 | -------------------------------------------------------------------------------- /src/components/app-checkbox/images/checked.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/components/app-checkbox/images/unchecked.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/components/app-checkbox/index.marko: -------------------------------------------------------------------------------- 1 | class { 2 | onInput(input) { 3 | this.state = { 4 | checked: input.checked === true 5 | }; 6 | } 7 | 8 | isChecked() { 9 | return this.state.checked === true; 10 | } 11 | 12 | setChecked(newChecked) { 13 | this.state.checked = newChecked; 14 | } 15 | 16 | toggle() { 17 | this.state.checked = !this.state.checked; 18 | } 19 | 20 | getData() { 21 | return this.input.data; 22 | } 23 | 24 | handleClick() { 25 | const newChecked = !this.state.checked; 26 | let defaultPrevented = false; 27 | 28 | this.emit("toggle", { 29 | checked: newChecked, 30 | data: this.getData(), 31 | preventDefault: function() { 32 | defaultPrevented = true; 33 | } 34 | }); 35 | 36 | if (!defaultPrevented) { 37 | this.state.checked = newChecked; 38 | } 39 | } 40 | } 41 | 42 | 52 | 53 | 54 | ${input.label} 55 | 56 | <${input}/> 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /src/components/app-checkbox/marko-tag.json: -------------------------------------------------------------------------------- 1 | { 2 | "@label": "string", 3 | "@data": "expression", 4 | "@checked": "boolean", 5 | "@class": "string" 6 | } -------------------------------------------------------------------------------- /src/components/app-checkbox/style.less: -------------------------------------------------------------------------------- 1 | @import "../../global-style/variables.less"; 2 | 3 | .app-button.app-checkbox { 4 | background-color: @button-secondary-bg; 5 | color: @button-secondary-fg; 6 | } 7 | 8 | .app-button.app-checkbox.checked { 9 | background-color: @button-bg; 10 | color: @button-fg; 11 | } 12 | 13 | .app-checkbox-icon { 14 | background-image: url(./images/unchecked.svg); 15 | background-size: contain; 16 | width: 18px; 17 | height: 18px; 18 | display: inline-block; 19 | background-repeat: no-repeat; 20 | opacity: 0.2; 21 | margin-right: 4px; 22 | } 23 | 24 | .app-checkbox.checked .app-checkbox-icon { 25 | background-image: url(./images/checked.svg); 26 | opacity: 1; 27 | } 28 | -------------------------------------------------------------------------------- /src/components/app-fetch-data/index.marko: -------------------------------------------------------------------------------- 1 | import { getUsers } from "../../services/users" 2 | 3 | class { 4 | onInput(input) { 5 | let users = []; 6 | let pageIndex = -1; 7 | 8 | const usersData = input.usersData; 9 | if (usersData) { 10 | users = usersData.users; 11 | pageIndex = usersData.pageIndex; 12 | } 13 | 14 | this.state = { 15 | loading: false, 16 | users: users, 17 | pageIndex: pageIndex 18 | }; 19 | } 20 | 21 | onMount() { 22 | this.fetchPromise = Promise.resolve(); 23 | 24 | if (this.state.users.length === 0) { 25 | this.loadMore(); 26 | } 27 | } 28 | 29 | loadMore() { 30 | const state = this.state; 31 | state.loading = true; 32 | 33 | this.fetchPromise = this.fetchPromise 34 | .then(function() { 35 | return getUsers({ 36 | pageIndex: ++state.pageIndex 37 | }); 38 | }) 39 | .then(function(usersData) { 40 | state.users = state.users.concat(usersData.users); 41 | state.loading = false; 42 | }) 43 | .catch(function(e) { 44 | state.loading = false; 45 | console.log("Fetch failed:", e); 46 | }); 47 | } 48 | 49 | handleLoadMoreClick() { 50 | this.loadMore(); 51 | } 52 | 53 | onUpdate() { 54 | if (this.state.pageIndex > 0) { 55 | const tableContainer = this.getEl("tableContainer"); 56 | tableContainer.scrollTop = tableContainer.scrollHeight; 57 | } 58 | } 59 | } 60 | 61 | 62 |
63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 80 | 81 | 82 | 83 | 84 | 85 |
IDAvatarNameEmail
${user.id} 78 | 79 | ${user.firstName} ${user.lastName}${user.email}
86 | 87 |
88 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /src/components/app-fetch-data/loading.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/app-fetch-data/style.less: -------------------------------------------------------------------------------- 1 | @import "~purecss/build/tables.css"; 2 | 3 | .app-fetch-data .loading { 4 | background-image: url("./loading.svg"); 5 | width: 64px; 6 | height: 64px; 7 | display: inline-block; 8 | vertical-align: middle; 9 | } 10 | 11 | .app-fetch-data .table-container { 12 | max-height: 400px; 13 | overflow: auto; 14 | } 15 | -------------------------------------------------------------------------------- /src/components/app-layout/index.marko: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <${data.title}/> 7 | 8 | 9 | <${data.styles}/> 10 | 11 | 12 |
13 |

14 | <${data.title}/> 15 |

16 |
17 | <${data.body}/> 18 |
19 |
20 | 21 | <${data.scripts}/> 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/components/app-layout/style.less: -------------------------------------------------------------------------------- 1 | .container { 2 | margin-left: auto; 3 | margin-right: auto; 4 | width: 800px; 5 | } 6 | -------------------------------------------------------------------------------- /src/components/app-main/component.js: -------------------------------------------------------------------------------- 1 | var raptorPubsub = require('raptor-pubsub'); 2 | var button = require('../app-button'); 3 | var checkbox = require('../app-checkbox'); 4 | var progressBar = require('../app-progress-bar'); 5 | var extend = require('raptor-util/extend'); 6 | 7 | var buttonSizes = ['small', 'normal', 'large']; 8 | var buttonVariants = ['primary', 'secondary']; 9 | 10 | var currentButtonSize = 0; 11 | var currentButtonVariant = 0; 12 | 13 | module.exports = { 14 | onInput: function(input) { 15 | var now = (new Date()).toString(); 16 | 17 | this.state = { 18 | buttonSize: input.buttonSize || 'small', 19 | buttonVariant: input.buttonVariant || 'primary', 20 | overlayVisible: false, 21 | checked: input.checked || { 22 | foo: false, 23 | bar: true, 24 | baz: false 25 | }, 26 | dynamicTabs: [ 27 | { 28 | timestamp: now 29 | }, 30 | { 31 | timestamp: now 32 | } 33 | ] 34 | }; 35 | }, 36 | 37 | handleCheckboxToggle: function(event, sourceWidget) { 38 | // event.preventDefault(); 39 | 40 | var name = event.data.name; 41 | 42 | // We treat complex objects stored in the state as immutable 43 | // since only a shallow compare is done to see if the state 44 | // has changed. Instead of modifying the "checked" object, 45 | // we create a new object with the updated state of what is 46 | // checked. 47 | var newChecked = extend({}, this.state.checked); 48 | newChecked[name] = event.checked; 49 | this.setState('checked', newChecked); 50 | }, 51 | 52 | /** 53 | * This demonstrates how to provide a custom state transition handler to avoid 54 | * a full rerender. 55 | */ 56 | update_overlayVisible: function(overlayVisible) { 57 | this.getComponent('overlay').setVisibility(overlayVisible); 58 | }, 59 | 60 | handleShowOverlayButtonClick: function() { 61 | // this.setState('overlayVisible', true); 62 | this.getComponent('overlay').show(); 63 | }, 64 | 65 | handleOverlayHide: function() { 66 | // Synchronize the updated state of the o 67 | this.setState('overlayVisible', false); 68 | }, 69 | 70 | handleOverlayShow: function() { 71 | this.setState('overlayVisible', true); 72 | }, 73 | 74 | handleShowNotificationButtonClick: function() { 75 | raptorPubsub.emit('notification', { 76 | message: 'This is a notification' 77 | }); 78 | }, 79 | 80 | handleOverlayOk: function() { 81 | raptorPubsub.emit('notification', { 82 | message: 'You clicked the "Done" button!' 83 | }); 84 | }, 85 | 86 | handleOverlayCancel: function() { 87 | raptorPubsub.emit('notification', { 88 | message: 'You clicked the "Cancel" button!' 89 | }); 90 | }, 91 | 92 | handleRenderButtonClick: function() { 93 | button.renderSync({ 94 | label: 'Hello World' 95 | }) 96 | .appendTo(this.getEl('renderTarget')); 97 | }, 98 | 99 | handleRenderCheckboxButtonClick: function() { 100 | checkbox.renderSync({ 101 | label: 'Hello World', 102 | checked: true 103 | }) 104 | .appendTo(this.getEl('renderTarget')); 105 | }, 106 | 107 | handleRenderProgressBarButtonClick: function() { 108 | progressBar.renderSync({ 109 | steps: [ 110 | { 111 | label: 'Step 1' 112 | }, 113 | { 114 | label: 'Step 2' 115 | }, 116 | { 117 | label: 'Step 3', 118 | active: true 119 | }, 120 | { 121 | label: 'Step 4' 122 | } 123 | ] 124 | }) 125 | .appendTo(this.getEl('renderTarget')); 126 | }, 127 | 128 | handleChangeButtonSizeClick: function() { 129 | var nextButtonSize = buttonSizes[++currentButtonSize % buttonSizes.length]; 130 | this.state.buttonSize = nextButtonSize; 131 | }, 132 | 133 | handleChangeButtonVariantClick: function() { 134 | var nextButtonVariant = buttonVariants[++currentButtonVariant % buttonVariants.length]; 135 | this.state.buttonVariant = nextButtonVariant; 136 | }, 137 | 138 | handleToggleCheckboxButtonClick: function(event) { 139 | var checkbox = this.getComponent('toggleCheckbox'); 140 | checkbox.toggle(); 141 | }, 142 | 143 | handleAddTabButtonClick: function() { 144 | this.state.dynamicTabs = this.state.dynamicTabs.concat({ 145 | timestamp: '' + new Date() 146 | }); 147 | } 148 | }; -------------------------------------------------------------------------------- /src/components/app-main/index.marko: -------------------------------------------------------------------------------- 1 |
2 |

Hello!

3 | 4 | <@section title="Buttons"> 5 | 6 | 7 | 10 | 15 | 16 | 17 | 20 | 25 | 26 |
8 | Primary: 9 | 11 | 12 | 13 | 14 |
18 | Secondary:  19 | 21 | 22 | 23 | 24 |
27 |
28 | 33 | 37 |
38 | 39 | <@section title="Checkboxes"> 40 |

41 | 48 | 55 | 62 |

63 |

64 | 71 |

72 |

73 | Checked: 74 |

    75 | 76 | 77 |
  • ${key}
  • 78 | 79 | 80 |
81 |

82 |

83 | 90 | 95 |

96 | 97 | <@section title="Overlays"> 98 | 102 | 106 | 107 | 114 |

Overlay Demo

115 | This is an overlay! 116 |
117 | 118 | <@section title="Tabs"> 119 |

Static tabs

120 | 121 | <@tab label="Home">Content for Home 122 | <@tab label="Profile">Content for Profile 123 | <@tab label="Messages">Content for Messages 124 | 125 |

Dynamic tabs

126 | 127 | 128 | <@tab label=("Tab " + tabIndex)> 129 | Content for tab ${tabIndex}: ${tab.timestamp} 130 | 131 | 132 | 133 | 134 | 135 | <@section title="Miscellaneous"> 136 | 137 | <@step name="contact-info">Contact Info 138 | <@step name="interests">Interests 139 | <@step name="family">Family 140 | 141 |
142 | 143 |
144 | 145 | 146 | <@section title="Client-side Rendering"> 147 | 148 | 149 | 152 |
153 | 154 | <@section title="Fetch data"> 155 | 156 | 157 | <@section title="Markdown"> 158 | 159 | --- 160 | 161 | > This section demonstrates Markdown in Marko 162 | 163 | ## Marko Features 164 | 165 | - High performance 166 | - Small 167 | - Intuitive 168 | 169 | # H1 170 | ## H2 171 | ### H3 172 | #### H4 173 | ##### H5 174 | ###### H6 175 | 176 | [markojs.com](http://markojs.com/) 177 | 178 | _emphasis_ 179 | __strong__ 180 | 181 | --- 182 | 183 | 184 | 185 |
186 | -------------------------------------------------------------------------------- /src/components/app-main/style.less: -------------------------------------------------------------------------------- 1 | @import "../../global-style/variables.less"; 2 | 3 | .render-target { 4 | padding-top: 1em; 5 | padding-bottom: 1em; 6 | } -------------------------------------------------------------------------------- /src/components/app-markdown/code-generator.js: -------------------------------------------------------------------------------- 1 | var marked = require("marked"); 2 | 3 | function removeIndentation(str) { 4 | var indentMatches = /\s*\n(\s+)/.exec(str); 5 | if (indentMatches) { 6 | var indent = indentMatches[1]; 7 | str = str.replace(new RegExp("^" + indent, "mg"), ""); 8 | } 9 | return str; 10 | } 11 | 12 | module.exports = function(el, codegen) { 13 | var bodyText = removeIndentation(el.bodyText); 14 | var builder = codegen.builder; 15 | var html = marked(bodyText); 16 | return builder.html(builder.literal(html)); 17 | }; 18 | -------------------------------------------------------------------------------- /src/components/app-markdown/marko-tag.json: -------------------------------------------------------------------------------- 1 | { 2 | "body": "static-text", 3 | "preserve-whitespace": true 4 | } 5 | -------------------------------------------------------------------------------- /src/components/app-notification/index.marko: -------------------------------------------------------------------------------- 1 | class { 2 | onInput(input) { 3 | this.state = { 4 | timeout: input.timeout, 5 | message: input.message 6 | }; 7 | } 8 | 9 | onMount() { 10 | setTimeout(() => { 11 | this.el.style.opacity = 1; 12 | this.el.style.maxHeight = "60px"; 13 | }, 10); 14 | } 15 | 16 | fadeOut(callback) { 17 | this.el.style.opacity = 0; 18 | setTimeout(callback, 300); 19 | } 20 | } 21 | 22 |
$!{data.message}
23 | -------------------------------------------------------------------------------- /src/components/app-notification/style.less: -------------------------------------------------------------------------------- 1 | @import "../../global-style/variables.less"; 2 | 3 | @notification-bg: #2ecc71; 4 | @notification-border-color: darken(@notification-bg, 20%); 5 | 6 | .app-notification { 7 | opacity: 0; 8 | transition-property: max-height, opacity; 9 | transition-duration: 0.3s; 10 | transition-timing-function: ease-out; 11 | max-height: 0px; 12 | margin-top: 8px; 13 | background-color: @notification-bg; 14 | color: white; 15 | width: 100%; 16 | border: 2px solid @notification-border-color; 17 | padding-left: 1em; 18 | padding-right: 1em; 19 | line-height: 40px; 20 | vertical-align: middle; 21 | border-radius: 4px; 22 | box-shadow: 3px 3px 3px rgba(51, 65, 80, 0.35); 23 | } 24 | -------------------------------------------------------------------------------- /src/components/app-notifications/component.js: -------------------------------------------------------------------------------- 1 | var raptorPubsub = require("raptor-pubsub"); 2 | 3 | var nextId = 0; 4 | 5 | module.exports = { 6 | onInput(input) { 7 | this.state = { 8 | notifications: input.notifications || [] 9 | }; 10 | }, 11 | 12 | onMount() { 13 | var self = this; 14 | 15 | this.subscribeTo(raptorPubsub).on("notification", function(eventArgs) { 16 | var message = eventArgs.message; 17 | self.addNotification(message); 18 | }); 19 | }, 20 | 21 | addNotification(message) { 22 | var notifications = this.state.notifications; 23 | var notificationId = "notification" + nextId++; 24 | notifications = [ 25 | { 26 | message: message, 27 | id: notificationId 28 | } 29 | ].concat(notifications); 30 | 31 | this.setState("notifications", notifications); 32 | 33 | setTimeout(() => { 34 | this.removeNotification(notificationId); 35 | }, 3000); 36 | }, 37 | 38 | removeNotification(notificationId) { 39 | var notificationWidget = this.getComponent(notificationId); 40 | notificationWidget.fadeOut(() => { 41 | var notifications = this.state.notifications.filter(notification => { 42 | return notification.id !== notificationId; 43 | }); 44 | this.setState("notifications", notifications); 45 | }); 46 | } 47 | }; 48 | -------------------------------------------------------------------------------- /src/components/app-notifications/index.marko: -------------------------------------------------------------------------------- 1 | style { 2 | .app-notifications { 3 | position: fixed; 4 | top: 0px; 5 | width: 500px; 6 | left: 50%; 7 | margin-left: -250px; 8 | } 9 | } 10 | 11 |
12 | 13 | 14 | 15 |
16 | -------------------------------------------------------------------------------- /src/components/app-number-spinner/index.marko: -------------------------------------------------------------------------------- 1 | static function getClassNameForValue(value) { 2 | if (value < 0) { 3 | return "negative"; 4 | } else if (value > 0) { 5 | return "positive"; 6 | } 7 | } 8 | 9 | class { 10 | onInput(input) { 11 | this.state = { 12 | value: input.value || 0 13 | }; 14 | } 15 | 16 | handleIncrementClick(delta) { 17 | this.state.value += delta; 18 | } 19 | 20 | handleInputKeyUp(event) { 21 | const newValue = event.target.value; 22 | if (/^-?[0-9]+$/.test(newValue)) { 23 | this.state.value = parseInt(newValue, 10); 24 | } 25 | } 26 | } 27 | 28 |
29 | 30 | 31 | 32 |
33 | -------------------------------------------------------------------------------- /src/components/app-number-spinner/style.less: -------------------------------------------------------------------------------- 1 | .number-spinner button { 2 | background: #4285f4; 3 | color: #ffffff; 4 | border: 1px solid #1266f1; 5 | display: inline-block; 6 | padding: 12px 32px; 7 | height: 26px; 8 | line-height: 26px; 9 | vertical-align: middle; 10 | text-align: center; 11 | font-family: Helvetica, sans-serif; 12 | font-weight: 600; 13 | font-size: 16px; 14 | outline: 0; 15 | transition: none; 16 | margin: 0; 17 | padding: 0; 18 | padding-left: 13px; 19 | padding-right: 13px; 20 | cursor: pointer; 21 | } 22 | 23 | .number-spinner input { 24 | background: #fff; 25 | border: 1px solid #1266f1; 26 | display: inline-block; 27 | height: 26px; 28 | line-height: 26px; 29 | vertical-align: middle; 30 | text-align: center; 31 | font-family: Helvetica, sans-serif; 32 | font-weight: 600; 33 | font-size: 16px; 34 | outline: 0; 35 | transition: none; 36 | margin: 0; 37 | padding: 0; 38 | padding-left: 13px; 39 | padding-right: 13px; 40 | } 41 | 42 | .number-spinner.positive input { 43 | background: rgb(86, 239, 165); 44 | } 45 | 46 | .number-spinner.negative input { 47 | background: rgb(235, 182, 176); 48 | } 49 | -------------------------------------------------------------------------------- /src/components/app-overlay/component.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | onInput: function(input) { 3 | this.state = { 4 | width: input.width || "80%", 5 | visible: input.visible === true ? true : false, 6 | body: input.renderBody 7 | }; 8 | }, 9 | 10 | onMount: function() { 11 | this.fixPageScrolling(); 12 | }, 13 | 14 | onUpdate: function() { 15 | this.fixPageScrolling(); 16 | }, 17 | 18 | fixPageScrolling: function() { 19 | if (this.state.visible === true) { 20 | document.body.style.overflow = "hidden"; 21 | } else { 22 | document.body.style.overflow = ""; 23 | } 24 | }, 25 | 26 | hide: function() { 27 | this.setVisibility(false); 28 | }, 29 | show: function() { 30 | this.setVisibility(true); 31 | }, 32 | 33 | setVisibility: function(visible) { 34 | if (this.state.visible === visible) { 35 | // Visibility did not change... nothing to do 36 | return; 37 | } 38 | 39 | if (visible) { 40 | this.emit("show"); 41 | } else { 42 | this.emit("hide"); 43 | } 44 | 45 | this.setState("visible", visible); 46 | }, 47 | 48 | handleMaskClick: function() { 49 | this.hide(); 50 | }, 51 | handleCancelButtonClick: function() { 52 | this.emit("cancel", {}); 53 | this.hide(); 54 | }, 55 | handleDoneButtonClick: function() { 56 | var preventDefault = false; 57 | 58 | this.emit("ok", { 59 | preventDefault: function() { 60 | preventDefault = true; 61 | } 62 | }); 63 | 64 | if (!preventDefault) { 65 | this.hide(); 66 | } 67 | } 68 | }; 69 | -------------------------------------------------------------------------------- /src/components/app-overlay/index.marko: -------------------------------------------------------------------------------- 1 |
2 |
3 |
8 |
9 | <${input}/> 10 |
11 | 23 |
24 |
25 | -------------------------------------------------------------------------------- /src/components/app-overlay/marko-tag.json: -------------------------------------------------------------------------------- 1 | { 2 | "@width": "integer", 3 | "@visible": "boolean" 4 | } 5 | -------------------------------------------------------------------------------- /src/components/app-overlay/style.less: -------------------------------------------------------------------------------- 1 | .app-overlay > .app-overlay-mask { 2 | position: fixed; 3 | top: 0px; 4 | left: 0px; 5 | right: 0px; 6 | bottom: 0px; 7 | visibility: hidden; 8 | background-color: #000; 9 | opacity: 0; 10 | transition-property: opacity, visibility; 11 | transition-duration: 0.3s; 12 | transition-timing-function: ease-out; 13 | } 14 | 15 | .app-overlay.visible > .app-overlay-mask { 16 | visibility: visible; 17 | opacity: 0.8; 18 | z-index: 999; 19 | } 20 | 21 | .app-overlay > .app-overlay-container { 22 | position: fixed; 23 | top: -50%; 24 | transform: translateX(-50%) translateY(-50%); 25 | left: 50%; 26 | background-color: white; 27 | opacity: 0; 28 | transition-property: opacity, top; 29 | transition-duration: 0.2s; 30 | transition-timing-function: ease-out; 31 | border-radius: 5px; 32 | overflow: auto; 33 | z-index: 1000; 34 | } 35 | 36 | .app-overlay.visible > .app-overlay-container { 37 | top: 50%; 38 | opacity: 1; 39 | } 40 | 41 | .app-overlay-body { 42 | padding: 1em; 43 | } 44 | 45 | .app-overlay-footer { 46 | width: 100%; 47 | bottom: 0px; 48 | left: 0px; 49 | text-align: right; 50 | padding: 1em; 51 | padding-top: 2em; 52 | } 53 | 54 | .app-overlay > .app-overlay-container .app-overlay > .app-overlay-container { 55 | height: 100%; 56 | } 57 | 58 | .app-overlay 59 | > .app-overlay-container 60 | .app-overlay 61 | > .app-overlay-container 62 | .app-overlay-footer { 63 | position: absolute; 64 | height: 48px; 65 | left: 0px; 66 | bottom: 0px; 67 | width: 100%; 68 | padding-top: 0px; 69 | } 70 | -------------------------------------------------------------------------------- /src/components/app-progress-bar/component.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | onInput: function(input) { 3 | var steps = input.steps || []; 4 | var activeIndex = 0; 5 | 6 | steps.forEach(function(step) { 7 | if (step.active) { 8 | activeIndex = steps.length; 9 | } 10 | }); 11 | 12 | this.state = { 13 | steps: steps, 14 | activeIndex: activeIndex 15 | }; 16 | }, 17 | 18 | setCurrentStepIndex(index) { 19 | if (this.state.activeIndex === index) { 20 | return; 21 | } 22 | 23 | var defaultPrevented = false; 24 | 25 | this.emit("beforeChange", { 26 | step: this.state.steps[this.state.activeIndex], 27 | preventDefault() { 28 | defaultPrevented = true; 29 | } 30 | }); 31 | 32 | if (defaultPrevented) { 33 | return; 34 | } 35 | 36 | var newStep = this.state.steps[index]; 37 | 38 | this.state.activeIndex = index; 39 | 40 | this.emit("change", { 41 | name: newStep.name, 42 | index: index 43 | }); 44 | }, 45 | 46 | handleStepClick(stepIndex, event) { 47 | event.preventDefault(); 48 | this.setCurrentStepIndex(stepIndex); 49 | } 50 | }; 51 | -------------------------------------------------------------------------------- /src/components/app-progress-bar/index.marko: -------------------------------------------------------------------------------- 1 |
2 | 3 |
6 | 7 | 8 | ${step.label} 9 | 10 | <${step}/> 11 | 12 | 13 |
14 | 15 |
16 | 22 | 23 | 24 | 25 |
26 |
27 | 28 |
29 | -------------------------------------------------------------------------------- /src/components/app-progress-bar/marko-tag.json: -------------------------------------------------------------------------------- 1 | { 2 | "@steps []": { 3 | "@name": "string", 4 | "@label": "string", 5 | "@active": "boolean" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/components/app-progress-bar/style.less: -------------------------------------------------------------------------------- 1 | @import "../../global-style/variables.less"; 2 | 3 | .app-progress-bar .progress-step { 4 | display: inline-block; 5 | height: 2em; 6 | } 7 | 8 | .app-progress-bar a.progress-step { 9 | display: inline-block; 10 | background-color: @button-secondary-bg; 11 | color: @button-secondary-fg; 12 | padding-left: 28px; 13 | padding-right: 8px; 14 | height: 2em; 15 | font-size: 1em; 16 | line-height: 2em; 17 | vertical-align: middle; 18 | } 19 | 20 | .app-progress-bar div.progress-step:first-child a.progress-step { 21 | padding-left: 8px; 22 | } 23 | 24 | .app-progress-bar .progress-step.active a.progress-step { 25 | background-color: @button-bg; 26 | color: @button-fg; 27 | } 28 | 29 | div.progress-step-end { 30 | display: inline-block; 31 | width: 20px; 32 | height: 2em; 33 | position: relative; 34 | font-size: 1em; 35 | line-height: 2em; 36 | vertical-align: middle; 37 | margin-right: -20px; 38 | } 39 | 40 | svg.progress-step-end { 41 | display: inline-block; 42 | width: 100%; 43 | height: 100%; 44 | } 45 | 46 | polygon.progress-step-end { 47 | fill: @button-secondary-bg; 48 | } 49 | 50 | .app-progress-bar .progress-step.active polygon.progress-step-end { 51 | fill: @button-bg; 52 | } 53 | -------------------------------------------------------------------------------- /src/components/app-sections/index.marko: -------------------------------------------------------------------------------- 1 | static function getAnchorName(section) { 2 | if (!section.anchorName) { 3 | section.anchorName = section.title.replace(/[^a-zA-Z]+/g, "-"); 4 | } 5 | 6 | return section.anchorName; 7 | } 8 | 9 |
10 |
11 | 18 |
19 |
20 | 21 |
22 |

${section.title}

23 |
24 | <${section}/> 25 |
26 |
27 | 28 |
29 |
30 | -------------------------------------------------------------------------------- /src/components/app-sections/marko-tag.json: -------------------------------------------------------------------------------- 1 | { 2 | "@sections
[]": { 3 | "@title": "string" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/components/app-sections/style.less: -------------------------------------------------------------------------------- 1 | @import "../../global-style/variables.less"; 2 | 3 | .app-sections { 4 | } 5 | 6 | .tableOfContents { 7 | position: fixed; 8 | right: 15px; 9 | top: 15px; 10 | background-color: #f7f7f9; 11 | box-shadow: 4px 4px 4px rgba(0, 0, 0, 0.2); 12 | padding: 1em; 13 | } 14 | 15 | .app-sections .section { 16 | margin-top: 40px; 17 | } 18 | 19 | .app-sections .section > h2 { 20 | border-bottom: 1px solid #c0c0c0; 21 | margin-bottom: 0.5em; 22 | } 23 | -------------------------------------------------------------------------------- /src/components/app-state-select/CA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marko-js-samples/marko-webpack/2675bce63a7977723adc1759f1bfd61e0e802744/src/components/app-state-select/CA.png -------------------------------------------------------------------------------- /src/components/app-state-select/CO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marko-js-samples/marko-webpack/2675bce63a7977723adc1759f1bfd61e0e802744/src/components/app-state-select/CO.png -------------------------------------------------------------------------------- /src/components/app-state-select/index.marko: -------------------------------------------------------------------------------- 1 | import imgCO from "./CO.png"; 2 | import imgCA from "./CA.png"; 3 | 4 | static const states = [ 5 | { 6 | name: "Colorado", 7 | value: "CO", 8 | img: imgCO 9 | }, 10 | { 11 | name: "California", 12 | value: "CA", 13 | img: imgCA 14 | } 15 | ]; 16 | 17 | class { 18 | onInput() { 19 | this.state = { 20 | stateIndex: null 21 | }; 22 | } 23 | 24 | handleSelectChange(event, selectEl) { 25 | if (selectEl.selectedIndex === 0) { 26 | this.state.stateIndex = null; 27 | } else { 28 | this.state.stateIndex = selectEl.selectedIndex - 1; 29 | } 30 | } 31 | } 32 | 33 | style { 34 | .state-icon { 35 | width: 75px; 36 | height: 50px; 37 | } 38 | } 39 | 40 |
41 |

Choose a state

42 | 50 | 51 |
You selected: ${states[state.stateIndex].value}
52 | 53 | 54 |
55 | -------------------------------------------------------------------------------- /src/components/app-tabs/index.marko: -------------------------------------------------------------------------------- 1 | class { 2 | onInput(input) { 3 | var activeIndex = 0; 4 | 5 | var tabs = input.tabs; 6 | 7 | if (tabs) { 8 | tabs.forEach(function(tab, i) { 9 | if (tab.active) { 10 | activeIndex = i; 11 | } 12 | }); 13 | } 14 | 15 | this.state = { 16 | activeIndex: activeIndex 17 | }; 18 | } 19 | 20 | setActiveIndex(newActiveIndex) { 21 | this.state.activeIndex = newActiveIndex; 22 | } 23 | 24 | handleTabClick(tabIndex, event) { 25 | this.setActiveIndex(tabIndex); 26 | event.preventDefault(); 27 | } 28 | } 29 | 30 |
31 | 43 |
44 |
45 | 46 |
47 | <${tab.renderBody}/> 48 |
49 | 50 |
51 |
52 | -------------------------------------------------------------------------------- /src/components/app-tabs/marko-tag.json: -------------------------------------------------------------------------------- 1 | { 2 | "@tabs []": { 3 | "@label": "string" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/components/app-tabs/style.less: -------------------------------------------------------------------------------- 1 | @import "../../global-style/variables.less"; 2 | 3 | .app-tabs .tab { 4 | 5 | } 6 | 7 | .app-tabs ul.tab-nav { 8 | padding-left: 0; 9 | list-style: none; 10 | } 11 | 12 | .app-tabs ul.tab-nav > li { 13 | float: left; 14 | } 15 | 16 | .app-tabs ul.tab-nav > li > a { 17 | margin-right: 2px; 18 | line-height: 1.42857143; 19 | border: 1px solid @button-secondary-bg; 20 | margin-left: 10px; 21 | border-radius: 4px 4px 0 0; 22 | padding-left: 1em; 23 | padding-right: 1em; 24 | padding-bottom: 0.5em; 25 | padding-top: 0.5em; 26 | display: inline-block; 27 | margin-bottom: -1px; 28 | background-color: @button-secondary-bg; 29 | color: @button-secondary-fg; 30 | } 31 | 32 | .app-tabs ul.tab-nav > li:first-child > a { 33 | margin-left: 0px; 34 | } 35 | 36 | .app-tabs .tab.active > a { 37 | background-color: transparent; 38 | border-bottom: 1px solid white; 39 | color: inherit; 40 | } 41 | 42 | .app-tabs .tab-panes { 43 | clear: both; 44 | 45 | } 46 | 47 | .app-tabs .tab-pane { 48 | display: none; 49 | border: 1px solid @button-secondary-bg; 50 | padding: 1em; 51 | } 52 | 53 | .app-tabs .tab-pane.active { 54 | display: block; 55 | } -------------------------------------------------------------------------------- /src/global-style/index.less: -------------------------------------------------------------------------------- 1 | @import "./variables.less"; 2 | 3 | body { 4 | font-family: @font-family; 5 | color: @font-color; 6 | padding: 0; 7 | margin: 0; 8 | } 9 | 10 | * { 11 | -moz-box-sizing: border-box; 12 | -webkit-box-sizing: border-box; 13 | box-sizing: border-box; 14 | } 15 | 16 | button::-moz-focus-inner, 17 | input::-moz-focus-inner { 18 | border: 0; 19 | padding: 0; 20 | } 21 | 22 | ul, 23 | ol { 24 | margin-top: 0; 25 | margin-bottom: 0; 26 | padding-top: 0; 27 | padding-bottom: 0; 28 | } 29 | 30 | form, 31 | img { 32 | margin: 0; 33 | padding: 0; 34 | } 35 | 36 | img { 37 | border-width: 0; 38 | } 39 | 40 | textarea, 41 | input { 42 | font-size: 14px; 43 | font-family: @font-family; 44 | } 45 | 46 | select { 47 | margin: 0; 48 | } 49 | 50 | table { 51 | border-collapse: separate; 52 | border-spacing: 0; 53 | } 54 | 55 | td { 56 | padding: 0; 57 | margin: 0; 58 | border-width: 0; 59 | } 60 | 61 | pre { 62 | margin: 0; 63 | padding: 0; 64 | } 65 | 66 | h1, 67 | h2, 68 | h3 { 69 | margin-top: 0; 70 | padding-top: 0.5em; 71 | margin-bottom: 0.2em; 72 | } 73 | 74 | h1 { 75 | font-size: 1.5em; 76 | color: @dark-color; 77 | 78 | a { 79 | color: @dark-color; 80 | } 81 | } 82 | 83 | h2 { 84 | font-size: 1.25em; 85 | } 86 | 87 | h3 { 88 | font-size: 1em; 89 | } 90 | 91 | label { 92 | display: block; 93 | font-size: 0.9em; 94 | } 95 | 96 | button, 97 | input, 98 | select, 99 | textarea { 100 | font-size: 100%; 101 | margin: 0; 102 | } 103 | 104 | button { 105 | padding: 0; 106 | cursor: pointer; 107 | line-height: normal; 108 | border: 0; 109 | } 110 | 111 | a { 112 | color: @link-color; 113 | text-decoration: none; 114 | } 115 | 116 | input[type="text"] { 117 | border: 1px solid #c0c0c0; 118 | } 119 | -------------------------------------------------------------------------------- /src/global-style/variables.less: -------------------------------------------------------------------------------- 1 | @medium-color: #2980b9; 2 | @light-color: lighten(@medium-color, 35%); 3 | @dark-color: darken(@medium-color, 25%); 4 | 5 | @link-color: @medium-color; 6 | 7 | @font-color: #333; 8 | @font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", 9 | Helvetica, Arial, sans-serif; 10 | 11 | @button-bg: @medium-color; 12 | @button-fg: #fff; 13 | 14 | @button-secondary-bg: #999999; 15 | @button-secondary-fg: #fff; 16 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | 3 | const app = express(); 4 | const port = process.env.PORT || 8080; 5 | 6 | // Enable gzip compression for all HTTP responses 7 | import compression from "compression"; 8 | app.use(compression()); 9 | 10 | // Allow all of the generated files to be served up by Express 11 | import serveStatic from "serve-static"; 12 | app.use("/static", serveStatic("dist/client")); 13 | 14 | // Initialize mock service routes 15 | import initServices from "./services/routes"; 16 | initServices(app); 17 | 18 | // Map the "/" route to the home page 19 | import HomePage from "./pages/home"; 20 | app.get("/", HomePage); 21 | 22 | // Start the server 23 | app.listen(port, err => { 24 | if (err) { 25 | throw err; 26 | } 27 | 28 | if (port !== "0") { 29 | console.log(`Listening on port ${port}`); 30 | } 31 | }); 32 | -------------------------------------------------------------------------------- /src/pages/home/index.js: -------------------------------------------------------------------------------- 1 | import template from "./template.marko"; 2 | 3 | export default (req, res) => { 4 | res.setHeader("Content-Type", "text/html; charset=utf-8"); 5 | template.render({}, res); 6 | }; 7 | -------------------------------------------------------------------------------- /src/pages/home/template.marko: -------------------------------------------------------------------------------- 1 | style.less { 2 | @import "../../global-style/index.less"; 3 | } 4 | 5 | 6 | <@title>Marko UI Components Playground 7 | <@styles> 8 | 12 | 13 | <@body> 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/services/mock-users-data.json: -------------------------------------------------------------------------------- 1 | [{"id":1,"firstName":"Evelyn","lastName":"Patterson","email":"epatterson0@ehow.com","avatar":"https://robohash.org/quinihilomnis.bmp?size=50x50&set=set1"}, 2 | {"id":2,"firstName":"Matthew","lastName":"Moore","email":"mmoore1@squidoo.com","avatar":"https://robohash.org/eaquemolestiasdistinctio.png?size=50x50&set=set1"}, 3 | {"id":3,"firstName":"Ryan","lastName":"Mendoza","email":"rmendoza2@bloglines.com","avatar":"https://robohash.org/corruptiipsumlabore.png?size=50x50&set=set1"}, 4 | {"id":4,"firstName":"Matthew","lastName":"Rice","email":"mrice3@cafepress.com","avatar":"https://robohash.org/voluptatererumpariatur.bmp?size=50x50&set=set1"}, 5 | {"id":5,"firstName":"Ryan","lastName":"Bradley","email":"rbradley4@yahoo.com","avatar":"https://robohash.org/inconsequaturtempora.bmp?size=50x50&set=set1"}, 6 | {"id":6,"firstName":"James","lastName":"Simmons","email":"jsimmons5@nbcnews.com","avatar":"https://robohash.org/iustoadest.jpg?size=50x50&set=set1"}, 7 | {"id":7,"firstName":"Heather","lastName":"Wagner","email":"hwagner6@state.tx.us","avatar":"https://robohash.org/sitlaborumdolorem.png?size=50x50&set=set1"}, 8 | {"id":8,"firstName":"Norma","lastName":"Payne","email":"npayne7@kickstarter.com","avatar":"https://robohash.org/undeetducimus.bmp?size=50x50&set=set1"}, 9 | {"id":9,"firstName":"Jeremy","lastName":"Wilson","email":"jwilson8@amazonaws.com","avatar":"https://robohash.org/quisutlaboriosam.png?size=50x50&set=set1"}, 10 | {"id":10,"firstName":"Sean","lastName":"Palmer","email":"spalmer9@eepurl.com","avatar":"https://robohash.org/quibusdamnullacorporis.jpg?size=50x50&set=set1"}, 11 | {"id":11,"firstName":"Tina","lastName":"Wilson","email":"twilsona@businesswire.com","avatar":"https://robohash.org/ipsumdebitisfugiat.png?size=50x50&set=set1"}, 12 | {"id":12,"firstName":"Andrew","lastName":"Carter","email":"acarterb@admin.ch","avatar":"https://robohash.org/consequaturidipsa.jpg?size=50x50&set=set1"}, 13 | {"id":13,"firstName":"Mildred","lastName":"Palmer","email":"mpalmerc@e-recht24.de","avatar":"https://robohash.org/nequereiciendisomnis.png?size=50x50&set=set1"}, 14 | {"id":14,"firstName":"Theresa","lastName":"Bowman","email":"tbowmand@cocolog-nifty.com","avatar":"https://robohash.org/quosexpeditaest.bmp?size=50x50&set=set1"}, 15 | {"id":15,"firstName":"Denise","lastName":"Dixon","email":"ddixone@bigcartel.com","avatar":"https://robohash.org/utrepudiandaesequi.png?size=50x50&set=set1"}, 16 | {"id":16,"firstName":"Nancy","lastName":"Gardner","email":"ngardnerf@godaddy.com","avatar":"https://robohash.org/molestiaeoditvoluptatibus.png?size=50x50&set=set1"}, 17 | {"id":17,"firstName":"Susan","lastName":"Taylor","email":"staylorg@php.net","avatar":"https://robohash.org/reiciendisetrepellendus.bmp?size=50x50&set=set1"}, 18 | {"id":18,"firstName":"Christopher","lastName":"Olson","email":"colsonh@shop-pro.jp","avatar":"https://robohash.org/estautfugit.bmp?size=50x50&set=set1"}, 19 | {"id":19,"firstName":"Kathleen","lastName":"Hall","email":"khalli@psu.edu","avatar":"https://robohash.org/illoutcorrupti.jpg?size=50x50&set=set1"}, 20 | {"id":20,"firstName":"Catherine","lastName":"Dixon","email":"cdixonj@4shared.com","avatar":"https://robohash.org/estatquepraesentium.png?size=50x50&set=set1"}, 21 | {"id":21,"firstName":"Jason","lastName":"Jones","email":"jjonesk@scribd.com","avatar":"https://robohash.org/sapienteinventoreesse.jpg?size=50x50&set=set1"}, 22 | {"id":22,"firstName":"Michelle","lastName":"Barnes","email":"mbarnesl@ycombinator.com","avatar":"https://robohash.org/excepturietnobis.jpg?size=50x50&set=set1"}, 23 | {"id":23,"firstName":"Roy","lastName":"Hunt","email":"rhuntm@mit.edu","avatar":"https://robohash.org/voluptaspossimustenetur.bmp?size=50x50&set=set1"}, 24 | {"id":24,"firstName":"Samuel","lastName":"Olson","email":"solsonn@blog.com","avatar":"https://robohash.org/utlaboreprovident.png?size=50x50&set=set1"}, 25 | {"id":25,"firstName":"Pamela","lastName":"Garcia","email":"pgarciao@nydailynews.com","avatar":"https://robohash.org/cumplaceatamet.bmp?size=50x50&set=set1"}, 26 | {"id":26,"firstName":"Janet","lastName":"Lawrence","email":"jlawrencep@google.pl","avatar":"https://robohash.org/autnatusmolestiae.bmp?size=50x50&set=set1"}, 27 | {"id":27,"firstName":"Charles","lastName":"Myers","email":"cmyersq@ask.com","avatar":"https://robohash.org/quasiplaceatomnis.jpg?size=50x50&set=set1"}, 28 | {"id":28,"firstName":"Kathleen","lastName":"Moore","email":"kmoorer@deviantart.com","avatar":"https://robohash.org/impeditrerumvel.bmp?size=50x50&set=set1"}, 29 | {"id":29,"firstName":"Jerry","lastName":"Price","email":"jprices@google.com","avatar":"https://robohash.org/nostrumetdolores.png?size=50x50&set=set1"}, 30 | {"id":30,"firstName":"Tammy","lastName":"Kelly","email":"tkellyt@unicef.org","avatar":"https://robohash.org/quibusdamsedquos.png?size=50x50&set=set1"}, 31 | {"id":31,"firstName":"Katherine","lastName":"Ford","email":"kfordu@tmall.com","avatar":"https://robohash.org/explicaboautdolorem.png?size=50x50&set=set1"}, 32 | {"id":32,"firstName":"Eric","lastName":"Kelley","email":"ekelleyv@amazon.co.uk","avatar":"https://robohash.org/inciduntquiaaut.bmp?size=50x50&set=set1"}, 33 | {"id":33,"firstName":"Paula","lastName":"Fernandez","email":"pfernandezw@clickbank.net","avatar":"https://robohash.org/accusamusestdoloribus.jpg?size=50x50&set=set1"}, 34 | {"id":34,"firstName":"Christopher","lastName":"Robertson","email":"crobertsonx@reference.com","avatar":"https://robohash.org/quamquisconsequatur.bmp?size=50x50&set=set1"}, 35 | {"id":35,"firstName":"Janet","lastName":"Alexander","email":"jalexandery@bing.com","avatar":"https://robohash.org/etmolestiaeoccaecati.bmp?size=50x50&set=set1"}, 36 | {"id":36,"firstName":"Melissa","lastName":"Harrison","email":"mharrisonz@nih.gov","avatar":"https://robohash.org/ipsumsitveritatis.png?size=50x50&set=set1"}, 37 | {"id":37,"firstName":"Lori","lastName":"Sanders","email":"lsanders10@friendfeed.com","avatar":"https://robohash.org/deseruntcupiditateet.png?size=50x50&set=set1"}, 38 | {"id":38,"firstName":"Janice","lastName":"Bradley","email":"jbradley11@jimdo.com","avatar":"https://robohash.org/voluptatumutiste.bmp?size=50x50&set=set1"}, 39 | {"id":39,"firstName":"Sharon","lastName":"Hernandez","email":"shernandez12@stanford.edu","avatar":"https://robohash.org/quiacumenim.png?size=50x50&set=set1"}, 40 | {"id":40,"firstName":"Frank","lastName":"Dunn","email":"fdunn13@hp.com","avatar":"https://robohash.org/quosedsit.png?size=50x50&set=set1"}, 41 | {"id":41,"firstName":"Dorothy","lastName":"Reid","email":"dreid14@ucoz.com","avatar":"https://robohash.org/minusquosnon.bmp?size=50x50&set=set1"}, 42 | {"id":42,"firstName":"Norma","lastName":"Rodriguez","email":"nrodriguez15@vkontakte.ru","avatar":"https://robohash.org/autmaximeeum.jpg?size=50x50&set=set1"}, 43 | {"id":43,"firstName":"Anna","lastName":"Ryan","email":"aryan16@quantcast.com","avatar":"https://robohash.org/dolorautquisquam.jpg?size=50x50&set=set1"}, 44 | {"id":44,"firstName":"Eric","lastName":"Foster","email":"efoster17@spotify.com","avatar":"https://robohash.org/doloresveroad.bmp?size=50x50&set=set1"}, 45 | {"id":45,"firstName":"Rebecca","lastName":"Ray","email":"rray18@tiny.cc","avatar":"https://robohash.org/nihilipsamin.png?size=50x50&set=set1"}, 46 | {"id":46,"firstName":"Marilyn","lastName":"Burke","email":"mburke19@accuweather.com","avatar":"https://robohash.org/aututdelectus.bmp?size=50x50&set=set1"}, 47 | {"id":47,"firstName":"Randy","lastName":"Daniels","email":"rdaniels1a@ox.ac.uk","avatar":"https://robohash.org/voluptaseumipsa.png?size=50x50&set=set1"}, 48 | {"id":48,"firstName":"Jeremy","lastName":"Arnold","email":"jarnold1b@uiuc.edu","avatar":"https://robohash.org/rerumeaquelabore.png?size=50x50&set=set1"}, 49 | {"id":49,"firstName":"Lisa","lastName":"Riley","email":"lriley1c@cpanel.net","avatar":"https://robohash.org/eiusautaliquid.jpg?size=50x50&set=set1"}, 50 | {"id":50,"firstName":"Kevin","lastName":"Franklin","email":"kfranklin1d@google.ru","avatar":"https://robohash.org/omnissuscipitquis.png?size=50x50&set=set1"}, 51 | {"id":51,"firstName":"Craig","lastName":"Mason","email":"cmason1e@geocities.com","avatar":"https://robohash.org/doloremdignissimoscum.jpg?size=50x50&set=set1"}, 52 | {"id":52,"firstName":"Juan","lastName":"Hart","email":"jhart1f@apache.org","avatar":"https://robohash.org/omniscorporisimpedit.bmp?size=50x50&set=set1"}, 53 | {"id":53,"firstName":"Helen","lastName":"Allen","email":"hallen1g@cnn.com","avatar":"https://robohash.org/corruptidistinctioquaerat.bmp?size=50x50&set=set1"}, 54 | {"id":54,"firstName":"Chris","lastName":"Boyd","email":"cboyd1h@vistaprint.com","avatar":"https://robohash.org/totamquaeratassumenda.png?size=50x50&set=set1"}, 55 | {"id":55,"firstName":"Barbara","lastName":"Bradley","email":"bbradley1i@google.co.uk","avatar":"https://robohash.org/explicaboquiadignissimos.png?size=50x50&set=set1"}, 56 | {"id":56,"firstName":"Linda","lastName":"Boyd","email":"lboyd1j@miitbeian.gov.cn","avatar":"https://robohash.org/sednumquameos.bmp?size=50x50&set=set1"}, 57 | {"id":57,"firstName":"Laura","lastName":"Green","email":"lgreen1k@si.edu","avatar":"https://robohash.org/voluptasquoharum.jpg?size=50x50&set=set1"}, 58 | {"id":58,"firstName":"Doris","lastName":"Thomas","email":"dthomas1l@github.com","avatar":"https://robohash.org/quisitvoluptas.bmp?size=50x50&set=set1"}, 59 | {"id":59,"firstName":"Willie","lastName":"Romero","email":"wromero1m@ycombinator.com","avatar":"https://robohash.org/distinctioexsed.png?size=50x50&set=set1"}, 60 | {"id":60,"firstName":"Virginia","lastName":"Cole","email":"vcole1n@imgur.com","avatar":"https://robohash.org/idcommodiiure.png?size=50x50&set=set1"}, 61 | {"id":61,"firstName":"Larry","lastName":"Rogers","email":"lrogers1o@mediafire.com","avatar":"https://robohash.org/consecteturveleos.jpg?size=50x50&set=set1"}, 62 | {"id":62,"firstName":"Stephen","lastName":"Brooks","email":"sbrooks1p@narod.ru","avatar":"https://robohash.org/etsuntunde.jpg?size=50x50&set=set1"}, 63 | {"id":63,"firstName":"Joseph","lastName":"Lee","email":"jlee1q@deviantart.com","avatar":"https://robohash.org/exnamqui.png?size=50x50&set=set1"}, 64 | {"id":64,"firstName":"Roger","lastName":"Long","email":"rlong1r@businessweek.com","avatar":"https://robohash.org/velnonnesciunt.bmp?size=50x50&set=set1"}, 65 | {"id":65,"firstName":"Virginia","lastName":"Harper","email":"vharper1s@stanford.edu","avatar":"https://robohash.org/delenitidoloresdolores.jpg?size=50x50&set=set1"}, 66 | {"id":66,"firstName":"Fred","lastName":"Robertson","email":"frobertson1t@tripod.com","avatar":"https://robohash.org/quononcumque.bmp?size=50x50&set=set1"}, 67 | {"id":67,"firstName":"Ernest","lastName":"Price","email":"eprice1u@devhub.com","avatar":"https://robohash.org/consequunturestesse.bmp?size=50x50&set=set1"}, 68 | {"id":68,"firstName":"Linda","lastName":"Murray","email":"lmurray1v@google.it","avatar":"https://robohash.org/ipsameligendiquia.bmp?size=50x50&set=set1"}, 69 | {"id":69,"firstName":"Peter","lastName":"Dean","email":"pdean1w@webnode.com","avatar":"https://robohash.org/auttotamporro.jpg?size=50x50&set=set1"}, 70 | {"id":70,"firstName":"Steven","lastName":"Baker","email":"sbaker1x@abc.net.au","avatar":"https://robohash.org/enimharumlaborum.jpg?size=50x50&set=set1"}, 71 | {"id":71,"firstName":"Linda","lastName":"Smith","email":"lsmith1y@google.it","avatar":"https://robohash.org/sitautsit.jpg?size=50x50&set=set1"}, 72 | {"id":72,"firstName":"Lillian","lastName":"Diaz","email":"ldiaz1z@exblog.jp","avatar":"https://robohash.org/autperspiciatisrepellat.jpg?size=50x50&set=set1"}, 73 | {"id":73,"firstName":"Alan","lastName":"Smith","email":"asmith20@cpanel.net","avatar":"https://robohash.org/minusquiaest.png?size=50x50&set=set1"}, 74 | {"id":74,"firstName":"Clarence","lastName":"Hill","email":"chill21@prweb.com","avatar":"https://robohash.org/ametetnostrum.bmp?size=50x50&set=set1"}, 75 | {"id":75,"firstName":"Paula","lastName":"White","email":"pwhite22@cocolog-nifty.com","avatar":"https://robohash.org/assumendaomnisearum.png?size=50x50&set=set1"}, 76 | {"id":76,"firstName":"Kathryn","lastName":"Wright","email":"kwright23@msu.edu","avatar":"https://robohash.org/temporesaepeenim.bmp?size=50x50&set=set1"}, 77 | {"id":77,"firstName":"Roger","lastName":"Hill","email":"rhill24@tripod.com","avatar":"https://robohash.org/erroretquia.bmp?size=50x50&set=set1"}, 78 | {"id":78,"firstName":"Todd","lastName":"Moreno","email":"tmoreno25@mit.edu","avatar":"https://robohash.org/quodinciduntmodi.jpg?size=50x50&set=set1"}, 79 | {"id":79,"firstName":"George","lastName":"Brown","email":"gbrown26@myspace.com","avatar":"https://robohash.org/mollitiaminusexercitationem.jpg?size=50x50&set=set1"}, 80 | {"id":80,"firstName":"Harry","lastName":"Dean","email":"hdean27@de.vu","avatar":"https://robohash.org/veldolordolorum.jpg?size=50x50&set=set1"}, 81 | {"id":81,"firstName":"Norma","lastName":"Perez","email":"nperez28@ebay.com","avatar":"https://robohash.org/totamesseexplicabo.png?size=50x50&set=set1"}, 82 | {"id":82,"firstName":"Cheryl","lastName":"Gilbert","email":"cgilbert29@ucsd.edu","avatar":"https://robohash.org/fugiatabfacere.bmp?size=50x50&set=set1"}, 83 | {"id":83,"firstName":"Willie","lastName":"Myers","email":"wmyers2a@ow.ly","avatar":"https://robohash.org/earumnoniste.bmp?size=50x50&set=set1"}, 84 | {"id":84,"firstName":"Fred","lastName":"Turner","email":"fturner2b@nasa.gov","avatar":"https://robohash.org/quisquamautemillo.bmp?size=50x50&set=set1"}, 85 | {"id":85,"firstName":"Harold","lastName":"Garza","email":"hgarza2c@simplemachines.org","avatar":"https://robohash.org/voluptassuntdebitis.bmp?size=50x50&set=set1"}, 86 | {"id":86,"firstName":"Judith","lastName":"Edwards","email":"jedwards2d@about.com","avatar":"https://robohash.org/inciduntinnesciunt.bmp?size=50x50&set=set1"}, 87 | {"id":87,"firstName":"James","lastName":"Hall","email":"jhall2e@infoseek.co.jp","avatar":"https://robohash.org/placeatquianemo.bmp?size=50x50&set=set1"}, 88 | {"id":88,"firstName":"Earl","lastName":"Phillips","email":"ephillips2f@technorati.com","avatar":"https://robohash.org/quodestcommodi.jpg?size=50x50&set=set1"}, 89 | {"id":89,"firstName":"Bruce","lastName":"Garza","email":"bgarza2g@php.net","avatar":"https://robohash.org/sapienteeosautem.bmp?size=50x50&set=set1"}, 90 | {"id":90,"firstName":"Joyce","lastName":"Simmons","email":"jsimmons2h@devhub.com","avatar":"https://robohash.org/voluptatemvelitofficiis.png?size=50x50&set=set1"}, 91 | {"id":91,"firstName":"Pamela","lastName":"Carr","email":"pcarr2i@scientificamerican.com","avatar":"https://robohash.org/quiiustoadipisci.bmp?size=50x50&set=set1"}, 92 | {"id":92,"firstName":"Debra","lastName":"Cox","email":"dcox2j@ucoz.com","avatar":"https://robohash.org/atquequoet.png?size=50x50&set=set1"}, 93 | {"id":93,"firstName":"Cynthia","lastName":"Murray","email":"cmurray2k@trellian.com","avatar":"https://robohash.org/voluptassuscipitipsa.bmp?size=50x50&set=set1"}, 94 | {"id":94,"firstName":"Margaret","lastName":"Bennett","email":"mbennett2l@jugem.jp","avatar":"https://robohash.org/magniettempore.png?size=50x50&set=set1"}, 95 | {"id":95,"firstName":"Dennis","lastName":"Gonzalez","email":"dgonzalez2m@prlog.org","avatar":"https://robohash.org/laboriosamconsequaturinventore.bmp?size=50x50&set=set1"}, 96 | {"id":96,"firstName":"Janice","lastName":"Adams","email":"jadams2n@vk.com","avatar":"https://robohash.org/facilismolestiaedeserunt.png?size=50x50&set=set1"}, 97 | {"id":97,"firstName":"Billy","lastName":"Miller","email":"bmiller2o@pen.io","avatar":"https://robohash.org/ullamreiciendiset.png?size=50x50&set=set1"}, 98 | {"id":98,"firstName":"Sara","lastName":"Stevens","email":"sstevens2p@vimeo.com","avatar":"https://robohash.org/utquasiquia.bmp?size=50x50&set=set1"}, 99 | {"id":99,"firstName":"Julia","lastName":"Gonzales","email":"jgonzales2q@chicagotribune.com","avatar":"https://robohash.org/ipsalaudantiumexercitationem.bmp?size=50x50&set=set1"}, 100 | {"id":100,"firstName":"Theresa","lastName":"Carter","email":"tcarter2r@google.fr","avatar":"https://robohash.org/quiconsequaturvoluptas.jpg?size=50x50&set=set1"}, 101 | {"id":101,"firstName":"Robin","lastName":"Henderson","email":"rhenderson2s@wufoo.com","avatar":"https://robohash.org/suntdoloresquaerat.jpg?size=50x50&set=set1"}, 102 | {"id":102,"firstName":"Helen","lastName":"Evans","email":"hevans2t@hc360.com","avatar":"https://robohash.org/cupiditatesolutavelit.jpg?size=50x50&set=set1"}, 103 | {"id":103,"firstName":"Paula","lastName":"Dunn","email":"pdunn2u@mapquest.com","avatar":"https://robohash.org/similiquesequinatus.bmp?size=50x50&set=set1"}, 104 | {"id":104,"firstName":"Lawrence","lastName":"Burke","email":"lburke2v@cloudflare.com","avatar":"https://robohash.org/veniaminventoresit.bmp?size=50x50&set=set1"}, 105 | {"id":105,"firstName":"Sharon","lastName":"Carr","email":"scarr2w@google.com.hk","avatar":"https://robohash.org/etaliquamaut.jpg?size=50x50&set=set1"}, 106 | {"id":106,"firstName":"Stephanie","lastName":"Brooks","email":"sbrooks2x@shareasale.com","avatar":"https://robohash.org/quiacorporisquia.bmp?size=50x50&set=set1"}, 107 | {"id":107,"firstName":"Catherine","lastName":"Stone","email":"cstone2y@jigsy.com","avatar":"https://robohash.org/nonvoluptateaperiam.jpg?size=50x50&set=set1"}, 108 | {"id":108,"firstName":"Russell","lastName":"Taylor","email":"rtaylor2z@lycos.com","avatar":"https://robohash.org/aliquamomnisomnis.bmp?size=50x50&set=set1"}, 109 | {"id":109,"firstName":"Keith","lastName":"Perez","email":"kperez30@digg.com","avatar":"https://robohash.org/etlaborumnisi.jpg?size=50x50&set=set1"}, 110 | {"id":110,"firstName":"Judith","lastName":"Hart","email":"jhart31@globo.com","avatar":"https://robohash.org/fugiatvitaequam.png?size=50x50&set=set1"}, 111 | {"id":111,"firstName":"Harold","lastName":"George","email":"hgeorge32@xrea.com","avatar":"https://robohash.org/providentquianimi.jpg?size=50x50&set=set1"}, 112 | {"id":112,"firstName":"Martin","lastName":"Knight","email":"mknight33@dagondesign.com","avatar":"https://robohash.org/voluptateprovidentquos.bmp?size=50x50&set=set1"}, 113 | {"id":113,"firstName":"James","lastName":"Martinez","email":"jmartinez34@shop-pro.jp","avatar":"https://robohash.org/saeperepellendusbeatae.jpg?size=50x50&set=set1"}, 114 | {"id":114,"firstName":"Evelyn","lastName":"Riley","email":"eriley35@hud.gov","avatar":"https://robohash.org/oditquaeratet.png?size=50x50&set=set1"}, 115 | {"id":115,"firstName":"William","lastName":"White","email":"wwhite36@independent.co.uk","avatar":"https://robohash.org/etlaboriosamea.bmp?size=50x50&set=set1"}, 116 | {"id":116,"firstName":"Anne","lastName":"Brooks","email":"abrooks37@rediff.com","avatar":"https://robohash.org/esseofficiaest.bmp?size=50x50&set=set1"}, 117 | {"id":117,"firstName":"Kathy","lastName":"Roberts","email":"kroberts38@alibaba.com","avatar":"https://robohash.org/saepeexpeditamolestiae.jpg?size=50x50&set=set1"}, 118 | {"id":118,"firstName":"Robin","lastName":"Garrett","email":"rgarrett39@europa.eu","avatar":"https://robohash.org/sapienteetofficiis.png?size=50x50&set=set1"}, 119 | {"id":119,"firstName":"Lois","lastName":"Bell","email":"lbell3a@ucoz.ru","avatar":"https://robohash.org/solutaquisdolore.png?size=50x50&set=set1"}, 120 | {"id":120,"firstName":"Robin","lastName":"Willis","email":"rwillis3b@arstechnica.com","avatar":"https://robohash.org/abvoluptasaut.jpg?size=50x50&set=set1"}, 121 | {"id":121,"firstName":"Juan","lastName":"Smith","email":"jsmith3c@cbsnews.com","avatar":"https://robohash.org/veletet.bmp?size=50x50&set=set1"}, 122 | {"id":122,"firstName":"Mark","lastName":"Greene","email":"mgreene3d@intel.com","avatar":"https://robohash.org/nihilmodiaut.jpg?size=50x50&set=set1"}, 123 | {"id":123,"firstName":"Janet","lastName":"Welch","email":"jwelch3e@alibaba.com","avatar":"https://robohash.org/undequamdolores.jpg?size=50x50&set=set1"}, 124 | {"id":124,"firstName":"Jean","lastName":"Ford","email":"jford3f@mashable.com","avatar":"https://robohash.org/ideta.bmp?size=50x50&set=set1"}, 125 | {"id":125,"firstName":"Jessica","lastName":"Fisher","email":"jfisher3g@flickr.com","avatar":"https://robohash.org/etdelectusnecessitatibus.png?size=50x50&set=set1"}, 126 | {"id":126,"firstName":"Elizabeth","lastName":"Daniels","email":"edaniels3h@pcworld.com","avatar":"https://robohash.org/exdoloremrerum.png?size=50x50&set=set1"}, 127 | {"id":127,"firstName":"Terry","lastName":"Cook","email":"tcook3i@tiny.cc","avatar":"https://robohash.org/ullamistemagni.bmp?size=50x50&set=set1"}, 128 | {"id":128,"firstName":"Frances","lastName":"Hart","email":"fhart3j@jugem.jp","avatar":"https://robohash.org/voluptasquodrepellendus.png?size=50x50&set=set1"}, 129 | {"id":129,"firstName":"Jesse","lastName":"Johnson","email":"jjohnson3k@fotki.com","avatar":"https://robohash.org/oditoptioculpa.bmp?size=50x50&set=set1"}, 130 | {"id":130,"firstName":"Michael","lastName":"Stevens","email":"mstevens3l@sfgate.com","avatar":"https://robohash.org/veroadiste.bmp?size=50x50&set=set1"}, 131 | {"id":131,"firstName":"Angela","lastName":"Bowman","email":"abowman3m@indiegogo.com","avatar":"https://robohash.org/etinaut.bmp?size=50x50&set=set1"}, 132 | {"id":132,"firstName":"Gerald","lastName":"Ward","email":"gward3n@plala.or.jp","avatar":"https://robohash.org/etvoluptatibusnostrum.jpg?size=50x50&set=set1"}, 133 | {"id":133,"firstName":"Jack","lastName":"Russell","email":"jrussell3o@bloglovin.com","avatar":"https://robohash.org/autvitaeillum.jpg?size=50x50&set=set1"}, 134 | {"id":134,"firstName":"Pamela","lastName":"Stevens","email":"pstevens3p@si.edu","avatar":"https://robohash.org/veroutdebitis.bmp?size=50x50&set=set1"}, 135 | {"id":135,"firstName":"Craig","lastName":"Holmes","email":"cholmes3q@earthlink.net","avatar":"https://robohash.org/quisednecessitatibus.bmp?size=50x50&set=set1"}, 136 | {"id":136,"firstName":"Victor","lastName":"Hill","email":"vhill3r@yellowbook.com","avatar":"https://robohash.org/accusantiumquiomnis.jpg?size=50x50&set=set1"}, 137 | {"id":137,"firstName":"Jose","lastName":"Sullivan","email":"jsullivan3s@dropbox.com","avatar":"https://robohash.org/blanditiisducimusvoluptas.jpg?size=50x50&set=set1"}, 138 | {"id":138,"firstName":"Susan","lastName":"Coleman","email":"scoleman3t@liveinternet.ru","avatar":"https://robohash.org/mollitiavoluptasvoluptate.png?size=50x50&set=set1"}, 139 | {"id":139,"firstName":"Gloria","lastName":"Foster","email":"gfoster3u@wordpress.org","avatar":"https://robohash.org/quammodiquisquam.png?size=50x50&set=set1"}, 140 | {"id":140,"firstName":"Anne","lastName":"Welch","email":"awelch3v@hostgator.com","avatar":"https://robohash.org/quisequivoluptas.png?size=50x50&set=set1"}, 141 | {"id":141,"firstName":"Louise","lastName":"Peterson","email":"lpeterson3w@house.gov","avatar":"https://robohash.org/delenitisedharum.png?size=50x50&set=set1"}, 142 | {"id":142,"firstName":"Nancy","lastName":"Coleman","email":"ncoleman3x@nytimes.com","avatar":"https://robohash.org/eligendivoluptasrepellat.png?size=50x50&set=set1"}, 143 | {"id":143,"firstName":"Howard","lastName":"Torres","email":"htorres3y@i2i.jp","avatar":"https://robohash.org/accusantiummaximererum.jpg?size=50x50&set=set1"}, 144 | {"id":144,"firstName":"Linda","lastName":"Patterson","email":"lpatterson3z@uol.com.br","avatar":"https://robohash.org/consequaturveritatistempore.bmp?size=50x50&set=set1"}, 145 | {"id":145,"firstName":"Julie","lastName":"Gilbert","email":"jgilbert40@ask.com","avatar":"https://robohash.org/quiaquiminus.bmp?size=50x50&set=set1"}, 146 | {"id":146,"firstName":"Harold","lastName":"Alvarez","email":"halvarez41@baidu.com","avatar":"https://robohash.org/quisconsequaturquis.bmp?size=50x50&set=set1"}, 147 | {"id":147,"firstName":"Walter","lastName":"Little","email":"wlittle42@i2i.jp","avatar":"https://robohash.org/dolorumutnumquam.png?size=50x50&set=set1"}, 148 | {"id":148,"firstName":"Mildred","lastName":"Matthews","email":"mmatthews43@example.com","avatar":"https://robohash.org/undequasrepellat.png?size=50x50&set=set1"}, 149 | {"id":149,"firstName":"Pamela","lastName":"Wheeler","email":"pwheeler44@microsoft.com","avatar":"https://robohash.org/itaqueestnisi.bmp?size=50x50&set=set1"}, 150 | {"id":150,"firstName":"Adam","lastName":"Gilbert","email":"agilbert45@shop-pro.jp","avatar":"https://robohash.org/utettotam.png?size=50x50&set=set1"}, 151 | {"id":151,"firstName":"Raymond","lastName":"Roberts","email":"rroberts46@shareasale.com","avatar":"https://robohash.org/autexblanditiis.bmp?size=50x50&set=set1"}, 152 | {"id":152,"firstName":"Lori","lastName":"Morales","email":"lmorales47@biblegateway.com","avatar":"https://robohash.org/doloresatet.jpg?size=50x50&set=set1"}, 153 | {"id":153,"firstName":"Louise","lastName":"Hall","email":"lhall48@fotki.com","avatar":"https://robohash.org/auteumodit.jpg?size=50x50&set=set1"}, 154 | {"id":154,"firstName":"Joyce","lastName":"Fuller","email":"jfuller49@prnewswire.com","avatar":"https://robohash.org/faceretemporaeum.png?size=50x50&set=set1"}, 155 | {"id":155,"firstName":"Martin","lastName":"Torres","email":"mtorres4a@geocities.com","avatar":"https://robohash.org/quoblanditiisautem.bmp?size=50x50&set=set1"}, 156 | {"id":156,"firstName":"Marie","lastName":"Duncan","email":"mduncan4b@ox.ac.uk","avatar":"https://robohash.org/quisquamsuntarchitecto.jpg?size=50x50&set=set1"}, 157 | {"id":157,"firstName":"Joseph","lastName":"Hill","email":"jhill4c@comsenz.com","avatar":"https://robohash.org/repellendussuntin.png?size=50x50&set=set1"}, 158 | {"id":158,"firstName":"Clarence","lastName":"Chavez","email":"cchavez4d@bbb.org","avatar":"https://robohash.org/veniammodivitae.png?size=50x50&set=set1"}, 159 | {"id":159,"firstName":"Linda","lastName":"Wheeler","email":"lwheeler4e@themeforest.net","avatar":"https://robohash.org/nobissimiliquenulla.png?size=50x50&set=set1"}, 160 | {"id":160,"firstName":"Fred","lastName":"Cole","email":"fcole4f@hud.gov","avatar":"https://robohash.org/quiaquiblanditiis.jpg?size=50x50&set=set1"}, 161 | {"id":161,"firstName":"Joan","lastName":"Henderson","email":"jhenderson4g@pcworld.com","avatar":"https://robohash.org/corporispraesentiumdolores.png?size=50x50&set=set1"}, 162 | {"id":162,"firstName":"Kathy","lastName":"Gordon","email":"kgordon4h@state.gov","avatar":"https://robohash.org/nihiloditqui.jpg?size=50x50&set=set1"}, 163 | {"id":163,"firstName":"Frank","lastName":"Crawford","email":"fcrawford4i@dion.ne.jp","avatar":"https://robohash.org/veloptioplaceat.jpg?size=50x50&set=set1"}, 164 | {"id":164,"firstName":"Kevin","lastName":"Freeman","email":"kfreeman4j@marketwatch.com","avatar":"https://robohash.org/quaseaquetemporibus.bmp?size=50x50&set=set1"}, 165 | {"id":165,"firstName":"Betty","lastName":"Fields","email":"bfields4k@nationalgeographic.com","avatar":"https://robohash.org/undeaperiamblanditiis.png?size=50x50&set=set1"}, 166 | {"id":166,"firstName":"Johnny","lastName":"Alvarez","email":"jalvarez4l@yale.edu","avatar":"https://robohash.org/estutnesciunt.jpg?size=50x50&set=set1"}, 167 | {"id":167,"firstName":"Timothy","lastName":"Roberts","email":"troberts4m@bandcamp.com","avatar":"https://robohash.org/necessitatibusnondolores.jpg?size=50x50&set=set1"}, 168 | {"id":168,"firstName":"Martin","lastName":"Hughes","email":"mhughes4n@huffingtonpost.com","avatar":"https://robohash.org/sintexpeditaquos.png?size=50x50&set=set1"}, 169 | {"id":169,"firstName":"Julia","lastName":"Gibson","email":"jgibson4o@e-recht24.de","avatar":"https://robohash.org/quasettempore.jpg?size=50x50&set=set1"}, 170 | {"id":170,"firstName":"Jessica","lastName":"Harris","email":"jharris4p@bigcartel.com","avatar":"https://robohash.org/quisimiliquearchitecto.bmp?size=50x50&set=set1"}, 171 | {"id":171,"firstName":"Samuel","lastName":"George","email":"sgeorge4q@cmu.edu","avatar":"https://robohash.org/utipsavoluptatem.bmp?size=50x50&set=set1"}, 172 | {"id":172,"firstName":"Todd","lastName":"Elliott","email":"telliott4r@ow.ly","avatar":"https://robohash.org/placeatnisia.bmp?size=50x50&set=set1"}, 173 | {"id":173,"firstName":"Albert","lastName":"Romero","email":"aromero4s@behance.net","avatar":"https://robohash.org/corruptiutlaboriosam.png?size=50x50&set=set1"}, 174 | {"id":174,"firstName":"Heather","lastName":"Palmer","email":"hpalmer4t@xing.com","avatar":"https://robohash.org/teneturmaioresaliquam.jpg?size=50x50&set=set1"}, 175 | {"id":175,"firstName":"Joseph","lastName":"Fox","email":"jfox4u@japanpost.jp","avatar":"https://robohash.org/impeditiurevel.bmp?size=50x50&set=set1"}, 176 | {"id":176,"firstName":"Angela","lastName":"Robertson","email":"arobertson4v@artisteer.com","avatar":"https://robohash.org/repellatoditquasi.bmp?size=50x50&set=set1"}, 177 | {"id":177,"firstName":"Lisa","lastName":"Chapman","email":"lchapman4w@gravatar.com","avatar":"https://robohash.org/natusnobismagni.bmp?size=50x50&set=set1"}, 178 | {"id":178,"firstName":"Kathy","lastName":"Myers","email":"kmyers4x@geocities.com","avatar":"https://robohash.org/etdolorenam.png?size=50x50&set=set1"}, 179 | {"id":179,"firstName":"Adam","lastName":"Lee","email":"alee4y@cdbaby.com","avatar":"https://robohash.org/nonblanditiisid.jpg?size=50x50&set=set1"}, 180 | {"id":180,"firstName":"Charles","lastName":"Daniels","email":"cdaniels4z@craigslist.org","avatar":"https://robohash.org/quimollitiadoloribus.png?size=50x50&set=set1"}, 181 | {"id":181,"firstName":"Edward","lastName":"Bennett","email":"ebennett50@delicious.com","avatar":"https://robohash.org/maioresquiamodi.png?size=50x50&set=set1"}, 182 | {"id":182,"firstName":"Wanda","lastName":"Carr","email":"wcarr51@shutterfly.com","avatar":"https://robohash.org/aspernaturrepellendusodio.jpg?size=50x50&set=set1"}, 183 | {"id":183,"firstName":"John","lastName":"Morris","email":"jmorris52@home.pl","avatar":"https://robohash.org/reprehenderitpraesentiumomnis.png?size=50x50&set=set1"}, 184 | {"id":184,"firstName":"Paul","lastName":"Anderson","email":"panderson53@diigo.com","avatar":"https://robohash.org/earepellatet.png?size=50x50&set=set1"}, 185 | {"id":185,"firstName":"Aaron","lastName":"Ryan","email":"aryan54@boston.com","avatar":"https://robohash.org/quietsuscipit.bmp?size=50x50&set=set1"}, 186 | {"id":186,"firstName":"Anthony","lastName":"Perry","email":"aperry55@google.fr","avatar":"https://robohash.org/aspernaturutnumquam.bmp?size=50x50&set=set1"}, 187 | {"id":187,"firstName":"Frances","lastName":"Simmons","email":"fsimmons56@economist.com","avatar":"https://robohash.org/illuminciduntdolores.bmp?size=50x50&set=set1"}, 188 | {"id":188,"firstName":"Jesse","lastName":"Sullivan","email":"jsullivan57@last.fm","avatar":"https://robohash.org/quisednostrum.png?size=50x50&set=set1"}, 189 | {"id":189,"firstName":"Joyce","lastName":"Wheeler","email":"jwheeler58@usa.gov","avatar":"https://robohash.org/doloretet.bmp?size=50x50&set=set1"}, 190 | {"id":190,"firstName":"Mildred","lastName":"Young","email":"myoung59@hubpages.com","avatar":"https://robohash.org/minimaarchitectoporro.bmp?size=50x50&set=set1"}, 191 | {"id":191,"firstName":"Susan","lastName":"Fields","email":"sfields5a@github.com","avatar":"https://robohash.org/autullameos.png?size=50x50&set=set1"}, 192 | {"id":192,"firstName":"John","lastName":"Daniels","email":"jdaniels5b@mapquest.com","avatar":"https://robohash.org/estnihilquos.jpg?size=50x50&set=set1"}, 193 | {"id":193,"firstName":"Eric","lastName":"Garza","email":"egarza5c@macromedia.com","avatar":"https://robohash.org/sitipsumeum.png?size=50x50&set=set1"}, 194 | {"id":194,"firstName":"Catherine","lastName":"Wilson","email":"cwilson5d@nydailynews.com","avatar":"https://robohash.org/aliasiustofacere.bmp?size=50x50&set=set1"}, 195 | {"id":195,"firstName":"Teresa","lastName":"Garza","email":"tgarza5e@hexun.com","avatar":"https://robohash.org/fugamodinon.bmp?size=50x50&set=set1"}, 196 | {"id":196,"firstName":"Nancy","lastName":"Foster","email":"nfoster5f@pcworld.com","avatar":"https://robohash.org/estnumquamaut.jpg?size=50x50&set=set1"}, 197 | {"id":197,"firstName":"Lois","lastName":"Wright","email":"lwright5g@princeton.edu","avatar":"https://robohash.org/sedvoluptatumratione.png?size=50x50&set=set1"}, 198 | {"id":198,"firstName":"Sandra","lastName":"Parker","email":"sparker5h@toplist.cz","avatar":"https://robohash.org/atqueeumomnis.bmp?size=50x50&set=set1"}, 199 | {"id":199,"firstName":"Patrick","lastName":"Sullivan","email":"psullivan5i@weather.com","avatar":"https://robohash.org/quaeratvoluptatumtotam.png?size=50x50&set=set1"}, 200 | {"id":200,"firstName":"Jeremy","lastName":"Lopez","email":"jlopez5j@issuu.com","avatar":"https://robohash.org/adipiscifugiatquam.jpg?size=50x50&set=set1"}, 201 | {"id":201,"firstName":"Brenda","lastName":"Lynch","email":"blynch5k@alexa.com","avatar":"https://robohash.org/placeatnisiodit.png?size=50x50&set=set1"}, 202 | {"id":202,"firstName":"Donald","lastName":"Peterson","email":"dpeterson5l@ted.com","avatar":"https://robohash.org/etevenietvelit.jpg?size=50x50&set=set1"}, 203 | {"id":203,"firstName":"Billy","lastName":"Fields","email":"bfields5m@shareasale.com","avatar":"https://robohash.org/quivitaesed.png?size=50x50&set=set1"}, 204 | {"id":204,"firstName":"Sandra","lastName":"Oliver","email":"soliver5n@so-net.ne.jp","avatar":"https://robohash.org/quiaexfacilis.bmp?size=50x50&set=set1"}, 205 | {"id":205,"firstName":"Edward","lastName":"Rodriguez","email":"erodriguez5o@icq.com","avatar":"https://robohash.org/dolorumharumquaerat.bmp?size=50x50&set=set1"}, 206 | {"id":206,"firstName":"Randy","lastName":"Rose","email":"rrose5p@springer.com","avatar":"https://robohash.org/sitculpaquia.jpg?size=50x50&set=set1"}, 207 | {"id":207,"firstName":"Norma","lastName":"James","email":"njames5q@yahoo.com","avatar":"https://robohash.org/omnisdoloret.bmp?size=50x50&set=set1"}, 208 | {"id":208,"firstName":"Pamela","lastName":"Reed","email":"preed5r@oaic.gov.au","avatar":"https://robohash.org/quaesitmollitia.bmp?size=50x50&set=set1"}, 209 | {"id":209,"firstName":"Ruth","lastName":"Woods","email":"rwoods5s@ovh.net","avatar":"https://robohash.org/laboriosamveritatisassumenda.png?size=50x50&set=set1"}, 210 | {"id":210,"firstName":"Harry","lastName":"Turner","email":"hturner5t@is.gd","avatar":"https://robohash.org/quisomnisatque.jpg?size=50x50&set=set1"}, 211 | {"id":211,"firstName":"Richard","lastName":"Black","email":"rblack5u@newyorker.com","avatar":"https://robohash.org/doloremqueeiuscumque.jpg?size=50x50&set=set1"}, 212 | {"id":212,"firstName":"Anna","lastName":"Jenkins","email":"ajenkins5v@wordpress.org","avatar":"https://robohash.org/quiisteaspernatur.bmp?size=50x50&set=set1"}, 213 | {"id":213,"firstName":"Sara","lastName":"Gomez","email":"sgomez5w@opensource.org","avatar":"https://robohash.org/estaperiamvel.jpg?size=50x50&set=set1"}, 214 | {"id":214,"firstName":"Heather","lastName":"Simpson","email":"hsimpson5x@istockphoto.com","avatar":"https://robohash.org/nihilquiplaceat.jpg?size=50x50&set=set1"}, 215 | {"id":215,"firstName":"Joseph","lastName":"Graham","email":"jgraham5y@ezinearticles.com","avatar":"https://robohash.org/quossimiliquenam.jpg?size=50x50&set=set1"}, 216 | {"id":216,"firstName":"Wanda","lastName":"Andrews","email":"wandrews5z@yahoo.com","avatar":"https://robohash.org/illodoloresnam.bmp?size=50x50&set=set1"}, 217 | {"id":217,"firstName":"Roger","lastName":"Lynch","email":"rlynch60@t-online.de","avatar":"https://robohash.org/voluptatumblanditiisest.png?size=50x50&set=set1"}, 218 | {"id":218,"firstName":"Lisa","lastName":"Alvarez","email":"lalvarez61@miitbeian.gov.cn","avatar":"https://robohash.org/evenietsitconsequatur.bmp?size=50x50&set=set1"}, 219 | {"id":219,"firstName":"Kevin","lastName":"Black","email":"kblack62@seattletimes.com","avatar":"https://robohash.org/quaeratestcorporis.jpg?size=50x50&set=set1"}, 220 | {"id":220,"firstName":"Catherine","lastName":"Nguyen","email":"cnguyen63@squidoo.com","avatar":"https://robohash.org/velitnemoet.bmp?size=50x50&set=set1"}, 221 | {"id":221,"firstName":"Roger","lastName":"Sullivan","email":"rsullivan64@sphinn.com","avatar":"https://robohash.org/aspernaturetnatus.png?size=50x50&set=set1"}, 222 | {"id":222,"firstName":"Roger","lastName":"Hansen","email":"rhansen65@latimes.com","avatar":"https://robohash.org/dolortenetursit.jpg?size=50x50&set=set1"}, 223 | {"id":223,"firstName":"Jennifer","lastName":"Scott","email":"jscott66@patch.com","avatar":"https://robohash.org/esttotamvoluptatum.png?size=50x50&set=set1"}, 224 | {"id":224,"firstName":"Charles","lastName":"Dean","email":"cdean67@salon.com","avatar":"https://robohash.org/omnisveritatismollitia.png?size=50x50&set=set1"}, 225 | {"id":225,"firstName":"Patrick","lastName":"Taylor","email":"ptaylor68@vk.com","avatar":"https://robohash.org/fugiatcommodinihil.png?size=50x50&set=set1"}, 226 | {"id":226,"firstName":"Mildred","lastName":"Cooper","email":"mcooper69@zimbio.com","avatar":"https://robohash.org/excepturiautmolestiae.bmp?size=50x50&set=set1"}, 227 | {"id":227,"firstName":"Barbara","lastName":"Moreno","email":"bmoreno6a@bravesites.com","avatar":"https://robohash.org/nonarchitectoquibusdam.png?size=50x50&set=set1"}, 228 | {"id":228,"firstName":"Andrew","lastName":"Scott","email":"ascott6b@epa.gov","avatar":"https://robohash.org/voluptatumeiusin.png?size=50x50&set=set1"}, 229 | {"id":229,"firstName":"Sharon","lastName":"Carter","email":"scarter6c@vistaprint.com","avatar":"https://robohash.org/nostrumutdicta.jpg?size=50x50&set=set1"}, 230 | {"id":230,"firstName":"Willie","lastName":"Baker","email":"wbaker6d@chicagotribune.com","avatar":"https://robohash.org/cumquesaepeofficiis.jpg?size=50x50&set=set1"}, 231 | {"id":231,"firstName":"Kathryn","lastName":"Meyer","email":"kmeyer6e@toplist.cz","avatar":"https://robohash.org/odioquistemporibus.bmp?size=50x50&set=set1"}, 232 | {"id":232,"firstName":"Nicole","lastName":"Nguyen","email":"nnguyen6f@pcworld.com","avatar":"https://robohash.org/similiqueofficianeque.bmp?size=50x50&set=set1"}, 233 | {"id":233,"firstName":"Sarah","lastName":"Stephens","email":"sstephens6g@uiuc.edu","avatar":"https://robohash.org/doloressedexpedita.bmp?size=50x50&set=set1"}, 234 | {"id":234,"firstName":"Douglas","lastName":"Mccoy","email":"dmccoy6h@reuters.com","avatar":"https://robohash.org/etinreprehenderit.bmp?size=50x50&set=set1"}, 235 | {"id":235,"firstName":"Deborah","lastName":"Hall","email":"dhall6i@nydailynews.com","avatar":"https://robohash.org/enimiurequis.jpg?size=50x50&set=set1"}, 236 | {"id":236,"firstName":"Joyce","lastName":"Robertson","email":"jrobertson6j@indiatimes.com","avatar":"https://robohash.org/odioquamunde.png?size=50x50&set=set1"}, 237 | {"id":237,"firstName":"Ashley","lastName":"Price","email":"aprice6k@ihg.com","avatar":"https://robohash.org/doloremrerumalias.jpg?size=50x50&set=set1"}, 238 | {"id":238,"firstName":"Gary","lastName":"Garrett","email":"ggarrett6l@over-blog.com","avatar":"https://robohash.org/totamvelinventore.bmp?size=50x50&set=set1"}, 239 | {"id":239,"firstName":"Sandra","lastName":"Harrison","email":"sharrison6m@weather.com","avatar":"https://robohash.org/occaecatiinventoreconsequatur.bmp?size=50x50&set=set1"}, 240 | {"id":240,"firstName":"Mark","lastName":"Gilbert","email":"mgilbert6n@hc360.com","avatar":"https://robohash.org/mollitiavoluptasvero.jpg?size=50x50&set=set1"}, 241 | {"id":241,"firstName":"Gregory","lastName":"Carpenter","email":"gcarpenter6o@feedburner.com","avatar":"https://robohash.org/consecteturtemporenihil.png?size=50x50&set=set1"}, 242 | {"id":242,"firstName":"Henry","lastName":"Webb","email":"hwebb6p@army.mil","avatar":"https://robohash.org/assumendaliberoaliquam.png?size=50x50&set=set1"}, 243 | {"id":243,"firstName":"Gary","lastName":"Nelson","email":"gnelson6q@chicagotribune.com","avatar":"https://robohash.org/etsitassumenda.bmp?size=50x50&set=set1"}, 244 | {"id":244,"firstName":"Jason","lastName":"Lawrence","email":"jlawrence6r@ifeng.com","avatar":"https://robohash.org/enimsaepeincidunt.png?size=50x50&set=set1"}, 245 | {"id":245,"firstName":"Alice","lastName":"Jones","email":"ajones6s@nbcnews.com","avatar":"https://robohash.org/etmolestiasharum.png?size=50x50&set=set1"}, 246 | {"id":246,"firstName":"Carol","lastName":"Stanley","email":"cstanley6t@ifeng.com","avatar":"https://robohash.org/omnisdolorveritatis.png?size=50x50&set=set1"}, 247 | {"id":247,"firstName":"Harry","lastName":"Bennett","email":"hbennett6u@eepurl.com","avatar":"https://robohash.org/minimaporroet.bmp?size=50x50&set=set1"}, 248 | {"id":248,"firstName":"Gary","lastName":"Bailey","email":"gbailey6v@about.me","avatar":"https://robohash.org/minimanemoaccusantium.png?size=50x50&set=set1"}, 249 | {"id":249,"firstName":"James","lastName":"Rose","email":"jrose6w@gizmodo.com","avatar":"https://robohash.org/consequunturincidunteligendi.bmp?size=50x50&set=set1"}, 250 | {"id":250,"firstName":"Philip","lastName":"Webb","email":"pwebb6x@myspace.com","avatar":"https://robohash.org/laborumconsequatursapiente.jpg?size=50x50&set=set1"}, 251 | {"id":251,"firstName":"Joan","lastName":"Carter","email":"jcarter6y@apache.org","avatar":"https://robohash.org/inutiste.png?size=50x50&set=set1"}, 252 | {"id":252,"firstName":"Deborah","lastName":"Moore","email":"dmoore6z@google.fr","avatar":"https://robohash.org/minusutquidem.bmp?size=50x50&set=set1"}, 253 | {"id":253,"firstName":"Patrick","lastName":"Price","email":"pprice70@stumbleupon.com","avatar":"https://robohash.org/quisquamrecusandaedolores.jpg?size=50x50&set=set1"}, 254 | {"id":254,"firstName":"Annie","lastName":"Rivera","email":"arivera71@whitehouse.gov","avatar":"https://robohash.org/distinctiomodirepellendus.png?size=50x50&set=set1"}, 255 | {"id":255,"firstName":"Ralph","lastName":"Morales","email":"rmorales72@bloglines.com","avatar":"https://robohash.org/fugaautoptio.jpg?size=50x50&set=set1"}, 256 | {"id":256,"firstName":"Adam","lastName":"Griffin","email":"agriffin73@apple.com","avatar":"https://robohash.org/illumdoloremiure.jpg?size=50x50&set=set1"}, 257 | {"id":257,"firstName":"Johnny","lastName":"Rogers","email":"jrogers74@time.com","avatar":"https://robohash.org/omnishicvoluptates.bmp?size=50x50&set=set1"}, 258 | {"id":258,"firstName":"Linda","lastName":"Flores","email":"lflores75@dailymail.co.uk","avatar":"https://robohash.org/dolorvelqui.jpg?size=50x50&set=set1"}, 259 | {"id":259,"firstName":"Frances","lastName":"Gilbert","email":"fgilbert76@free.fr","avatar":"https://robohash.org/nobisutunde.bmp?size=50x50&set=set1"}, 260 | {"id":260,"firstName":"Virginia","lastName":"Kim","email":"vkim77@google.it","avatar":"https://robohash.org/sedomnisenim.png?size=50x50&set=set1"}, 261 | {"id":261,"firstName":"John","lastName":"Ruiz","email":"jruiz78@plala.or.jp","avatar":"https://robohash.org/rerumenimanimi.png?size=50x50&set=set1"}, 262 | {"id":262,"firstName":"Judith","lastName":"Jones","email":"jjones79@seattletimes.com","avatar":"https://robohash.org/ducimuscommodiaspernatur.png?size=50x50&set=set1"}, 263 | {"id":263,"firstName":"Sara","lastName":"Gilbert","email":"sgilbert7a@mac.com","avatar":"https://robohash.org/vitaequiaratione.bmp?size=50x50&set=set1"}, 264 | {"id":264,"firstName":"Timothy","lastName":"Wells","email":"twells7b@disqus.com","avatar":"https://robohash.org/solutaexplicaboet.bmp?size=50x50&set=set1"}, 265 | {"id":265,"firstName":"Richard","lastName":"Armstrong","email":"rarmstrong7c@columbia.edu","avatar":"https://robohash.org/repellendusmollitiaodio.jpg?size=50x50&set=set1"}, 266 | {"id":266,"firstName":"Brenda","lastName":"Lewis","email":"blewis7d@amazon.de","avatar":"https://robohash.org/molestiaeetvitae.jpg?size=50x50&set=set1"}, 267 | {"id":267,"firstName":"George","lastName":"Adams","email":"gadams7e@qq.com","avatar":"https://robohash.org/exidcumque.jpg?size=50x50&set=set1"}, 268 | {"id":268,"firstName":"Alan","lastName":"Butler","email":"abutler7f@1688.com","avatar":"https://robohash.org/quisnobisprovident.png?size=50x50&set=set1"}, 269 | {"id":269,"firstName":"Michelle","lastName":"Riley","email":"mriley7g@sina.com.cn","avatar":"https://robohash.org/assumendarationevelit.jpg?size=50x50&set=set1"}, 270 | {"id":270,"firstName":"Sara","lastName":"Olson","email":"solson7h@liveinternet.ru","avatar":"https://robohash.org/sitquoaperiam.bmp?size=50x50&set=set1"}, 271 | {"id":271,"firstName":"Katherine","lastName":"Gibson","email":"kgibson7i@e-recht24.de","avatar":"https://robohash.org/numquamfugitillo.bmp?size=50x50&set=set1"}, 272 | {"id":272,"firstName":"Janice","lastName":"Diaz","email":"jdiaz7j@myspace.com","avatar":"https://robohash.org/utoditquia.bmp?size=50x50&set=set1"}, 273 | {"id":273,"firstName":"Anna","lastName":"Carter","email":"acarter7k@admin.ch","avatar":"https://robohash.org/excepturinecessitatibusiusto.bmp?size=50x50&set=set1"}, 274 | {"id":274,"firstName":"Dorothy","lastName":"Mitchell","email":"dmitchell7l@printfriendly.com","avatar":"https://robohash.org/omnisliberout.bmp?size=50x50&set=set1"}, 275 | {"id":275,"firstName":"Amy","lastName":"Mccoy","email":"amccoy7m@nhs.uk","avatar":"https://robohash.org/veritatisoccaecatiincidunt.png?size=50x50&set=set1"}, 276 | {"id":276,"firstName":"Marie","lastName":"Crawford","email":"mcrawford7n@nytimes.com","avatar":"https://robohash.org/velitutaut.bmp?size=50x50&set=set1"}, 277 | {"id":277,"firstName":"Henry","lastName":"Perkins","email":"hperkins7o@163.com","avatar":"https://robohash.org/officiisdoloret.png?size=50x50&set=set1"}, 278 | {"id":278,"firstName":"Juan","lastName":"Owens","email":"jowens7p@google.co.uk","avatar":"https://robohash.org/quisquiiusto.jpg?size=50x50&set=set1"}, 279 | {"id":279,"firstName":"Marie","lastName":"Dean","email":"mdean7q@sphinn.com","avatar":"https://robohash.org/laborumsinteos.png?size=50x50&set=set1"}, 280 | {"id":280,"firstName":"James","lastName":"Wilson","email":"jwilson7r@webmd.com","avatar":"https://robohash.org/aspernaturillumaut.bmp?size=50x50&set=set1"}, 281 | {"id":281,"firstName":"Anne","lastName":"Baker","email":"abaker7s@hatena.ne.jp","avatar":"https://robohash.org/suntetut.png?size=50x50&set=set1"}, 282 | {"id":282,"firstName":"Kelly","lastName":"Mason","email":"kmason7t@nasa.gov","avatar":"https://robohash.org/quamaperiamut.jpg?size=50x50&set=set1"}, 283 | {"id":283,"firstName":"Ryan","lastName":"Carr","email":"rcarr7u@samsung.com","avatar":"https://robohash.org/quiscommodiculpa.png?size=50x50&set=set1"}, 284 | {"id":284,"firstName":"Bonnie","lastName":"Bennett","email":"bbennett7v@virginia.edu","avatar":"https://robohash.org/itaquequisut.png?size=50x50&set=set1"}, 285 | {"id":285,"firstName":"Alan","lastName":"Ramos","email":"aramos7w@hp.com","avatar":"https://robohash.org/ipsaautad.png?size=50x50&set=set1"}, 286 | {"id":286,"firstName":"Jack","lastName":"Bryant","email":"jbryant7x@cnbc.com","avatar":"https://robohash.org/doloresdolorcupiditate.bmp?size=50x50&set=set1"}, 287 | {"id":287,"firstName":"Elizabeth","lastName":"Miller","email":"emiller7y@cnbc.com","avatar":"https://robohash.org/minimaautlabore.bmp?size=50x50&set=set1"}, 288 | {"id":288,"firstName":"Chris","lastName":"Morrison","email":"cmorrison7z@slate.com","avatar":"https://robohash.org/nostrumvoluptateet.png?size=50x50&set=set1"}, 289 | {"id":289,"firstName":"Kenneth","lastName":"Kennedy","email":"kkennedy80@dion.ne.jp","avatar":"https://robohash.org/eaqueitaqueex.png?size=50x50&set=set1"}, 290 | {"id":290,"firstName":"Thomas","lastName":"Andrews","email":"tandrews81@topsy.com","avatar":"https://robohash.org/natusasperioresaut.bmp?size=50x50&set=set1"}, 291 | {"id":291,"firstName":"Anthony","lastName":"Berry","email":"aberry82@netscape.com","avatar":"https://robohash.org/porrovelillum.jpg?size=50x50&set=set1"}, 292 | {"id":292,"firstName":"Henry","lastName":"Bailey","email":"hbailey83@blogs.com","avatar":"https://robohash.org/quaeratetitaque.jpg?size=50x50&set=set1"}, 293 | {"id":293,"firstName":"Jean","lastName":"Davis","email":"jdavis84@guardian.co.uk","avatar":"https://robohash.org/quisquamquiminus.jpg?size=50x50&set=set1"}, 294 | {"id":294,"firstName":"Cynthia","lastName":"Freeman","email":"cfreeman85@yelp.com","avatar":"https://robohash.org/cupiditateminimaexplicabo.bmp?size=50x50&set=set1"}, 295 | {"id":295,"firstName":"Terry","lastName":"James","email":"tjames86@google.co.uk","avatar":"https://robohash.org/ullamquisquamipsum.png?size=50x50&set=set1"}, 296 | {"id":296,"firstName":"Samuel","lastName":"Lewis","email":"slewis87@huffingtonpost.com","avatar":"https://robohash.org/perferendisrationeaut.bmp?size=50x50&set=set1"}, 297 | {"id":297,"firstName":"Helen","lastName":"Watkins","email":"hwatkins88@vistaprint.com","avatar":"https://robohash.org/deseruntetitaque.jpg?size=50x50&set=set1"}, 298 | {"id":298,"firstName":"Roger","lastName":"Scott","email":"rscott89@jugem.jp","avatar":"https://robohash.org/veritatisinvel.png?size=50x50&set=set1"}, 299 | {"id":299,"firstName":"Kenneth","lastName":"Cruz","email":"kcruz8a@rambler.ru","avatar":"https://robohash.org/quisquamquiaqui.jpg?size=50x50&set=set1"}, 300 | {"id":300,"firstName":"Donald","lastName":"Dean","email":"ddean8b@i2i.jp","avatar":"https://robohash.org/eaeosvoluptatem.png?size=50x50&set=set1"} 301 | ] -------------------------------------------------------------------------------- /src/services/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "browser": { 3 | "./users.js": "./users-browser.js" 4 | } 5 | } -------------------------------------------------------------------------------- /src/services/routes.js: -------------------------------------------------------------------------------- 1 | var usersService = require('./users'); 2 | 3 | module.exports = function(app) { 4 | app.get('/services/users', function(req, res) { 5 | var pageIndex = req.query.pageIndex; 6 | if (typeof pageIndex === 'string') { 7 | pageIndex = parseInt(pageIndex, 10); 8 | } else { 9 | pageIndex = 0; 10 | } 11 | 12 | usersService.getUsers({ pageIndex: pageIndex }) 13 | .then(function(data) { 14 | res.json(data); 15 | }) 16 | .catch(function(err) { 17 | console.log(err); 18 | res.status(500).send('Unable to load users'); 19 | }); 20 | }); 21 | }; -------------------------------------------------------------------------------- /src/services/users-browser.js: -------------------------------------------------------------------------------- 1 | require('whatwg-fetch'); 2 | 3 | exports.getUsers = function(options) { 4 | return fetch('/services/users?pageIndex=' + (options.pageIndex || 0)) 5 | .then(function(response) { 6 | return response.json(); 7 | }); 8 | }; -------------------------------------------------------------------------------- /src/services/users.js: -------------------------------------------------------------------------------- 1 | var mockUsersData = require('./mock-users-data.json'); 2 | 3 | const pageSize = 10; 4 | 5 | exports.getUsers = function(options) { 6 | var pageIndex = options.pageIndex || 0; 7 | var start = pageIndex * pageSize; 8 | 9 | var users = []; 10 | 11 | for (var i=start; i { 22 | setTimeout(function() { 23 | resolve(results); 24 | }, 1000); 25 | }); 26 | }; -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const webpack = require("webpack"); 3 | const { CleanWebpackPlugin } = require("clean-webpack-plugin"); 4 | const MarkoPlugin = require("@marko/webpack/plugin").default; 5 | const CSSExtractPlugin = require("mini-css-extract-plugin"); 6 | const SpawnServerPlugin = require("spawn-server-webpack-plugin"); 7 | const OptimizeCssAssetsPlugin = require("optimize-css-assets-webpack-plugin"); 8 | 9 | const { NODE_ENV } = process.env; 10 | const isProd = NODE_ENV === "production"; 11 | const isDev = !isProd; 12 | const markoPlugin = new MarkoPlugin(); 13 | const spawnedServer = isDev && new SpawnServerPlugin(); 14 | 15 | module.exports = [ 16 | compiler({ 17 | name: "Client", 18 | optimization: { 19 | splitChunks: { 20 | chunks: "all", 21 | maxInitialRequests: 3 22 | } 23 | }, 24 | output: { 25 | filename: "[name].[contenthash:8].js", 26 | path: path.join(__dirname, "dist/client") 27 | }, 28 | devServer: isDev ? { 29 | overlay: true, 30 | stats: "minimal", 31 | contentBase: false, 32 | ...spawnedServer.devServerConfig 33 | }: undefined, 34 | plugins: [ 35 | new webpack.DefinePlugin({ 36 | "process.browser": true 37 | }), 38 | new CSSExtractPlugin({ 39 | filename: "[name].[contenthash:8].css" 40 | }), 41 | isProd && new OptimizeCssAssetsPlugin(), 42 | markoPlugin.browser 43 | ] 44 | }), 45 | compiler({ 46 | name: "Server", 47 | target: "async-node", 48 | externals: [/^[^./!]/], // excludes node_modules 49 | optimization: { 50 | minimize: false 51 | }, 52 | output: { 53 | libraryTarget: "commonjs2", 54 | path: path.join(__dirname, "dist/server") 55 | }, 56 | plugins: [ 57 | new webpack.DefinePlugin({ 58 | "process.browser": undefined, 59 | "process.env.BUNDLE": true 60 | }), 61 | new webpack.BannerPlugin({ 62 | banner: 'require("source-map-support").install();', 63 | raw: true 64 | }), 65 | new CSSExtractPlugin({ 66 | filename: "[name].[contenthash:8].css" 67 | }), 68 | isDev && spawnedServer, 69 | markoPlugin.server 70 | ] 71 | }) 72 | ]; 73 | 74 | // Shared config for both server and client compilers. 75 | function compiler(config) { 76 | return { 77 | ...config, 78 | mode: isProd ? "production" : "development", 79 | devtool: isProd ? "source-map" : "inline-source-map", 80 | output: { 81 | publicPath: "/static/", 82 | ...config.output 83 | }, 84 | resolve: { 85 | extensions: [".js", ".json", ".marko"] 86 | }, 87 | module: { 88 | rules: [ 89 | { 90 | test: /\.marko$/, 91 | loader: "@marko/webpack/loader" 92 | }, 93 | { 94 | test: /\.(less|css)$/, 95 | use: [CSSExtractPlugin.loader, "css-loader", "less-loader"] 96 | }, 97 | { 98 | test: /\.svg/, 99 | loader: "svg-url-loader" 100 | }, 101 | { 102 | test: /\.(jpg|jpeg|gif|png)$/, 103 | loader: "file-loader", 104 | options: { 105 | // File assets from server & browser compiler output to client folder. 106 | outputPath: "../client" 107 | } 108 | } 109 | ] 110 | }, 111 | plugins: [...config.plugins, isProd && new CleanWebpackPlugin()].filter(Boolean) 112 | }; 113 | } 114 | --------------------------------------------------------------------------------