├── .gitignore ├── .meteor ├── .finished-upgraders ├── .gitignore ├── .id ├── packages ├── platforms ├── release └── versions ├── client ├── main.css ├── main.html ├── main.jsx └── styles.js ├── imports └── dataTemperature.js ├── package.json ├── readme.md └── server └── dataPublications.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | packages 4 | .flooignore 5 | .floo -------------------------------------------------------------------------------- /.meteor/.finished-upgraders: -------------------------------------------------------------------------------- 1 | # This file contains information which helps Meteor properly upgrade your 2 | # app when you run 'meteor update'. You should check it into version control 3 | # with your project. 4 | 5 | notices-for-0.9.0 6 | notices-for-0.9.1 7 | 0.9.4-platform-file 8 | notices-for-facebook-graph-api-2 9 | 1.2.0-standard-minifiers-package 10 | 1.2.0-meteor-platform-split 11 | 1.2.0-cordova-changes 12 | 1.2.0-breaking-changes 13 | 1.3.0-split-minifiers-package 14 | -------------------------------------------------------------------------------- /.meteor/.gitignore: -------------------------------------------------------------------------------- 1 | local 2 | -------------------------------------------------------------------------------- /.meteor/.id: -------------------------------------------------------------------------------- 1 | # This file contains a token that is unique to your project. 2 | # Check it into your repository along with the rest of this directory. 3 | # It can be used for purposes such as: 4 | # - ensuring you don't accidentally deploy one app on top of another 5 | # - providing package authors with aggregated statistics 6 | 7 | tavwnl18iejha1alg3pk 8 | -------------------------------------------------------------------------------- /.meteor/packages: -------------------------------------------------------------------------------- 1 | # Meteor packages used by this project, one per line. 2 | # Check this file (and the other files in this directory) into your repository. 3 | # 4 | # 'meteor add' and 'meteor remove' will edit this file for you, 5 | # but you can also edit it by hand. 6 | 7 | meteor-base # Packages every Meteor app needs to have 8 | mobile-experience # Packages for a great mobile UX 9 | mongo # The database Meteor supports right now 10 | blaze-html-templates # Compile .html files into Meteor Blaze views 11 | session # Client-side reactive dictionary for your app 12 | jquery # Helpful client-side library 13 | tracker # Meteor's client-side reactive programming library 14 | 15 | es5-shim # ECMAScript 5 compatibility for older browsers. 16 | ecmascript # Enable ECMAScript2015+ syntax in app code 17 | 18 | modules 19 | dburles:mongo-collection-instances 20 | standard-minifier-css 21 | standard-minifier-js 22 | ultimatejs:tracker-react 23 | fastclick 24 | -------------------------------------------------------------------------------- /.meteor/platforms: -------------------------------------------------------------------------------- 1 | server 2 | browser 3 | -------------------------------------------------------------------------------- /.meteor/release: -------------------------------------------------------------------------------- 1 | METEOR@1.3-rc.1 2 | -------------------------------------------------------------------------------- /.meteor/versions: -------------------------------------------------------------------------------- 1 | allow-deny@1.0.2-rc.1 2 | autoupdate@1.2.6-rc.1 3 | babel-compiler@6.5.1-rc.1 4 | babel-runtime@0.1.6-rc.1 5 | base64@1.0.6-rc.1 6 | binary-heap@1.0.6-rc.1 7 | blaze@2.1.5-rc.1 8 | blaze-html-templates@1.0.2-rc.1 9 | blaze-tools@1.0.6-rc.1 10 | boilerplate-generator@1.0.6-rc.1 11 | caching-compiler@1.0.2-rc.1 12 | caching-html-compiler@1.0.4-rc.1 13 | callback-hook@1.0.6-rc.1 14 | check@1.1.2-rc.1 15 | dburles:mongo-collection-instances@0.3.5 16 | ddp@1.2.3-rc.1 17 | ddp-client@1.2.3-rc.1 18 | ddp-common@1.2.3-rc.1 19 | ddp-server@1.2.4-rc.1 20 | deps@1.0.10-rc.1 21 | diff-sequence@1.0.3-rc.1 22 | ecmascript@0.4.1-rc.1 23 | ecmascript-runtime@0.2.8-rc.1 24 | ejson@1.0.9-rc.1 25 | es5-shim@4.5.8-rc.1 26 | fastclick@1.0.9-rc.1 27 | geojson-utils@1.0.6-rc.1 28 | hot-code-push@1.0.2-rc.1 29 | html-tools@1.0.7-rc.1 30 | htmljs@1.0.7-rc.1 31 | http@1.1.3-rc.1 32 | id-map@1.0.5-rc.1 33 | jquery@1.11.6-rc.1 34 | lai:collection-extensions@0.2.1_1 35 | launch-screen@1.0.7-rc.1 36 | livedata@1.0.16-rc.1 37 | logging@1.0.10-rc.1 38 | meteor@1.1.12-rc.1 39 | meteor-base@1.0.2-rc.1 40 | minifier-css@1.1.9-rc.1 41 | minifier-js@1.1.9-rc.1 42 | minimongo@1.0.12-rc.1 43 | mobile-experience@1.0.2-rc.1 44 | mobile-status-bar@1.0.9-rc.1 45 | modules@0.5.1-rc.1 46 | modules-runtime@0.6.1-rc.1 47 | mongo@1.1.5-rc.1 48 | mongo-id@1.0.2-rc.1 49 | npm-mongo@1.4.41-rc.1 50 | observe-sequence@1.0.9-rc.1 51 | ordered-dict@1.0.5-rc.1 52 | promise@0.6.4-rc.1 53 | random@1.0.7-rc.1 54 | reactive-dict@1.1.5-rc.1 55 | reactive-var@1.0.7-rc.1 56 | reload@1.1.6-rc.1 57 | retry@1.0.5-rc.1 58 | routepolicy@1.0.8-rc.1 59 | session@1.1.3-rc.1 60 | spacebars@1.0.9-rc.1 61 | spacebars-compiler@1.0.9-rc.1 62 | standard-minifier-css@1.0.4-rc.1 63 | standard-minifier-js@1.0.4-rc.1 64 | templating@1.1.7-rc.1 65 | templating-tools@1.0.2-rc.1 66 | tracker@1.0.11-rc.1 67 | ui@1.0.9-rc.1 68 | ultimatejs:tracker-react@1.0.4 69 | underscore@1.0.6-rc.1 70 | url@1.0.7-rc.1 71 | webapp@1.2.6-rc.1 72 | webapp-hashing@1.0.7-rc.1 73 | -------------------------------------------------------------------------------- /client/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 0; 3 | margin: 0; 4 | background: #3498db; 5 | font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif !important;; 6 | font-weight: 400; 7 | font-size: 14px; 8 | } 9 | 10 | .container { 11 | padding: 40px 0 12 | } 13 | 14 | .wrapper { 15 | max-width: 600px; 16 | margin: 10px auto; 17 | display: flex 18 | } 19 | 20 | .footer { 21 | text-align: center; 22 | } 23 | 24 | .left { 25 | width: 50%; 26 | float: left; 27 | padding-right: 10px 28 | } 29 | 30 | .right { 31 | width: 50%; 32 | float: left; 33 | padding-left: 10px 34 | } 35 | 36 | .intro { 37 | display: inline-block; 38 | } 39 | 40 | div.thermometer > div > div:nth-child(1) > div { 41 | transition-duration: 1s; 42 | } 43 | 44 | @media (max-width: 650px) { 45 | body { 46 | padding: 0 20px 0 20px; 47 | } 48 | .wrapper { 49 | display: block; 50 | } 51 | .left { 52 | float: none; 53 | width: 100%; 54 | padding: 0; 55 | } 56 | .right { 57 | float: none; 58 | width: 100%; 59 | padding: 0; 60 | } 61 | } -------------------------------------------------------------------------------- /client/main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | TrackerReact 4 | 5 | 6 | 7 | 8 |
9 | -------------------------------------------------------------------------------- /client/main.jsx: -------------------------------------------------------------------------------- 1 | /* This code only runs on the CLIENT */ 2 | 3 | /*********************************************************************************************************************** 4 | * 5 | * [Set-Up] 6 | * Import latest npm modules for react and a js style file. 7 | * 8 | **********************************************************************************************************************/ 9 | import React from 'react'; 10 | import ReactDOM from 'react-dom'; 11 | import style from './styles'; 12 | 13 | 14 | /*********************************************************************************************************************** 15 | * 16 | * [Main] 17 | * This component is rendered to the DOM and encapsulates two React Components: MethodShowcase and 18 | * CollectionShowcase 19 | * 20 | **********************************************************************************************************************/ 21 | class Main extends React.Component { 22 | 23 | /* React 24 | Static methods are a good way to keep the render function clean (as opposed to variable declarations), while 25 | being able to share static html snippets across instances and other components, without camelcase pollution. 26 | */ 27 | static header(heading, claim, repo, packageInstall) { 28 | return ( 29 |
30 |

{heading}

31 |

{claim}

32 |
33 | GitHub | {packageInstall} 35 |
36 |
37 | ) 38 | } 39 | 40 | constructor() { 41 | super(); 42 | this.state = { 43 | showCollectionShowcase: true, 44 | showMethodShowcase: true 45 | }; 46 | } 47 | 48 | showMethodShowcase() { 49 | this.setState({showMethodShowcase: !this.state.showMethodShowcase}); 50 | } 51 | 52 | showCollectionShowcase() { 53 | this.setState({showCollectionShowcase: !this.state.showCollectionShowcase}); 54 | } 55 | 56 | render() { 57 | return ( 58 |
59 | {Main.header( 60 | "TrackerReact", 61 | "No-Config reactive React Components with Meteor", 62 | "https://github.com/ultimatejs/tracker-react", 63 | "meteor add ultimatejs:tracker-react")} 64 |
65 | {this.state.showMethodShowcase ? : null} 66 | {this.state.showCollectionShowcase ? : null} 67 |
68 |
69 | 72 | 75 |
76 |
77 | ) 78 | } 79 | } 80 | 81 | /* React 82 | Render into main.html, target the div with id="root". React can live next to other elements inside the main html 83 | document, i.e. legacy templates/engines. 84 | */ 85 | Meteor.startup(function () { 86 | ReactDOM.render(
, document.getElementById('root')); 87 | }); 88 | 89 | 90 | /*********************************************************************************************************************** 91 | * 92 | * [CollectionShowcase] 93 | * This component uses TrackerReact to provide real-time data from the "tasks" Meteor Collection. 94 | * Make sure collections are also instantiated on the server, published and subscribed on the client. 95 | * 96 | **********************************************************************************************************************/ 97 | 98 | /* Meteor 99 | Install TrackerReact via "meteor add ultimatejs:tracker-react" and import the default composition helper 100 | "TrackerReact" via Meteors module system (meteor/author:package-name). 101 | */ 102 | import TrackerReact from 'meteor/ultimatejs:tracker-react'; 103 | 104 | /* Meteor 105 | Initialise the "tasks" collection on the client. The same is done on the server. See 106 | "/server/dataPublications.js". 107 | */ 108 | Tasks = new Mongo.Collection("tasks"); 109 | 110 | /* Meteor, React -> TrackerReact 111 | In order to have react re-render on data invalidation and update our component, we need to compose it with 112 | TrackerReact. If we would not do so, everything would still work fine but updates are only shown on page/component 113 | re-load. -> "TrackerReact(React.Component)" 114 | 115 | Profiler: Set {profiler: false} to turn profile logs off. If not used, this second argument can be omitted. 116 | */ 117 | class CollectionShowcase extends TrackerReact(React.Component) { 118 | 119 | /* Meteor, React 120 | In this example, the Meteor data is only used inside this react component. Therefore, no active data subscription 121 | exists up on rendering this component. To make sure we have all the necessary data up on rendering, we subscribe 122 | to our data when the component is about to render. 123 | 124 | The "constructor()" method is called before rendering and therefore a good place to start our subscription. We 125 | could also subscribe outside of the react life cycle, i.e., simply at the beginning of the page. 126 | */ 127 | constructor() { 128 | super(); 129 | /* React 130 | Data subscription(s) define our state (availability of data), so it should also be assigned to it (return object). 131 | */ 132 | this.state = { 133 | subscription: { 134 | tasks: Meteor.subscribe('tasks') 135 | } 136 | } 137 | } 138 | 139 | /* Meteor, React 140 | Since we are not planning to use this data anywhere else, we will stop the subscription when the component unmounts. 141 | */ 142 | //noinspection JSUnusedGlobalSymbols 143 | componentWillUnmount() { 144 | this.state.subscription.tasks.stop(); 145 | } 146 | 147 | /* Meteor, React 148 | As mentioned before, data subscriptions define state. Therefore we can toggle subscriptions and assign new ones. 149 | This method could also be made static'aly available and passed around for generic subscription management. However, 150 | within the same component tree, a state-handler should be passed around instead. 151 | */ 152 | toggleSubscription(publication) { 153 | let subscription = this.state.subscription[publication]; 154 | 155 | if (subscription.ready()) { 156 | subscription.stop() 157 | } else { 158 | this.setState({subscription: {tasks: Meteor.subscribe(publication)}}) 159 | } 160 | } 161 | 162 | /* Meteor, React 163 | An object array is simply returned from our collection. With TrackerReact, any changes to the underlying will lead 164 | to an automatic component re-render with a new return value -> virtual dom diffing -> dom update. 165 | */ 166 | //noinspection JSMethodCanBeStatic 167 | tasks() { 168 | return Tasks.find().fetch().reverse(); 169 | } 170 | 171 | handleTasksInsert(e) { 172 | e.preventDefault(); 173 | 174 | /* Meteor 175 | Client-side inserts - due to our allowed auth settings - will propagate from the client, to the server 176 | and to other clients automatically. 177 | */ 178 | Tasks.insert({ 179 | title: this.refs["todoTitle"].value || "No Title", 180 | text: this.refs["todoText"].value || "No Message Text" 181 | }); 182 | } 183 | 184 | render() { 185 | return ( 186 |
187 |
188 |

Reactive Data

189 |

Subscribe to Meteor Collections and 190 | see real-time updates. Open this 191 | page in another browser window and save a new todo message to the "todos" collection.

192 |
193 |
194 | 195 | 196 | 197 | 200 |
201 |
    202 | {this.tasks().map((task) => { 203 | return 204 | })} 205 |
206 |
207 | ) 208 | } 209 | } 210 | 211 | /* React 212 | A generic react component can be simply used within a TrackerReact component. 213 | */ 214 | class Task extends React.Component { 215 | render() { 216 | return ( 217 |
  • 218 |
    {this.props.task.title}
    219 |
    {this.props.task.text}
    220 |
  • 221 | ) 222 | } 223 | } 224 | 225 | 226 | /*********************************************************************************************************************** 227 | * 228 | * [MethodShowcase] 229 | * This component uses Meteor Methods to invoke collection changes on the server side. Changes are 230 | * optimistically applied on the client until the server either confirms or discards the change (i.e. due to missing 231 | * authentication). Here for, the method needs to be both defined on the server and the client (stub simulation). 232 | * 233 | **********************************************************************************************************************/ 234 | 235 | /* Meteor 236 | The relevant Methods and the "temperature" collection is defined in this file. It is in an external file, so that it 237 | can be easily imported on the server as well. See "/server/dataPublications.js". 238 | */ 239 | import "/imports/dataTemperature"; 240 | 241 | /* React 242 | We use a simple react component from npm to illustrate reactivity and optimistic updates. 243 | Source: https://www.npmjs.com/package/react-thermometer 244 | */ 245 | import Thermometer from "react-thermometer"; 246 | 247 | /* Meteor, React -> TrackerReact 248 | Here the mixin method is used (scroll to the end of the file). Real-time data-invalidation due to optimistic updates. 249 | -> "ReactMixin(MethodShowcase.prototype, TrackerReactMixin);" 250 | */ 251 | import ReactMixin from 'react-mixin'; 252 | import {TrackerReactMixin} from 'meteor/ultimatejs:tracker-react'; 253 | 254 | class MethodShowcase extends React.Component { 255 | 256 | /* Meteor, React 257 | Same reasoning as before. See above [CollectionShowcase]. 258 | */ 259 | constructor() { 260 | super(); 261 | 262 | this.state = { 263 | subscription: { 264 | temperature: Meteor.subscribe('temperature') 265 | } 266 | } 267 | } 268 | 269 | /* Meteor, React 270 | Same reasoning as before. See above [CollectionShowcase]. 271 | */ 272 | //noinspection JSUnusedGlobalSymbols 273 | componentWillUnmount() { 274 | this.state.subscription.temperature.stop(); 275 | } 276 | 277 | /* Meteor 278 | FindOne returns the last document object added to the "temperature" collection. Note that we are returning a 279 | loading stub for when the data is not loaded yet. With TrackerReact, loading indicators can be implicit to data 280 | availability and is not limited to whole subscriptions. 281 | */ 282 | //noinspection JSMethodCanBeStatic 283 | temperature() { 284 | return Temperature.findOne({}, {sort: {created: -1}}) || {current: 0, source: "loading"}; 285 | } 286 | 287 | /* Meteor 288 | Before we inserted data directly into a client side collection which propagated itself to the server. Here, a 289 | Meteor Method is "called" to trigger an insert on the server. But the method is also available on the client, 290 | allowing for an optimistic update of data. 291 | 292 | In the end, the data inserted on the server slightly diverts with a different "source" property. As soon the 293 | server caught up, the optimistic data of the client is corrected by the data on the server (source changed from 294 | client to server). Despite any network latency. 295 | 296 | See "/server/dataPublications.js". 297 | */ 298 | handleTemperatureReading(e) { 299 | e.preventDefault(); 300 | 301 | let reading = this.refs["reading"].value; 302 | let delay = this.refs["delay"].value; 303 | 304 | if (reading >= 0 && reading <= 30) { 305 | Meteor.call("addMeasure", reading, delay); 306 | } 307 | } 308 | 309 | /* Meteor 310 | A Meteor Method that only runs on the server, without optimistic updates, changing the last reading 311 | on the server's mongo collection. 312 | 313 | See "/server/dataPublications.js". 314 | */ 315 | //noinspection JSMethodCanBeStatic 316 | changeTemperature(amount) { 317 | Meteor.call("changeMeasure", amount); 318 | } 319 | 320 | render() { 321 | return ( 322 |
    323 |
    324 |

    Optimistic Updates

    325 |

    Make use of Meteor Methods. Simulate 326 | different network latencies below and observe how 327 | data changes are first applied client side, than confirmed server side.

    328 |
    329 |
    330 | 331 | 332 | 333 | 334 | 335 |
    336 |
    337 | 346 |
    347 |
    {this.temperature().current + "° Celsius"}
    348 |
    {"( " + this.temperature().source + " )"}
    349 |
    350 | ) 351 | } 352 | } 353 | 354 | ReactMixin(MethodShowcase.prototype, TrackerReactMixin); 355 | -------------------------------------------------------------------------------- /client/styles.js: -------------------------------------------------------------------------------- 1 | export default style = { 2 | heading: { 3 | fontStyle: "italic", 4 | fontSize: "30px", 5 | textAlign: "center", 6 | color: "#fff", 7 | marginBottom: "40px" 8 | }, 9 | subHeading: { 10 | fontSize: "14px", 11 | fontWeight: "normal", 12 | textAlign: "center", 13 | color: "#fff", 14 | padding: "0 20%", 15 | marginBottom: "10px" 16 | }, 17 | install: { 18 | textAlign: "center", 19 | marginBottom: "20px", 20 | color: "white", 21 | repo: { 22 | fontVariant: "normal", 23 | fontStyle: "normal", 24 | textDecoration: "none", 25 | color: "white" 26 | }, 27 | bash: { 28 | backgroundColor: "#58A6DB", 29 | display: "inline", 30 | padding: "8px 15px", 31 | borderRadius: "5px", 32 | fontFamily: "monospace", 33 | border: "solid 1px #2096CD", 34 | whiteSpace: "nowrap", 35 | lineHeight: "45px" 36 | } 37 | }, 38 | content: { 39 | background: "#fff", 40 | padding: "35px 20px 25px 20px", 41 | heading: { 42 | color: "#565656", 43 | margin: "0 0 1rem 0" 44 | }, 45 | intro: { 46 | marginBottom: "1rem", 47 | color: "#747474", 48 | lineHeight: "1.2rem", 49 | textAlign: "justify" 50 | } 51 | }, 52 | method: { 53 | thermometer: { 54 | padding: "20px 0 20px 50px", 55 | float: "left" 56 | }, 57 | label: { 58 | textAlign: "center", 59 | paddingTop: "35%", 60 | fontSize: "50px", 61 | fontWeight: "bold", 62 | color: "lightgrey" 63 | }, 64 | subLabel: { 65 | textAlign: "center", 66 | paddingTop: "1rem", 67 | fontSize: "25px", 68 | fontWeight: "bold", 69 | color: "lightgrey" 70 | } 71 | }, 72 | collection: { 73 | olList: { 74 | paddingLeft: "40px", 75 | maxHeight: "380px", 76 | overflow: "scroll" 77 | } 78 | }, 79 | input: { 80 | width: "100%", 81 | boxSizing: "border-box", 82 | padding: "10px", 83 | border: "solid 1px #CCC", 84 | outline: "none", 85 | marginBottom: "1rem" 86 | }, 87 | button: { 88 | padding: "10px 20px", 89 | background: "#2096cd", 90 | color: "#FFF", 91 | border: "none", 92 | outline: "none", 93 | cursor: "pointer", 94 | marginRight: "20px", 95 | marginBottom: "1rem", 96 | last: function() { 97 | let last = Object.assign({}, this); 98 | last.marginRight = "none"; 99 | return last; 100 | }, 101 | inverted: function() { 102 | let last = Object.assign({}, this); 103 | last.background = "#58A6DB"; 104 | return last; 105 | } 106 | }, 107 | todo: { 108 | listStyleType: "decimal", 109 | marginBottom: "1rem", 110 | title: { 111 | fontWeight: "bold", 112 | borderBottom: "lightgray 1px solid", 113 | marginBottom: "3px" 114 | }, 115 | text: { 116 | fontSize: "11px" 117 | } 118 | } 119 | }; 120 | -------------------------------------------------------------------------------- /imports/dataTemperature.js: -------------------------------------------------------------------------------- 1 | /* This code is not eagerly loaded (needs import) */ 2 | 3 | /*********************************************************************************************************************** 4 | * 5 | * [Temperature] Collection & Methods 6 | * Instantiate Mongo Collection and declare Meteor Methods to be used on the server and the client for controlled, 7 | * optimistic updates. 8 | * 9 | **********************************************************************************************************************/ 10 | Temperature = new Mongo.Collection("temperature"); 11 | 12 | /* Meteor 13 | On fresh install, when there are no readings available, insert one fixture on the server. 14 | */ 15 | if (Meteor.isServer && Temperature.find().count() <= 0) { 16 | Temperature.insert({current: 21, created: new Date(), source: "fixture"}); 17 | } 18 | 19 | /* 20 | All methods are available to the client and the server. However, within the method blocks functionality can also be 21 | separated. 22 | */ 23 | Meteor.methods({ 24 | addMeasure (reading = 10, delay = 3) { 25 | 26 | delay = delay > 0 ? delay : 3; 27 | reading = reading <= 30 ? reading : 30; 28 | 29 | let measure = { 30 | current: reading, 31 | created: new Date() 32 | }; 33 | 34 | /* 35 | On the server, we insert the "source" property as "from Server" and pretend to have a network latency defined by 36 | the delay argument of the method call. 37 | */ 38 | if (Meteor.isServer) { 39 | measure.source = "from Server"; 40 | // Only available on the server. Pause execution for delay. 41 | Meteor._sleepForMs(delay * 1000); 42 | } else { 43 | measure.source = "from Client"; 44 | } 45 | 46 | /* 47 | The measure is both inserted on the server and optimistically on the client. The server is however always the 48 | source of truth (correcting optimistic updates; here = source "from server"). 49 | */ 50 | Temperature.insert(measure); 51 | }, 52 | changeMeasure (amount) { 53 | if (Meteor.isServer) { 54 | 55 | // Get last result 56 | let reading = Temperature.findOne({}, {sort: {created: -1}}); 57 | 58 | if (typeof reading === "undefined") return; 59 | 60 | const newTemp = Number(reading.current) + Number(amount); 61 | amount = newTemp <= 30 ? newTemp : 30; 62 | 63 | /* 64 | Based of the original document _id, the record is updated and re-propagated across all clients. 65 | */ 66 | Temperature.update({_id: reading._id}, {$set: {current: amount}}); 67 | 68 | } else { 69 | /* 70 | The client did not do any optimist update. 71 | */ 72 | console.log("A reading correction was sent to the server.") 73 | } 74 | } 75 | }); 76 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "dependencies": { 7 | "history": "^1.17.0", 8 | "react": "^0.14.7", 9 | "react-dom": "^0.14.7", 10 | "react-mixin": "^3.0.3", 11 | "react-router": "^2.0.0-rc5", 12 | "react-thermometer": "0.0.3" 13 | }, 14 | "devDependencies": {}, 15 | "scripts": { 16 | "test": "echo \"Error: no test specified\" && exit 1" 17 | }, 18 | "author": "", 19 | "license": "ISC" 20 | } 21 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ## [TrackerReact](https://github.com/ultimatejs/tracker-react) Implementation Example 2 | "No-Config reactive React Components with Meteor" 3 | 4 | An implementation example of React with Meteor 1.3 5 | * clone/fork/download 6 | * `npm install` 7 | * `meteor` 8 | 9 | **Read the source file & comments**: [client/main.jsx](https://github.com/D1no/TrackerReact-Example/blob/master/client/main.jsx) 10 | 11 | *Note*: The source code is thoroughly annotated to showcase the benefit of [TrackerReact](https://github.com/ultimatejs/tracker-react). Please go through the code 12 | to 13 | identify implementation patterns and - in case of mistakes or areas of improvement - provide your feedback. 14 | 15 | ## TrackerReact 16 | Repo: https://github.com/ultimatejs/tracker-react 17 | 18 | Meteor Package 19 | 20 | ``` 21 | meteor add ultimatejs:tracker-react 22 | ``` 23 | 24 | ![image](https://cloud.githubusercontent.com/assets/2397125/13386586/369daaa8-deae-11e5-9321-d63a8cd508cf.png) 25 | 26 | 27 | ## License 28 | MIT 29 | -------------------------------------------------------------------------------- /server/dataPublications.js: -------------------------------------------------------------------------------- 1 | /* This code only runs on the SERVER */ 2 | 3 | /*********************************************************************************************************************** 4 | * 5 | * [Tasks] Collection 6 | * Instantiate Mongo Collection and publish it to the clients. 7 | * 8 | **********************************************************************************************************************/ 9 | Tasks = new Mongo.Collection("tasks"); 10 | 11 | /* Meteor 12 | The client needs to subscribe to this publication. 13 | */ 14 | Meteor.publish('tasks', function () { 15 | return Tasks.find({}); 16 | }); 17 | 18 | /* Meteor 19 | We allow all clients to make changes directly to the collection. But only the server can remove documents. 20 | Still, any user can update any document. Here are no further auth checks = bad for production. Read up on it online. 21 | */ 22 | Tasks.allow({ 23 | insert: function (userId, doc) { 24 | return true; 25 | }, 26 | update: function (userId, doc, fields, modifier) { 27 | return true; 28 | }, 29 | remove: function (userId, doc) { 30 | return false; 31 | } 32 | }); 33 | 34 | /*********************************************************************************************************************** 35 | * 36 | * [Temperature] Collection 37 | * Import Meteor Methods incl. instantiating the Mongo Collection and publish it to the clients. 38 | * 39 | **********************************************************************************************************************/ 40 | import "/imports/dataTemperature"; 41 | 42 | /* Meteor 43 | The client needs to subscribe to this publication. 44 | */ 45 | Meteor.publish('temperature', function () { 46 | return Temperature.find({}); 47 | }); 48 | 49 | /* Meteor 50 | We allow all clients to make changes directly to the collection. But only the server can remove documents. 51 | Still, any user can update any document. Here are no further auth checks = bad for production. Read up on it online. 52 | */ 53 | Temperature.allow({ 54 | insert: function (userId, doc) { 55 | return true; 56 | }, 57 | update: function (userId, doc, fields, modifier) { 58 | return true; 59 | }, 60 | remove: function (userId, doc) { 61 | return false; 62 | } 63 | }); --------------------------------------------------------------------------------