Skip to content

Instantly share code, notes, and snippets.

@eudoxia0
Last active July 28, 2016 19:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save eudoxia0/54b7f4b85daff47af938306e8f779007 to your computer and use it in GitHub Desktop.
Save eudoxia0/54b7f4b85daff47af938306e8f779007 to your computer and use it in GitHub Desktop.
React Kanban app
var AddTask = React.createClass({
getInitialState: function() {
return { text: "" };
},
handleChange: function(event) {
this.setState({text: event.target.value});
},
render: function() {
return (
<div className="add-task">
<input type="text"
value={this.state.text}
onChange={this.handleChange} />
<button type="button" onClick={this.props.addTask.bind(null, this.state.text)}>
Add
</button>
</div>
);
}
});
@app.route("/api/<int:list_id>/task", methods=["PUT"])
def add_task(list_id):
# Add a task to a list.
try:
DB.lists[list_id].tasks.append(Task(text=request.form.get("text")))
except IndexError:
return json.dumps({"status": "FAIL"})
return json.dumps({"status": "OK"})
var App = React.createClass({
render: function() {
var lists = this.props.lists.map(function(list, index) {
return (
<TaskList key={index}
id={index}
name={list.name}
tasks={list.tasks} />
);
});
return (
<div className="lists">
{lists}
</div>
);
}
});
@charset "utf-8";
body {
margin: 0;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
DB = Board([
TaskList(name="Todo",
tasks=[
Task("Write example React app"),
Task("Write documentation")
]),
TaskList(name="Done",
tasks=[
Task("Learn the basics of React")
])
])
.delete:before {
content: "×";
}
.delete {
color: red;
float: right;
font-weight: bold;
}
@app.route("/api/<int:list_id>/task/<int:task_id>", methods=["DELETE"])
def delete_task(list_id, task_id):
# Remove a task from a list.
try:
del DB.lists[list_id].tasks[task_id]
except IndexError:
return json.dumps({"status": "FAIL"})
return json.dumps({"status": "OK"})
.drop {
width: 100%;
text-align: center;
font-weight: bold;
padding: 15px 0;
}
.drop-state-none {
display: none;
}
.drop-state-dragging {
background-color: #E98B39;
}
.drop-state-hovering {
background-color: #2ECC71;
}
@app.route("/api/board/")
def get_board():
"""Return the state of the board."""
return json.dumps(DB.to_dict())
var DragDropMixin = ReactDND.DragDropMixin;
import json
from flask import Flask, request
app = Flask(__name__, static_url_path='', static_folder='.')
@app.route("/")
def index():
return app.send_static_file('index.html')
const ItemTypes = {
TASK: 'task'
};
.lists {
padding: 50px;
}
.task-list {
width: 220px;
float: left;
margin-right: 25px;
padding: 25px;
border: 1px solid #ccc;
border-radius: 5px;
}
.list-title {
margin: 0;
text-align: center;
}
.list-tasks {
padding: 0;
}
class Task(object):
"""A task."""
def __init__(self, text):
self.text = text
def to_dict(self):
return {"text": self.text}
class TaskList(object):
"""A list of Task objects."""
def __init__(self, name, tasks):
self.name = name
self.tasks = tasks
def to_dict(self):
return {
"name": self.name,
"tasks": [task.to_dict() for task in self.tasks]
}
class Board(object):
"""A collection of TaskLists."""
def __init__(self, lists):
self.lists = lists
def to_dict(self):
return {
"lists": [list.to_dict() for list in self.lists]
}
$(document).ready(function() {
$.getJSON('http://localhost:8000/api/board', function(data) {
React.render(
<App lists={data.lists} />,
document.body
);
});
});
if __name__ == "__main__":
app.run(port=8000, debug=True)
.task {
list-style-type: none;
border: 1px solid #ccc;
border-radius: 5px;
padding: 10px;
margin-bottom: 10px;
}
var Task = React.createClass({
mixins: [DragDropMixin],
statics: {
configureDragDrop: function(register) {
register(ItemTypes.TASK, {
dragSource: {
beginDrag: function(component) {
return {
item: {
text: component.props.text,
deleteTask: component.props.deleteTask
}
};
}
}
});
}
},
render: function() {
return (
<li className="task"
{...this.dragSourceFor(ItemTypes.TASK)}>
{this.props.text}
<span className="delete"
onClick={this.props.deleteTask} />
</li>
);
}
});
var TaskDropBin = React.createClass({
mixins: [DragDropMixin],
statics: {
configureDragDrop: function(register) {
register(ItemTypes.TASK, {
dropTarget: {
acceptDrop: function(component, item) {
// When a task is dropped, add it to the parent task list
item.deleteTask();
component.props.list.addTask(item.text);
}
}
});
}
},
render: function() {
const dropState = this.getDropState(ItemTypes.TASK);
var stateClass = 'none';
if (dropState.isHovering) {
stateClass = 'hovering';
} else if (dropState.isDragging) {
stateClass = 'dragging';
}
return <div className={"drop drop-state-" + stateClass}
{...this.dropTargetFor(ItemTypes.TASK)}>
Drop here
</div>;
}
});
var TaskList = React.createClass({
getInitialState: function() {
return { tasks: this.props.tasks };
},
deleteTask: function(id) {
var self = this;
$.ajax({
url: '/api/' + this.props.id + '/task/' + id,
type: 'DELETE',
success: function(result) {
var tasks = self.state.tasks;
tasks.splice(id, 1);
self.setState({ tasks: tasks });
}
});
},
addTask: function(text) {
var self = this;
$.ajax({
url: '/api/' + this.props.id + '/task',
type: 'PUT',
data: { 'text' : text },
success: function(result) {
self.setState({ tasks: self.state.tasks.concat([{ text: text }]) });
}
});
},
render: function() {
var self = this;
var task_list = this.state.tasks.map(function(task, index) {
return (
<Task key={index}
text={task.text}
deleteTask={self.deleteTask.bind(self, index)} />
);
});
return (
<div className="task-list">
<h1 className="list-title">
{this.props.name}
</h1>
<ul className="list-tasks">
{task_list}
</ul>
<TaskDropBin list={this} />
<AddTask addTask={self.addTask} />
</div>
);
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment