diff --git a/History.md b/History.md index aa4f68f555..9cdb1f08a2 100644 --- a/History.md +++ b/History.md @@ -230,13 +230,15 @@ ### `meteor` command-line tool -* There are two new example apps that can be used as starting points for - developing Meteor apps with React and Angular. They include the necessary - packages for the relevant view layer and don't include Blaze. Use them with: +* You can now create three new example apps with the command line tool. These + are the apps from the official tutorials at http://meteor.com/tutorials, which + demonstrate building the same app with Blaze, Angular, and React. Try these + apps with: ```sh - meteor create --example react - meteor create --example angular + meteor create --example simple-todos + meteor create --example simple-todos-react + meteor create --example simple-todos-angular ``` * `meteor shell` no longer crashes when piped from another command. diff --git a/examples/react/.meteor/packages b/examples/react/.meteor/packages deleted file mode 100644 index a7096bcd4f..0000000000 --- a/examples/react/.meteor/packages +++ /dev/null @@ -1,22 +0,0 @@ -# Meteor packages used by this project, one per line. -# Check this file (and the other files in this directory) into your repository. -# -# 'meteor add' and 'meteor remove' will edit this file for you, -# but you can also edit it by hand. - -meteor-base # Packages every Meteor app needs to have -mobile-experience # Packages for a great mobile UX -mongo # The database Meteor supports right now -session # Client-side reactive dictionary for your app -jquery # Helpful client-side library -tracker # Meteor's client-side reactive programming library - -standard-minifiers # JS/CSS minifiers run for production mode -es5-shim # ECMAScript 5 compatibility for older browsers. -ecmascript # Enable ECMAScript2015+ syntax in app code - -autopublish # Publish all data to the clients (for prototyping) -insecure # Allow all DB writes from clients (for prototyping) - -static-html # Render static HTML content -react # Everything you need to use React with Meteor diff --git a/examples/react/.meteor/release b/examples/react/.meteor/release deleted file mode 100644 index 69acc9f344..0000000000 --- a/examples/react/.meteor/release +++ /dev/null @@ -1 +0,0 @@ -METEOR@1.2-rc.15 diff --git a/examples/react/.meteor/versions b/examples/react/.meteor/versions deleted file mode 100644 index 1f51e9a214..0000000000 --- a/examples/react/.meteor/versions +++ /dev/null @@ -1,73 +0,0 @@ -autopublish@1.0.4-rc.0 -autoupdate@1.2.3-rc.1 -babel-compiler@5.8.22-rc.1 -babel-runtime@0.1.4-rc.0 -base64@1.0.4-rc.0 -binary-heap@1.0.4-rc.0 -blaze@2.1.3-rc.0 -blaze-tools@1.0.4-rc.0 -boilerplate-generator@1.0.4-rc.1 -caching-compiler@1.0.0-rc.0 -caching-html-compiler@1.0.1-rc.0 -callback-hook@1.0.4-rc.0 -check@1.0.6-rc.0 -coffeescript@1.0.8-rc.4 -cosmos:browserify@0.4.0 -ddp@1.2.1-rc.0 -ddp-client@1.2.1-rc.2 -ddp-common@1.2.1-rc.0 -ddp-server@1.2.1-rc.2 -deps@1.0.8-rc.0 -diff-sequence@1.0.1-rc.0 -ecmascript@0.1.3-rc.3 -ecmascript-collections@0.1.5-rc.1 -ejson@1.0.7-rc.0 -es5-shim@0.1.0-rc.0 -fastclick@1.0.7-rc.0 -geojson-utils@1.0.4-rc.0 -hot-code-push@1.0.0-rc.0 -html-tools@1.0.5-rc.0 -htmljs@1.0.5-rc.1 -http@1.1.1-rc.1 -id-map@1.0.4-rc.0 -insecure@1.0.4-rc.0 -jquery@1.11.4-rc.0 -jsx@0.2.1 -launch-screen@1.0.3-rc.1 -livedata@1.0.14-rc.0 -logging@1.0.8-rc.1 -meteor@1.1.7-rc.2 -meteor-base@1.0.1-rc.0 -minifiers@1.1.6-rc.1 -minimongo@1.0.9-rc.0 -mobile-experience@1.0.1-rc.0 -mobile-status-bar@1.0.5-rc.1 -mongo@1.1.1-rc.4 -mongo-id@1.0.1-rc.0 -npm-mongo@1.4.39-rc.0_1 -observe-sequence@1.0.7-rc.0 -ordered-dict@1.0.4-rc.0 -promise@0.4.8-rc.0 -random@1.0.4-rc.0 -react@0.1.12 -react-meteor-data@0.1.8 -react-runtime@0.13.3_6 -react-runtime-dev@0.13.3_5 -react-runtime-prod@0.13.3_4 -reactive-dict@1.1.1-rc.0 -reactive-var@1.0.6-rc.0 -reload@1.1.4-rc.0 -retry@1.0.4-rc.0 -routepolicy@1.0.6-rc.0 -session@1.1.1-rc.0 -spacebars@1.0.7-rc.0 -spacebars-compiler@1.0.7-rc.0 -standard-minifiers@1.0.0-rc.2 -static-html@1.0.0-rc.5 -templating-tools@1.0.0-rc.0 -tracker@1.0.8-rc.0 -ui@1.0.7-rc.0 -underscore@1.0.4-rc.0 -url@1.0.5-rc.0 -webapp@1.2.2-rc.3 -webapp-hashing@1.0.4-rc.0 diff --git a/examples/react/index.html b/examples/react/index.html deleted file mode 100644 index ee14011d45..0000000000 --- a/examples/react/index.html +++ /dev/null @@ -1,11 +0,0 @@ - - react-skel - - - -

