Add simple-todos example apps

This commit is contained in:
Sashko Stubailo
2015-09-16 11:39:21 -07:00
parent dd7227a319
commit 036ffb4e03
45 changed files with 1270 additions and 181 deletions

View File

@@ -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.

View File

@@ -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

View File

@@ -1 +0,0 @@
METEOR@1.2-rc.15

View File

@@ -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

View File

@@ -1,11 +0,0 @@
<head>
<title>react-skel</title>
</head>
<body>
<h1>Welcome to Meteor and React!</h1>
<p><a href="https://www.meteor.com/tutorials/react">Try the tutorial!</a></p>
<div id="react-container"></div>
</body>

View File

@@ -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 (
<li key={item._id}>
{item.text}
</li>
);
});
},
render() {
return (
<div>
<ul>
{this.renderItems()}
</ul>
<button onClick={this.addItem}>
Add item
</button>
</div>
)
}
});
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(<App />, document.getElementById("react-container"));
});
}
if (Meteor.isServer) {
// Code inside here will run on the server only
}

View File

@@ -1 +0,0 @@
/* CSS declarations go here */

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1 @@
METEOR@1.1.0.2

View File

@@ -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

View File

@@ -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)

View File

@@ -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;
}
}

View File

@@ -0,0 +1,7 @@
<head>
<title>Todo List</title>
</head>
<body ng-include="'todos-list.ng.html'"
ng-controller="TodosListCtrl">
</body>

View File

@@ -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 }
]
});
});
}

View File

@@ -0,0 +1,39 @@
<div class="container">
<header>
<h1>Todo List ( {{ incompleteCount() }} )</h1>
<label class="hide-completed">
<input type="checkbox" ng-model="$parent.hideCompleted"/>
Hide Completed Tasks
</label>
<meteor-include src="loginButtons"></meteor-include>
<!-- add a form below the h1 -->
<form class="new-task"
ng-submit="addTask(newTask); newTask='';"
ng-show="$root.currentUser">
<input ng-model="newTask" type="text"
name="text" placeholder="Type to add new tasks" />
</form>
</header>
<ul ng-repeat="task in tasks">
<li ng-class="{'checked': task.checked, 'private': task.private}">
<button class="delete" ng-click="deleteTask(task)">&times;</button>
<input type="checkbox" ng-checked="task.checked"
ng-click="setChecked(task)" class="toggle-checked" />
<button class="toggle-private"
ng-if="task.owner === $root.currentUser._id"
ng-click="setPrivate(task)">
{{task.private == true ? "Private" : "Public"}}
</button>
<span class="text">
<strong>{{task.username}}</strong> - {{task.text}}
</span>
</li>
</ul>
</div>

View File

@@ -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

View File

@@ -0,0 +1 @@
local

View File

@@ -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

View File

@@ -0,0 +1,2 @@
server
browser

View File

@@ -0,0 +1 @@
METEOR@1.1.0.2

View File

@@ -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

View File

@@ -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 <span ref="container" />;
}
});

View File

@@ -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 <Task
key={task._id}
task={task}
showPrivateButton={showPrivateButton} />;
});
},
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 (
<div className="container">
<header>
<h1>Todo List ({this.data.incompleteCount})</h1>
<label className="hide-completed">
<input
type="checkbox"
readOnly={true}
checked={this.state.hideCompleted}
onClick={this.toggleHideCompleted} />
Hide Completed Tasks
</label>
<AccountsUIWrapper />
{ this.data.currentUser ?
<form className="new-task" onSubmit={this.handleSubmit} >
<input
type="text"
ref="textInput"
placeholder="Type to add new tasks" />
</form> : ''
}
</header>
<ul>
{this.renderTasks()}
</ul>
</div>
);
}
});

View File

@@ -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.

View File

@@ -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)

View File

@@ -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 (
<li className={taskClassName}>
<button className="delete" onClick={this.deleteThisTask}>
&times;
</button>
<input
type="checkbox"
readOnly={true}
checked={this.props.task.checked}
onClick={this.toggleChecked} />
{ this.props.showPrivateButton ? (
<button className="toggle-private" onClick={this.togglePrivate}>
{ this.props.task.private ? "Private" : "Public" }
</button>
) : ''}
<span className="text">
<strong>{this.props.task.username}</strong>: {this.props.task.text}
</span>
</li>
);
}
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 KiB

View File

@@ -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;
}
}

View File

@@ -0,0 +1,7 @@
<head>
<title>Todo List</title>
</head>
<body>
<div id="render-target"></div>
</body>

View File

@@ -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(<App />, 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 } });
}
});

View File

@@ -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

View File

@@ -0,0 +1 @@
local

View File

@@ -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

View File

@@ -0,0 +1,2 @@
server
browser

View File

@@ -0,0 +1 @@
METEOR@1.1.0.2

View File

@@ -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

View File

@@ -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.

View File

@@ -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)

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 KiB

View File

@@ -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;
}
}

View File

@@ -0,0 +1,50 @@
<head>
<title>Todo List</title>
</head>
<body>
<div class="container">
<header>
<h1>Todo List ({{incompleteCount}})</h1>
<label class="hide-completed">
<input type="checkbox" checked="{{hideCompleted}}" />
Hide Completed Tasks
</label>
{{> loginButtons}}
{{#if currentUser}}
<form class="new-task">
<input type="text" name="text" placeholder="Type to add new tasks" />
</form>
{{/if}}
</header>
<ul>
{{#each tasks}}
{{> task}}
{{/each}}
</ul>
</div>
</body>
<template name="task">
<li class="{{#if checked}}checked{{/if}} {{#if private}}private{{/if}}">
<button class="delete">&times;</button>
<input type="checkbox" checked="{{checked}}" class="toggle-checked" />
{{#if isOwner}}
<button class="toggle-private">
{{#if private}}
Private
{{else}}
Public
{{/if}}
</button>
{{/if}}
<span class="text"><strong>{{username}}</strong> - {{text}}</span>
</li>
</template>

View File

@@ -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 } });
}
});