Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Component <-> Flux <-> Server integration #42

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
3 changes: 3 additions & 0 deletions client/app/app_router.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
/** @jsx React.DOM */

// include the es6 shim
require("es6-shim");

var React = require("react");

var Router = require("react-router");
Expand Down
17 changes: 15 additions & 2 deletions client/app/components/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,27 @@ Dispatcher.register(function(payload) {
case SurveyConstants.DELETE_SURVEY:
SurveyStore.deleteSurvey(payload.id)
break;

case SurveyConstants.RECORD_SURVEY:
SurveyStore.recordSurvey(payload.results);
break;

case SurveyConstants.LIST_SURVEYS:
SurveyStore.listSurveys();
break;

case SurveyConstants.GET_SURVEY:
SurveyStore.getSurvey(payload);
break;

}
});

var App = React.createClass({
handleChange: function() {
SurveyStore.listSurveys(function(surveys) {
//SurveyStore.listSurveys(function(surveys) {
console.debug("TODO: update app state based on surveys returned by SurveyStore.listSurveys (once it actually returns some)");
});
// });
},

componentDidMount: function() {
Expand Down
41 changes: 10 additions & 31 deletions client/app/components/list_surveys.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,46 +2,25 @@

var React = require("react");
var Promise = require('es6-promise').Promise;
var AsyncState = require('react-router').AsyncState;

var SurveyTable = require('./survey_table');
var Router = require("react-router");
var SurveyStore = require("../flux/SurveyStore");
var SurveyActions = require("../flux/SurveyActions");
var SurveyTable = require("./survey_table");

var ListSurveys = React.createClass({
mixins:[AsyncState],

statics:{
getInitialAsyncState: function(path, query, setState){
return new Promise(function(resolve, reject){
setTimeout(function () {
setState({
surveys:[
{
id: 'asd123',
uri: 'asd123',
editUri: 'ad123',
title: 'Superhero mashup',
publishedDate: new Date(),
modifiedDate: new Date(),
activity: [121,32,54,12,546]
}
]
})
resolve();
}, 100);
});
}
mixins:[SurveyStore.makeChangeMixin("surveys")],
componentDidMount: function(){
SurveyActions.list();
},

render: function(){
if(!this.state.surveys){
return <div>Loading ... </div>
}

return (
<div className='list-surveys'>
<h1>Active Surveys</h1>
<SurveyTable surveys={this.state.surveys}/>
</div>
<div>
<SurveyTable surveys={this.state.surveys} />
</div>
);
}
});
Expand Down
16 changes: 10 additions & 6 deletions client/app/components/survey_table_row.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ var Link = require('react-router').Link;

var MONTHS = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];

var formatDate = function (date) {
var formatDate = function (timestamp) {
var date = new Date(+timestamp);
return MONTHS[date.getMonth()] + ' ' + date.getDate() + ', ' + date.getFullYear();
};

Expand All @@ -18,8 +19,11 @@ var SurveyTableRow = React.createClass({
survey: React.PropTypes.shape({
id: React.PropTypes.string.isRequired,
title: React.PropTypes.string.isRequired,
publishedDate: React.PropTypes.instanceOf(Date).isRequired,
modifiedDate: React.PropTypes.instanceOf(Date).isRequired,
description: React.PropTypes.string.isRequired,
createdAt: React.PropTypes.number.isRequired,
updatedAt: React.PropTypes.number.isRequired,
// createdAt: React.PropTypes.instanceOf(Date).isRequired,
// updatedAt: React.PropTypes.instanceOf(Date).isRequired,
activity: React.PropTypes.array.isRequired
}).isRequired
},
Expand All @@ -38,10 +42,10 @@ var SurveyTableRow = React.createClass({
{survey.title}
</Link>
</td>
<td className='published'>{formatDate(survey.publishedDate)}</td>
<td className='modified'>{formatDate(survey.modifiedDate)}</td>
<td className='published'>{formatDate(survey.createdAt)}</td>
<td className='modified'>{formatDate(survey.updatedAt)}</td>
<td className='total'>{integerWithThousandsSeparator(total)}</td>
<td className='activity'></td>
<td className='activity'>...</td>
<td>
<Link to='edit' surveyId={survey.id} className="btn btn-link btn-editSurvey edit">
<i className="glyphicon glyphicon-pencil"></i>
Expand Down
58 changes: 41 additions & 17 deletions client/app/components/take_survey_ctrl.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,55 @@
/** @jsx React.DOM */
var React = require("react");
var TakeSurvey = require("./take_survey");
var mockData = require("../mock_survey_data");
var merge = require('lodash-node/modern/objects/merge');
var SurveyActions = require("../flux/SurveyActions");
var SurveyStore = require("../flux/SurveyStore");

var TakeSurveyCtrl = React.createClass({
propTypes: {
survey_id: React.PropTypes.string
},
getDefaultProps: function () {
return {
survey_id: null
};
},
getInitialState: function () {
return {
survey: mockData
};
},
mixins: [SurveyStore.makeChangeMixin("surveys")],

handleSurveySave: function(results) {
console.log('TODO: handle survey save', results);
SurveyActions.record(results);
},

// get the survey from SurveyStore if it has it
getSurvey: function(id) {
if (!this.state.surveys) {
return;
}

return this.state.surveys.find(function(item){
return item.id === id;
});
},
render:function () {
var props = merge({}, this.state.survey, {

render: function () {
var survey = this.getSurvey(this.props.params.surveyId);

if (!survey) {
return <div>Loading...</div>;
}

var props = merge({}, survey, {
onSave: this.handleSurveySave
});
return TakeSurvey(props);
},

// fetch the survey from the server when the id changes
requestSurvey: function(id) {
console.log(id);
if (id && !this.getSurvey(id)) {
SurveyActions.get(id);
}
},

componentDidMount: function(){
this.requestSurvey(this.props.params.surveyId);
},

componentWillRecieveProps: function(nextProps){
this.requestSurvey(nextProps.params.surveyId);
}
});

Expand Down
20 changes: 20 additions & 0 deletions client/app/flux/SurveyActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,26 @@ var SurveyActions = {
actionType: SurveyConstants.DELETE_SURVEY,
id: id
});
},

record: function(results) {
Dispatcher.dispatch({
actionType: SurveyConstants.RECORD_SURVEY,
results: results
});
},

list: function() {
Dispatcher.dispatch({
actionType: SurveyConstants.LIST_SURVEYS
});
},

get: function(id) {
Dispatcher.dispatch({
actionType: SurveyConstants.GET_SURVEY,
id: id
});
}
}

Expand Down
5 changes: 4 additions & 1 deletion client/app/flux/SurveyConstants.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
module.exports = {
SAVE_SURVEY: "save",
DELETE_SURVEY: "delete"
DELETE_SURVEY: "delete",
RECORD_SURVEY: "record",
LIST_SURVEYS: "list",
GET_SURVEY: "get"
}
89 changes: 76 additions & 13 deletions client/app/flux/SurveyStore.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
var EventEmitter = require("event-emitter");
var request = require("superagent");
var makeChangeMixin = require("./makeChangeMixin");
var CHANGE_EVENT = "changeEvent";

var SurveyStore = function() {
this.emitter = new EventEmitter();
this.items = [];
};

// Basic event handling functions

SurveyStore.prototype.emitChange = function() {
this.emitter.emit(CHANGE_EVENT);
};
Expand All @@ -16,29 +18,90 @@ SurveyStore.prototype.addChangeListener = function(callback) {
};

SurveyStore.prototype.removeChangeListener = function(callback) {
this.emitter.removeListener(CHANGE_EVENT, callback);
this.emitter.off(CHANGE_EVENT, callback);
};



// Survey-specific methods
SurveyStore.prototype.saveSurvey = function(survey) {
console.debug("TODO: fire XHR to persist survey, then invoke this.emitChange() after the XHR has completed.");
request.post('/api/surveys')
.send(survey)
.end(function(res){
if (res.status === 201) {
this.emitChange();
}
else {
// TODO handle showing this error to the user
console.error("saveSurvey failed with " + res.status, res.body);
}
}.bind(this));
};

SurveyStore.prototype.deleteSurvey = function(payload) {
var id = payload;
console.debug("TODO: delete survey", id);

this.emitChange();
}
};

SurveyStore.prototype.deleteSurvey = function(id) {
console.debug("TODO: delete survey", id);
SurveyStore.prototype.recordSurvey = function(results) {
console.debug("TODO: record the survey results", results);

this.emitChange();
}
};

SurveyStore.prototype.listSurveys = function() {
request.get('/api/surveys')
.accept('json')
.send()
.end(function(res){
if (res.status === 200) {
this.items = res.body.surveys;
this.emitChange();
}
else {
// TODO handle showing this error to the user
console.error("listSurveys failed with " + res.status, res.body);
}
}.bind(this));
};

SurveyStore.prototype.getSurvey = function(payload) {
var id = payload.id;
request.get('/api/surveys/' + encodeURIComponent(id))
.accept('json')
.end(function(res){
if (res.status === 404) {
// TODO handle showing this to the user
console.warn("survey " + id + " is not found");
return;
}
else if (res.status !== 200) {
console.error("error fetching survey " + id + " with status " + res.status, res.body);
return;
}

SurveyStore.prototype.listSurveys = function(callback) {
console.debug("TODO: fetch surveys from server via XHR");
// see if we have an item with the same id
var existingItemIndex = this.items.findIndex(function(item){
return item.id === id;
});

// either replace the current item or add a new one
if (existingItemIndex !== -1) {
this.items[existingItemIndex] = res.body;
}
else {
this.items.push(res.body);
}
this.emitChange();
}.bind(this));
};

SurveyStore.prototype.getState = function() {
return this.items;
};

callback([]);
}
SurveyStore.prototype.makeChangeMixin = makeChangeMixin;

// The SurveyStore is a singleton, so export only the one instance.
module.exports = new SurveyStore();
global.SurveyStore = module.exports = new SurveyStore();
29 changes: 29 additions & 0 deletions client/app/flux/makeChangeMixin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// creates a mixin which updates this.state[key] to reflect the store's state
// this function should be placed on a store's prototype
var makeChangeMixin = function(key) {
var store = this;
var mixin = {};
var prefix = "_" + this.constructor.name;
var changeHandlerName = prefix + "_change_handler__";

mixin.getInitialState = function() {
return {};
};

mixin.componentDidMount = function() {
store.addChangeListener(this[changeHandlerName]);
};

mixin.componentWillUnmount = function() {
store.removeChangeListener(this[changeHandlerName]);
};

mixin[changeHandlerName] = function() {
var update = {};
update[key] = store.getState();
this.setState(update);
};

return mixin;
};
module.exports = makeChangeMixin;
Loading