Welcome to Meteor and React!

- -

Try the tutorial!

- -
- diff --git a/examples/react/index.jsx b/examples/react/index.jsx deleted file mode 100644 index 72b1dfc6f9..0000000000 --- a/examples/react/index.jsx +++ /dev/null @@ -1,65 +0,0 @@ -// A collection that is synchronized across client and server with the -// 'autopublish' package. To control where the data is accessible from, remove -// 'autopublish' and use Meteor.publish and Meteor.subscribe -Items = new Mongo.Collection("items"); - -// A React component, defined in the normal way -App = React.createClass({ - mixins: [ReactMeteorData], - - // Load data from collections inside this special method enabled by the - // ReactMeteorData mixin. The results are attached to this.data on the - // component - getMeteorData() { - return { - items: Items.find().fetch() - }; - }, - - addItem() { - const nextIndex = Items.find().count() + 1; - - // We can insert from the client because we have the 'insecure' package - // installed. Remove it and use Meteor methods for better security - Items.insert({ - text: "Hello world! " + nextIndex - }); - }, - - renderItems() { - return this.data.items.map((item) => { - return ( -
  • - {item.text} -
  • - ); - }); - }, - - render() { - return ( -
    - - - -
    - ) - } -}); - -if (Meteor.isClient) { - // Code here runs on the client only - - Meteor.startup(() => { - // Make sure to render after startup so the DOM is ready - React.render(, document.getElementById("react-container")); - }); -} - -if (Meteor.isServer) { - // Code inside here will run on the server only -} diff --git a/examples/react/style.css b/examples/react/style.css deleted file mode 100644 index b6b4052b43..0000000000 --- a/examples/react/style.css +++ /dev/null @@ -1 +0,0 @@ -/* CSS declarations go here */ diff --git a/examples/react/.meteor/.finished-upgraders b/examples/simple-todos-angular/.meteor/.finished-upgraders similarity index 75% rename from examples/react/.meteor/.finished-upgraders rename to examples/simple-todos-angular/.meteor/.finished-upgraders index cbcc9f90c6..8a761038c5 100644 --- a/examples/react/.meteor/.finished-upgraders +++ b/examples/simple-todos-angular/.meteor/.finished-upgraders @@ -6,6 +6,3 @@ notices-for-0.9.0 notices-for-0.9.1 0.9.4-platform-file notices-for-facebook-graph-api-2 -1.2.0-standard-minifiers-package -1.2.0-meteor-platform-split -1.2.0-cordova-changes diff --git a/examples/react/.meteor/.gitignore b/examples/simple-todos-angular/.meteor/.gitignore similarity index 100% rename from examples/react/.meteor/.gitignore rename to examples/simple-todos-angular/.meteor/.gitignore diff --git a/examples/simple-todos-angular/.meteor/packages b/examples/simple-todos-angular/.meteor/packages new file mode 100644 index 0000000000..2d0433a829 --- /dev/null +++ b/examples/simple-todos-angular/.meteor/packages @@ -0,0 +1,10 @@ +# Meteor packages used by this project, one per line. +# Check this file (and the other files in this directory) into your repository. +# +# 'meteor add' and 'meteor remove' will edit this file for you, +# but you can also edit it by hand. + +meteor-platform +urigo:angular +accounts-ui +accounts-password diff --git a/examples/react/.meteor/platforms b/examples/simple-todos-angular/.meteor/platforms similarity index 100% rename from examples/react/.meteor/platforms rename to examples/simple-todos-angular/.meteor/platforms diff --git a/examples/simple-todos-angular/.meteor/release b/examples/simple-todos-angular/.meteor/release new file mode 100644 index 0000000000..dab6b552c0 --- /dev/null +++ b/examples/simple-todos-angular/.meteor/release @@ -0,0 +1 @@ +METEOR@1.1.0.2 diff --git a/examples/simple-todos-angular/.meteor/versions b/examples/simple-todos-angular/.meteor/versions new file mode 100644 index 0000000000..96bf8b78b8 --- /dev/null +++ b/examples/simple-todos-angular/.meteor/versions @@ -0,0 +1,61 @@ +accounts-base@1.2.0 +accounts-password@1.1.1 +accounts-ui@1.1.5 +accounts-ui-unstyled@1.1.7 +angular:angular@1.4.2 +autoupdate@1.2.1 +base64@1.0.3 +binary-heap@1.0.3 +blaze@2.1.2 +blaze-tools@1.0.3 +boilerplate-generator@1.0.3 +callback-hook@1.0.3 +check@1.0.5 +dburles:mongo-collection-instances@0.3.4 +ddp@1.1.0 +deps@1.0.7 +ejson@1.0.6 +email@1.0.6 +fastclick@1.0.3 +geojson-utils@1.0.3 +html-tools@1.0.4 +htmljs@1.0.4 +http@1.1.0 +id-map@1.0.3 +jquery@1.11.3_2 +json@1.0.3 +lai:collection-extensions@0.1.4 +launch-screen@1.0.2 +less@1.0.14 +livedata@1.0.13 +localstorage@1.0.3 +logging@1.0.7 +meteor@1.1.6 +meteor-platform@1.2.2 +minifiers@1.1.5 +minimongo@1.0.8 +mobile-status-bar@1.0.3 +mongo@1.1.0 +npm-bcrypt@0.7.8_2 +observe-sequence@1.0.6 +ordered-dict@1.0.3 +random@1.0.3 +reactive-dict@1.1.0 +reactive-var@1.0.5 +reload@1.1.3 +retry@1.0.3 +routepolicy@1.0.5 +service-configuration@1.0.4 +session@1.1.0 +sha@1.0.3 +spacebars@1.0.6 +spacebars-compiler@1.0.6 +srp@1.0.3 +templating@1.1.1 +tracker@1.0.7 +ui@1.0.6 +underscore@1.0.3 +urigo:angular@0.9.2 +url@1.0.4 +webapp@1.2.0 +webapp-hashing@1.0.3 diff --git a/examples/simple-todos-angular/README.md b/examples/simple-todos-angular/README.md new file mode 100644 index 0000000000..258e5f8975 --- /dev/null +++ b/examples/simple-todos-angular/README.md @@ -0,0 +1,9 @@ +# Simple Todo List + +The Meteor Tutorial app, angular-meteor version. + +Use it to share a single todo list with your friends. The list updates on everyone's screen in real time, and you can make tasks private if you don't want others to see them. + +Learn how to build this app by following the [Meteor Tutorial - Angular version](https://www.meteor.com/tutorials/angular/creating-an-app). + +![screenshot](https://github.com/meteor/simple-todos/blob/master/screenshot.png) diff --git a/examples/simple-todos-angular/simple-todos-angular.css b/examples/simple-todos-angular/simple-todos-angular.css new file mode 100644 index 0000000000..0bca5d2ada --- /dev/null +++ b/examples/simple-todos-angular/simple-todos-angular.css @@ -0,0 +1,127 @@ +/* CSS declarations go here */ +body { + font-family: sans-serif; + background-color: #315481; + background-image: linear-gradient(to bottom, #315481, #918e82 100%); + background-attachment: fixed; + + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + + padding: 0; + margin: 0; + + font-size: 14px; +} + +.container { + max-width: 600px; + margin: 0 auto; + min-height: 100%; + background: white; +} + +header { + background: #d2edf4; + background-image: linear-gradient(to bottom, #d0edf5, #e1e5f0 100%); + padding: 20px 15px 15px 15px; + position: relative; +} + +#login-buttons { + display: block; +} + +h1 { + font-size: 1.5em; + margin: 0; + margin-bottom: 10px; + display: inline-block; + margin-right: 1em; +} + +form { + margin-top: 10px; + margin-bottom: -10px; + position: relative; +} + +.new-task input { + box-sizing: border-box; + padding: 10px 0; + background: transparent; + border: none; + width: 100%; + padding-right: 80px; + font-size: 1em; +} + +.new-task input:focus{ + outline: 0; +} + +ul { + margin: 0; + padding: 0; + background: white; +} + +.delete { + float: right; + font-weight: bold; + background: none; + font-size: 1em; + border: none; + position: relative; +} + +li { + position: relative; + list-style: none; + padding: 15px; + border-bottom: #eee solid 1px; +} + +li .text { + margin-left: 10px; +} + +li.checked { + color: #888; +} + +li.checked .text { + text-decoration: line-through; +} + +li.private { + background: #eee; + border-color: #ddd; +} + +header .hide-completed { + float: right; +} + +.toggle-private { + margin-left: 5px; +} + +@media (max-width: 600px) { + li { + padding: 12px 15px; + } + + .search { + width: 150px; + clear: both; + } + + .new-task input { + padding-bottom: 5px; + } +} + diff --git a/examples/simple-todos-angular/simple-todos-angular.html b/examples/simple-todos-angular/simple-todos-angular.html new file mode 100644 index 0000000000..e43eceb5c9 --- /dev/null +++ b/examples/simple-todos-angular/simple-todos-angular.html @@ -0,0 +1,7 @@ + + Todo List + + + + diff --git a/examples/simple-todos-angular/simple-todos-angular.js b/examples/simple-todos-angular/simple-todos-angular.js new file mode 100644 index 0000000000..1d371fb3ca --- /dev/null +++ b/examples/simple-todos-angular/simple-todos-angular.js @@ -0,0 +1,113 @@ +Tasks = new Mongo.Collection('tasks'); + +if (Meteor.isClient) { + + Accounts.ui.config({ + passwordSignupFields: "USERNAME_ONLY" + }); + + // This code only runs on the client + angular.module('simple-todos',['angular-meteor']); + + function onReady() { + angular.bootstrap(document, ['simple-todos']); + } + + if (Meteor.isCordova) + angular.element(document).on('deviceready', onReady); + else + angular.element(document).ready(onReady); + + angular.module('simple-todos').controller('TodosListCtrl', ['$scope', '$meteor', + function ($scope, $meteor) { + + $scope.$meteorSubscribe('tasks'); + + $scope.tasks = $meteor.collection(function() { + return Tasks.find($scope.getReactively('query'), {sort: {createdAt: -1}}) + }); + + $scope.addTask = function (newTask) { + $meteor.call('addTask', newTask); + }; + + $scope.deleteTask = function (task) { + $meteor.call('deleteTask', task._id); + }; + + $scope.setChecked = function (task) { + $meteor.call('setChecked', task._id, !task.checked); + }; + + $scope.setPrivate = function (task) { + $meteor.call('setPrivate', task._id, ! task.private); + }; + + $scope.$watch('hideCompleted', function() { + if ($scope.hideCompleted) + $scope.query = {checked: {$ne: true}}; + else + $scope.query = {}; + }); + + $scope.incompleteCount = function () { + return Tasks.find({ checked: {$ne: true} }).count(); + }; + + }]); +} + +Meteor.methods({ + addTask: function (text) { + // Make sure the user is logged in before inserting a task + if (! Meteor.userId()) { + throw new Meteor.Error('not-authorized'); + } + + Tasks.insert({ + text: text, + createdAt: new Date(), + owner: Meteor.userId(), + username: Meteor.user().username + }); + }, + deleteTask: function (taskId) { + var task = Tasks.findOne(taskId); + if (task.private && task.owner !== Meteor.userId()) { + // If the task is private, make sure only the owner can delete it + throw new Meteor.Error('not-authorized'); + } + + Tasks.remove(taskId); + }, + setChecked: function (taskId, setChecked) { + var task = Tasks.findOne(taskId); + if (task.private && task.owner !== Meteor.userId()) { + // If the task is private, make sure only the owner can check it off + throw new Meteor.Error('not-authorized'); + } + + Tasks.update(taskId, { $set: { checked: setChecked} }); + }, + setPrivate: function (taskId, setToPrivate) { + var task = Tasks.findOne(taskId); + + // Make sure only the task owner can make a task private + if (task.owner !== Meteor.userId()) { + throw new Meteor.Error('not-authorized'); + } + + Tasks.update(taskId, { $set: { private: setToPrivate } }); + } +}); + +if (Meteor.isServer) { + Meteor.publish('tasks', function () { + return Tasks.find({ + $or: [ + { private: {$ne: true} }, + { owner: this.userId } + ] + }); + }); +} diff --git a/examples/simple-todos-angular/todos-list.ng.html b/examples/simple-todos-angular/todos-list.ng.html new file mode 100644 index 0000000000..1799364849 --- /dev/null +++ b/examples/simple-todos-angular/todos-list.ng.html @@ -0,0 +1,39 @@ +
    +
    +

    Todo List ( {{ incompleteCount() }} )

    + + + + + + +
    + +
    +
    + + +
    diff --git a/examples/simple-todos-react/.meteor/.finished-upgraders b/examples/simple-todos-react/.meteor/.finished-upgraders new file mode 100644 index 0000000000..8a761038c5 --- /dev/null +++ b/examples/simple-todos-react/.meteor/.finished-upgraders @@ -0,0 +1,8 @@ +# This file contains information which helps Meteor properly upgrade your +# app when you run 'meteor update'. You should check it into version control +# with your project. + +notices-for-0.9.0 +notices-for-0.9.1 +0.9.4-platform-file +notices-for-facebook-graph-api-2 diff --git a/examples/simple-todos-react/.meteor/.gitignore b/examples/simple-todos-react/.meteor/.gitignore new file mode 100644 index 0000000000..4083037423 --- /dev/null +++ b/examples/simple-todos-react/.meteor/.gitignore @@ -0,0 +1 @@ +local diff --git a/examples/simple-todos-react/.meteor/packages b/examples/simple-todos-react/.meteor/packages new file mode 100644 index 0000000000..81dd35c307 --- /dev/null +++ b/examples/simple-todos-react/.meteor/packages @@ -0,0 +1,10 @@ +# Meteor packages used by this project, one per line. +# Check this file (and the other files in this directory) into your repository. +# +# 'meteor add' and 'meteor remove' will edit this file for you, +# but you can also edit it by hand. + +meteor-platform +react +accounts-ui +accounts-password diff --git a/examples/simple-todos-react/.meteor/platforms b/examples/simple-todos-react/.meteor/platforms new file mode 100644 index 0000000000..efeba1b50c --- /dev/null +++ b/examples/simple-todos-react/.meteor/platforms @@ -0,0 +1,2 @@ +server +browser diff --git a/examples/simple-todos-react/.meteor/release b/examples/simple-todos-react/.meteor/release new file mode 100644 index 0000000000..dab6b552c0 --- /dev/null +++ b/examples/simple-todos-react/.meteor/release @@ -0,0 +1 @@ +METEOR@1.1.0.2 diff --git a/examples/simple-todos-react/.meteor/versions b/examples/simple-todos-react/.meteor/versions new file mode 100644 index 0000000000..777cca40d7 --- /dev/null +++ b/examples/simple-todos-react/.meteor/versions @@ -0,0 +1,67 @@ +accounts-base@1.2.0 +accounts-password@1.1.1 +accounts-ui@1.1.5 +accounts-ui-unstyled@1.1.7 +autoupdate@1.2.1 +babel-compiler@5.6.15 +babel-runtime@0.1.1 +base64@1.0.3 +binary-heap@1.0.3 +blaze@2.1.2 +blaze-tools@1.0.3 +boilerplate-generator@1.0.3 +callback-hook@1.0.3 +check@1.0.5 +coffeescript@1.0.6 +cosmos:browserify@0.4.0 +ddp@1.1.0 +deps@1.0.7 +ejson@1.0.6 +email@1.0.6 +fastclick@1.0.3 +geojson-utils@1.0.3 +html-tools@1.0.4 +htmljs@1.0.4 +http@1.1.0 +id-map@1.0.3 +jquery@1.11.3_2 +json@1.0.3 +jsx@0.1.1 +launch-screen@1.0.2 +less@1.0.14 +livedata@1.0.13 +localstorage@1.0.3 +logging@1.0.7 +meteor@1.1.6 +meteor-platform@1.2.2 +minifiers@1.1.5 +minimongo@1.0.8 +mobile-status-bar@1.0.3 +mongo@1.1.0 +npm-bcrypt@0.7.8_2 +observe-sequence@1.0.6 +ordered-dict@1.0.3 +random@1.0.3 +react@0.1.2 +react-meteor-data@0.1.0 +react-runtime@0.13.3_2 +react-runtime-dev@0.13.3_2 +react-runtime-prod@0.13.3_1 +reactive-dict@1.1.0 +reactive-var@1.0.5 +reload@1.1.3 +retry@1.0.3 +routepolicy@1.0.5 +service-configuration@1.0.4 +session@1.1.0 +sha@1.0.3 +spacebars@1.0.6 +spacebars-compiler@1.0.6 +srp@1.0.3 +templating@1.1.1 +tracker@1.0.7 +ui@1.0.6 +underscore@1.0.3 +url@1.0.4 +webapp@1.2.0 +webapp-hashing@1.0.3 diff --git a/examples/simple-todos-react/AccountsUIWrapper.jsx b/examples/simple-todos-react/AccountsUIWrapper.jsx new file mode 100644 index 0000000000..87fd3b3ea8 --- /dev/null +++ b/examples/simple-todos-react/AccountsUIWrapper.jsx @@ -0,0 +1,15 @@ +AccountsUIWrapper = React.createClass({ + componentDidMount() { + // Use Meteor Blaze to render login buttons + this.view = Blaze.render(Template.loginButtons, + React.findDOMNode(this.refs.container)); + }, + componentWillUnmount() { + // Clean up Blaze view + Blaze.remove(this.view); + }, + render() { + // Just render a placeholder container that will be filled in + return ; + } +}); diff --git a/examples/simple-todos-react/App.jsx b/examples/simple-todos-react/App.jsx new file mode 100644 index 0000000000..82915ec4f5 --- /dev/null +++ b/examples/simple-todos-react/App.jsx @@ -0,0 +1,93 @@ +// App component - represents the whole app +App = React.createClass({ + + // This mixin makes the getMeteorData method work + mixins: [ReactMeteorData], + + getInitialState() { + return { + hideCompleted: false + } + }, + + // Loads items from the Tasks collection and puts them on this.data.tasks + getMeteorData() { + let query = {}; + + if (this.state.hideCompleted) { + // If hide completed is checked, filter tasks + query = {checked: {$ne: true}}; + } + + return { + tasks: Tasks.find(query, {sort: {createdAt: -1}}).fetch(), + incompleteCount: Tasks.find({checked: {$ne: true}}).count(), + currentUser: Meteor.user() + }; + }, + + renderTasks() { + // Get tasks from this.data.tasks + return this.data.tasks.map((task) => { + const currentUserId = this.data.currentUser && this.data.currentUser._id; + const showPrivateButton = task.owner === currentUserId; + + return ; + }); + }, + + handleSubmit(event) { + event.preventDefault(); + + // Find the text field via the React ref + var text = React.findDOMNode(this.refs.textInput).value.trim(); + + Meteor.call("addTask", text); + + // Clear form + React.findDOMNode(this.refs.textInput).value = ""; + }, + + toggleHideCompleted() { + this.setState({ + hideCompleted: ! this.state.hideCompleted + }); + }, + + render() { + return ( +
    +
    +

    Todo List ({this.data.incompleteCount})

    + + + + + + { this.data.currentUser ? +
    + +
    : '' + } +
    + +
      + {this.renderTasks()} +
    +
    + ); + } +}); diff --git a/examples/simple-todos-react/LICENSE b/examples/simple-todos-react/LICENSE new file mode 100644 index 0000000000..e41973a6e1 --- /dev/null +++ b/examples/simple-todos-react/LICENSE @@ -0,0 +1,22 @@ +======================================== +Meteor is licensed under the MIT License +======================================== + +Copyright (C) 2011--2015 Meteor Development Group + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/examples/simple-todos-react/README.md b/examples/simple-todos-react/README.md new file mode 100644 index 0000000000..214dd3a445 --- /dev/null +++ b/examples/simple-todos-react/README.md @@ -0,0 +1,9 @@ +# Simple Todo List + +The Meteor Tutorial app. + +Use it to share a single todo list with your friends. The list updates on everyone's screen in real time, and you can make tasks private if you don't want others to see them. + +Learn how to build this app by following the [Meteor Tutorial](http://www.meteor.com/install). + +![screenshot](screenshot.png) \ No newline at end of file diff --git a/examples/simple-todos-react/Task.jsx b/examples/simple-todos-react/Task.jsx new file mode 100644 index 0000000000..37eadd38b8 --- /dev/null +++ b/examples/simple-todos-react/Task.jsx @@ -0,0 +1,52 @@ +// Task component - represents a single todo item +Task = React.createClass({ + propTypes: { + task: React.PropTypes.object.isRequired, + showPrivateButton: React.PropTypes.bool.isRequired + }, + + toggleChecked() { + // Set the checked property to the opposite of its current value + Meteor.call("setChecked", this.props.task._id, ! this.props.task.checked); + }, + + deleteThisTask() { + Meteor.call("removeTask", this.props.task._id); + }, + + togglePrivate() { + Meteor.call("setPrivate", this.props.task._id, ! this.props.task.private); + }, + + render() { + // Give tasks a different className when they are checked off, + // so that we can style them nicely in CSS + // Add "checked" and/or "private" to the className when needed + const taskClassName = (this.props.task.checked ? "checked" : "") + " " + + (this.props.task.private ? "private" : ""); + + return ( +
  • + + + + + { this.props.showPrivateButton ? ( + + ) : ''} + + + {this.props.task.username}: {this.props.task.text} + +
  • + ); + } +}); diff --git a/examples/simple-todos-react/screenshot.png b/examples/simple-todos-react/screenshot.png new file mode 100644 index 0000000000..a9a716d24a Binary files /dev/null and b/examples/simple-todos-react/screenshot.png differ diff --git a/examples/simple-todos-react/simple-todos-react.css b/examples/simple-todos-react/simple-todos-react.css new file mode 100644 index 0000000000..cec3ae619a --- /dev/null +++ b/examples/simple-todos-react/simple-todos-react.css @@ -0,0 +1,126 @@ +/* CSS declarations go here */ +body { + font-family: sans-serif; + background-color: #315481; + background-image: linear-gradient(to bottom, #315481, #918e82 100%); + background-attachment: fixed; + + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + + padding: 0; + margin: 0; + + font-size: 14px; +} + +.container { + max-width: 600px; + margin: 0 auto; + min-height: 100%; + background: white; +} + +header { + background: #d2edf4; + background-image: linear-gradient(to bottom, #d0edf5, #e1e5f0 100%); + padding: 20px 15px 15px 15px; + position: relative; +} + +#login-buttons { + display: block; +} + +h1 { + font-size: 1.5em; + margin: 0; + margin-bottom: 10px; + display: inline-block; + margin-right: 1em; +} + +form { + margin-top: 10px; + margin-bottom: -10px; + position: relative; +} + +.new-task input { + box-sizing: border-box; + padding: 10px 0; + background: transparent; + border: none; + width: 100%; + padding-right: 80px; + font-size: 1em; +} + +.new-task input:focus{ + outline: 0; +} + +ul { + margin: 0; + padding: 0; + background: white; +} + +.delete { + float: right; + font-weight: bold; + background: none; + font-size: 1em; + border: none; + position: relative; +} + +li { + position: relative; + list-style: none; + padding: 15px; + border-bottom: #eee solid 1px; +} + +li .text { + margin-left: 10px; +} + +li.checked { + color: #888; +} + +li.checked .text { + text-decoration: line-through; +} + +li.private { + background: #eee; + border-color: #ddd; +} + +header .hide-completed { + float: right; +} + +.toggle-private { + margin-left: 5px; +} + +@media (max-width: 600px) { + li { + padding: 12px 15px; + } + + .search { + width: 150px; + clear: both; + } + + .new-task input { + padding-bottom: 5px; + } +} \ No newline at end of file diff --git a/examples/simple-todos-react/simple-todos-react.html b/examples/simple-todos-react/simple-todos-react.html new file mode 100644 index 0000000000..1aae2d4736 --- /dev/null +++ b/examples/simple-todos-react/simple-todos-react.html @@ -0,0 +1,7 @@ + + Todo List + + + +
    + diff --git a/examples/simple-todos-react/simple-todos-react.jsx b/examples/simple-todos-react/simple-todos-react.jsx new file mode 100644 index 0000000000..2b4ada2b4b --- /dev/null +++ b/examples/simple-todos-react/simple-todos-react.jsx @@ -0,0 +1,75 @@ +// Define a collection to hold our tasks +Tasks = new Mongo.Collection("tasks"); + +if (Meteor.isClient) { + // This code is executed on the client only + Accounts.ui.config({ + passwordSignupFields: "USERNAME_ONLY" + }); + + Meteor.subscribe("tasks"); + + Meteor.startup(function () { + // Use Meteor.startup to render the component after the page is ready + React.render(, document.getElementById("render-target")); + }); +} + +if (Meteor.isServer) { + // Only publish tasks that are public or belong to the current user + Meteor.publish("tasks", function () { + return Tasks.find({ + $or: [ + { private: {$ne: true} }, + { owner: this.userId } + ] + }); + }); +} + +Meteor.methods({ + addTask(text) { + // Make sure the user is logged in before inserting a task + if (! Meteor.userId()) { + throw new Meteor.Error("not-authorized"); + } + + Tasks.insert({ + text: text, + createdAt: new Date(), + owner: Meteor.userId(), + username: Meteor.user().username + }); + }, + + removeTask(taskId) { + const task = Tasks.findOne(taskId); + if (task.private && task.owner !== Meteor.userId()) { + // If the task is private, make sure only the owner can delete it + throw new Meteor.Error("not-authorized"); + } + + Tasks.remove(taskId); + }, + + setChecked(taskId, setChecked) { + const task = Tasks.findOne(taskId); + if (task.private && task.owner !== Meteor.userId()) { + // If the task is private, make sure only the owner can check it off + throw new Meteor.Error("not-authorized"); + } + + Tasks.update(taskId, { $set: { checked: setChecked} }); + }, + + setPrivate(taskId, setToPrivate) { + const task = Tasks.findOne(taskId); + + // Make sure only the task owner can make a task private + if (task.owner !== Meteor.userId()) { + throw new Meteor.Error("not-authorized"); + } + + Tasks.update(taskId, { $set: { private: setToPrivate } }); + } +}); diff --git a/examples/simple-todos/.meteor/.finished-upgraders b/examples/simple-todos/.meteor/.finished-upgraders new file mode 100644 index 0000000000..8a761038c5 --- /dev/null +++ b/examples/simple-todos/.meteor/.finished-upgraders @@ -0,0 +1,8 @@ +# This file contains information which helps Meteor properly upgrade your +# app when you run 'meteor update'. You should check it into version control +# with your project. + +notices-for-0.9.0 +notices-for-0.9.1 +0.9.4-platform-file +notices-for-facebook-graph-api-2 diff --git a/examples/simple-todos/.meteor/.gitignore b/examples/simple-todos/.meteor/.gitignore new file mode 100644 index 0000000000..4083037423 --- /dev/null +++ b/examples/simple-todos/.meteor/.gitignore @@ -0,0 +1 @@ +local diff --git a/examples/simple-todos/.meteor/packages b/examples/simple-todos/.meteor/packages new file mode 100644 index 0000000000..aaee953fcc --- /dev/null +++ b/examples/simple-todos/.meteor/packages @@ -0,0 +1,9 @@ +# Meteor packages used by this project, one per line. +# Check this file (and the other files in this directory) into your repository. +# +# 'meteor add' and 'meteor remove' will edit this file for you, +# but you can also edit it by hand. + +meteor-platform +accounts-ui +accounts-password diff --git a/examples/simple-todos/.meteor/platforms b/examples/simple-todos/.meteor/platforms new file mode 100644 index 0000000000..efeba1b50c --- /dev/null +++ b/examples/simple-todos/.meteor/platforms @@ -0,0 +1,2 @@ +server +browser diff --git a/examples/simple-todos/.meteor/release b/examples/simple-todos/.meteor/release new file mode 100644 index 0000000000..dab6b552c0 --- /dev/null +++ b/examples/simple-todos/.meteor/release @@ -0,0 +1 @@ +METEOR@1.1.0.2 diff --git a/examples/simple-todos/.meteor/versions b/examples/simple-todos/.meteor/versions new file mode 100644 index 0000000000..6766561ee5 --- /dev/null +++ b/examples/simple-todos/.meteor/versions @@ -0,0 +1,57 @@ +accounts-base@1.2.0 +accounts-password@1.1.1 +accounts-ui@1.1.5 +accounts-ui-unstyled@1.1.7 +autoupdate@1.2.1 +base64@1.0.3 +binary-heap@1.0.3 +blaze@2.1.2 +blaze-tools@1.0.3 +boilerplate-generator@1.0.3 +callback-hook@1.0.3 +check@1.0.5 +ddp@1.1.0 +deps@1.0.7 +ejson@1.0.6 +email@1.0.6 +fastclick@1.0.3 +geojson-utils@1.0.3 +html-tools@1.0.4 +htmljs@1.0.4 +http@1.1.0 +id-map@1.0.3 +jquery@1.11.3_2 +json@1.0.3 +launch-screen@1.0.2 +less@1.0.14 +livedata@1.0.13 +localstorage@1.0.3 +logging@1.0.7 +meteor@1.1.6 +meteor-platform@1.2.2 +minifiers@1.1.5 +minimongo@1.0.8 +mobile-status-bar@1.0.3 +mongo@1.1.0 +npm-bcrypt@0.7.8_2 +observe-sequence@1.0.6 +ordered-dict@1.0.3 +random@1.0.3 +reactive-dict@1.1.0 +reactive-var@1.0.5 +reload@1.1.3 +retry@1.0.3 +routepolicy@1.0.5 +service-configuration@1.0.4 +session@1.1.0 +sha@1.0.3 +spacebars@1.0.6 +spacebars-compiler@1.0.6 +srp@1.0.3 +templating@1.1.1 +tracker@1.0.7 +ui@1.0.6 +underscore@1.0.3 +url@1.0.4 +webapp@1.2.0 +webapp-hashing@1.0.3 diff --git a/examples/simple-todos/LICENSE b/examples/simple-todos/LICENSE new file mode 100644 index 0000000000..e41973a6e1 --- /dev/null +++ b/examples/simple-todos/LICENSE @@ -0,0 +1,22 @@ +======================================== +Meteor is licensed under the MIT License +======================================== + +Copyright (C) 2011--2015 Meteor Development Group + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/examples/simple-todos/README.md b/examples/simple-todos/README.md new file mode 100644 index 0000000000..214dd3a445 --- /dev/null +++ b/examples/simple-todos/README.md @@ -0,0 +1,9 @@ +# Simple Todo List + +The Meteor Tutorial app. + +Use it to share a single todo list with your friends. The list updates on everyone's screen in real time, and you can make tasks private if you don't want others to see them. + +Learn how to build this app by following the [Meteor Tutorial](http://www.meteor.com/install). + +![screenshot](screenshot.png) \ No newline at end of file diff --git a/examples/simple-todos/screenshot.png b/examples/simple-todos/screenshot.png new file mode 100644 index 0000000000..a9a716d24a Binary files /dev/null and b/examples/simple-todos/screenshot.png differ diff --git a/examples/simple-todos/simple-todos.css b/examples/simple-todos/simple-todos.css new file mode 100644 index 0000000000..ed5e77ec5d --- /dev/null +++ b/examples/simple-todos/simple-todos.css @@ -0,0 +1,126 @@ +/* CSS declarations go here */ +body { + font-family: sans-serif; + background-color: #315481; + background-image: linear-gradient(to bottom, #315481, #918e82 100%); + background-attachment: fixed; + + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + + padding: 0; + margin: 0; + + font-size: 14px; +} + +.container { + max-width: 600px; + margin: 0 auto; + min-height: 100%; + background: white; +} + +header { + background: #d2edf4; + background-image: linear-gradient(to bottom, #d0edf5, #e1e5f0 100%); + padding: 20px 15px 15px 15px; + position: relative; +} + +#login-buttons { + display: block; +} + +h1 { + font-size: 1.5em; + margin: 0; + margin-bottom: 10px; + display: inline-block; + margin-right: 1em; +} + +form { + margin-top: 10px; + margin-bottom: -10px; + position: relative; +} + +.new-task input { + box-sizing: border-box; + padding: 10px 0; + background: transparent; + border: none; + width: 100%; + padding-right: 80px; + font-size: 1em; +} + +.new-task input:focus{ + outline: 0; +} + +ul { + margin: 0; + padding: 0; + background: white; +} + +.delete { + float: right; + font-weight: bold; + background: none; + font-size: 1em; + border: none; + position: relative; +} + +li { + position: relative; + list-style: none; + padding: 15px; + border-bottom: #eee solid 1px; +} + +li .text { + margin-left: 10px; +} + +li.checked { + color: #888; +} + +li.checked .text { + text-decoration: line-through; +} + +li.private { + background: #eee; + border-color: #ddd; +} + +header .hide-completed { + float: right; +} + +.toggle-private { + margin-left: 5px; +} + +@media (max-width: 600px) { + li { + padding: 12px 15px; + } + + .search { + width: 150px; + clear: both; + } + + .new-task input { + padding-bottom: 5px; + } +} diff --git a/examples/simple-todos/simple-todos.html b/examples/simple-todos/simple-todos.html new file mode 100644 index 0000000000..08854e2ffe --- /dev/null +++ b/examples/simple-todos/simple-todos.html @@ -0,0 +1,50 @@ + + Todo List + + + +
    +
    +

    Todo List ({{incompleteCount}})

    + + + + {{> loginButtons}} + + {{#if currentUser}} +
    + +
    + {{/if}} +
    + +
      + {{#each tasks}} + {{> task}} + {{/each}} +
    +
    + + + diff --git a/examples/simple-todos/simple-todos.js b/examples/simple-todos/simple-todos.js new file mode 100644 index 0000000000..9bf41bbfc8 --- /dev/null +++ b/examples/simple-todos/simple-todos.js @@ -0,0 +1,123 @@ +Tasks = new Mongo.Collection("tasks"); + +if (Meteor.isServer) { + // This code only runs on the server + // Only publish tasks that are public or belong to the current user + Meteor.publish("tasks", function () { + return Tasks.find({ + $or: [ + { private: {$ne: true} }, + { owner: this.userId } + ] + }); + }); +} + +if (Meteor.isClient) { + // This code only runs on the client + Meteor.subscribe("tasks"); + + Template.body.helpers({ + tasks: function () { + if (Session.get("hideCompleted")) { + // If hide completed is checked, filter tasks + return Tasks.find({checked: {$ne: true}}, {sort: {createdAt: -1}}); + } else { + // Otherwise, return all of the tasks + return Tasks.find({}, {sort: {createdAt: -1}}); + } + }, + hideCompleted: function () { + return Session.get("hideCompleted"); + }, + incompleteCount: function () { + return Tasks.find({checked: {$ne: true}}).count(); + } + }); + + Template.body.events({ + "submit .new-task": function (event) { + // Prevent default browser form submit + event.preventDefault(); + + // Get value from form element + var text = event.target.text.value; + + // Insert a task into the collection + Meteor.call("addTask", text); + + // Clear form + event.target.text.value = ""; + }, + "change .hide-completed input": function (event) { + Session.set("hideCompleted", event.target.checked); + } + }); + + Template.task.helpers({ + isOwner: function () { + return this.owner === Meteor.userId(); + } + }); + + Template.task.events({ + "click .toggle-checked": function () { + // Set the checked property to the opposite of its current value + Meteor.call("setChecked", this._id, ! this.checked); + }, + "click .delete": function () { + Meteor.call("deleteTask", this._id); + }, + "click .toggle-private": function () { + Meteor.call("setPrivate", this._id, ! this.private); + } + }); + + Accounts.ui.config({ + passwordSignupFields: "USERNAME_ONLY" + }); +} + +Meteor.methods({ + addTask: function (text) { + // Make sure the user is logged in before inserting a task + if (! Meteor.userId()) { + throw new Meteor.Error("not-authorized"); + } + + Tasks.insert({ + text: text, + createdAt: new Date(), + owner: Meteor.userId(), + username: Meteor.user().username + }); + }, + deleteTask: function (taskId) { + var task = Tasks.findOne(taskId); + if (task.private && task.owner !== Meteor.userId()) { + // If the task is private, make sure only the owner can delete it + throw new Meteor.Error("not-authorized"); + } + + Tasks.remove(taskId); + }, + setChecked: function (taskId, setChecked) { + var task = Tasks.findOne(taskId); + if (task.private && task.owner !== Meteor.userId()) { + // If the task is private, make sure only the owner can check it off + throw new Meteor.Error("not-authorized"); + } + + Tasks.update(taskId, { $set: { checked: setChecked} }); + }, + setPrivate: function (taskId, setToPrivate) { + var task = Tasks.findOne(taskId); + + // Make sure only the task owner can make a task private + if (task.owner !== Meteor.userId()) { + throw new Meteor.Error("not-authorized"); + } + + Tasks.update(taskId, { $set: { private: setToPrivate } }); + } +});