Handle task interaction like story interaction

When moving or adding tasks, instead of waiting for the user to
reselect the task reload the task each time.
This commit is contained in:
Tom Willemse 2013-07-13 21:09:40 +02:00
parent bdd07b68ca
commit 84ae7a5fd4
3 changed files with 54 additions and 15 deletions

View file

@ -46,11 +46,13 @@
(defmethod datastore-get-story ((datastore pg-datastore) id) (defmethod datastore-get-story ((datastore pg-datastore) id)
(with-connection (connection-spec datastore) (with-connection (connection-spec datastore)
(append (query (:select :* :from 'story :where (:= 'id id)) :alist) (append (query (:select :* :from 'story :where (:= 'id id)) :alist)
`((tasks . ,(query (:order-by `((tasks . ,(datastore-get-tasks-for-story datastore id))))))
(:select :* :from 'task
:where (:= 'story-id id)) (defmethod datastore-get-tasks-for-story ((datastore pg-datastore) id)
(with-connection (connection-spec datastore)
(query (:order-by (:select :* :from 'task :where (:= 'story-id id))
'priority) 'priority)
:alists)))))) :alists)))
(defmethod datastore-post-story (defmethod datastore-post-story
((datastore pg-datastore) role necessity title content reporter) ((datastore pg-datastore) role necessity title content reporter)

View file

@ -105,12 +105,13 @@
(encode-json-to-string '((status . "ok")))) (encode-json-to-string '((status . "ok"))))
403)) 403))
(define-route tasks-new ("stories/tasks/new" :method :post) (define-route tasks-new ("stories/tasks/new" :method :post
:content-type "text/json")
(if (logged-in-p) (if (logged-in-p)
(with-post-parameters ("storyId" "description") (with-post-parameters ("storyId" "description")
(post-task storyid description (post-task storyid description
(hunchentoot:session-value :username)) (hunchentoot:session-value :username))
200) (encode-json-to-string '((status . "ok"))))
403)) 403))
(define-route stories-state ("stories/state" :method :post (define-route stories-state ("stories/state" :method :post
@ -148,12 +149,13 @@
(encode-json-to-string '((status . "ok")))) (encode-json-to-string '((status . "ok"))))
403)) 403))
(define-route task-priority ("tasks/:dir" :method :post) (define-route task-priority ("tasks/:dir" :method :post
:content-type "text/json")
(if (logged-in-p) (if (logged-in-p)
(let* ((id (hunchentoot:post-parameter "id"))) (let* ((id (hunchentoot:post-parameter "id")))
(story-change-priority (story-change-priority
'task id (intern (string-upcase dir) :keyword)) 'task id (intern (string-upcase dir) :keyword))
200) (encode-json-to-string '((status . "ok"))))
403)) 403))
(define-route login-page ("login") (define-route login-page ("login")
@ -217,3 +219,9 @@
(if (logged-in-p) (if (logged-in-p)
(encode-json-to-string (get-story id)) (encode-json-to-string (get-story id))
403)) 403))
(define-route scrumli-story-tasks ("stories/:id/tasks"
:content-type "json")
(if (logged-in-p)
(encode-json-to-string (get-tasks-for-story id))
403))

View file

@ -20,10 +20,18 @@ var StoryTaskRow = React.createClass({
return {state: this.props.task.state}; return {state: this.props.task.state};
}, },
moveUp: React.autoBind(function(event) { moveUp: React.autoBind(function(event) {
$.post("/tasks/up", {'id': this.props.task.id}); $.post("/tasks/up", {'id': this.props.task.id})
.done(function (data) {
if (data.status == "ok")
this.props.onMoved(1);
}.bind(this));
}), }),
moveDown: React.autoBind(function(event) { moveDown: React.autoBind(function(event) {
$.post("/tasks/down", {'id': this.props.task.id}); $.post("/tasks/down", {'id': this.props.task.id})
.done(function (data) {
if (data.status == "ok")
this.props.onMoved(-1);
}.bind(this));
}), }),
render: function() { render: function() {
return ( return (
@ -50,7 +58,8 @@ var StoryTaskRow = React.createClass({
var StoryTaskTable = React.createClass({ var StoryTaskTable = React.createClass({
render: function() { render: function() {
var taskNodes = this.props.tasks.map(function (task) { var taskNodes = this.props.tasks.map(function (task) {
return <StoryTaskRow task={task} />; return <StoryTaskRow task={task}
onMoved={this.props.onTaskMoved} />;
}.bind(this)); }.bind(this));
return ( return (
@ -91,14 +100,32 @@ var StoryTaskForm = React.createClass({
var StoryData = React.createClass({ var StoryData = React.createClass({
handleTaskSubmit: React.autoBind(function (task) { handleTaskSubmit: React.autoBind(function (task) {
task.storyId = this.state.data.id; task.storyId = this.state.data.id;
$.post("/stories/tasks/new", task); $.post("/stories/tasks/new", task)
.done(function(data) {
if (data.status == "ok")
this.loadStoryFromServer();
}.bind(this));
}), }),
loadStoryFromServer: function() {
$.get("/stories/" + this.state.data.id)
.done(this.setData.bind(this));
},
componentWillMount: function() {
setInterval(
this.loadStoryFromServer.bind(this),
this.props.pollInterval
);
},
getInitialState: function() { getInitialState: function() {
return {data: null}; return {data: null};
}, },
setData: function(data) { setData: function(data) {
this.setState({data: null});
this.setState({data: data}); this.setState({data: data});
}, },
handleTaskMoved: React.autoBind(function(direction) {
this.loadStoryFromServer();
}),
render: function() { render: function() {
if (this.state.data) { if (this.state.data) {
return (<div> return (<div>
@ -107,7 +134,8 @@ var StoryData = React.createClass({
<div class="well"> <div class="well">
{this.state.data.content} {this.state.data.content}
</div> </div>
<StoryTaskTable tasks={this.state.data.tasks || []} /> <StoryTaskTable tasks={this.state.data.tasks || []}
onTaskMoved={this.handleTaskMoved} />
<StoryTaskForm onTaskSubmit={this.handleTaskSubmit} /> <StoryTaskForm onTaskSubmit={this.handleTaskSubmit} />
</div>); </div>);
} }
@ -283,7 +311,8 @@ var StoryPage = React.createClass({
<StoryForm onStorySubmit={this.handleStorySubmit} /> <StoryForm onStorySubmit={this.handleStorySubmit} />
</div> </div>
<div class="span6"> <div class="span6">
<StoryData ref="data" /> <StoryData ref="data"
pollInterval={this.props.pollInterval} />
</div> </div>
</div> </div>
); );