From c449b89b98b0f5c2344023c61deba0f295c9548a Mon Sep 17 00:00:00 2001 From: nicrocs Date: Tue, 15 Dec 2015 16:54:28 -0600 Subject: [PATCH 1/6] Update to use questions instead of comments --- comments.json | 12 -- public/css/base.css | 35 ++++- public/index.html | 10 +- public/scripts/example.js | 2 +- public/scripts/questions.js | 280 ++++++++++++++++++++++++++++++++++++ questions.json | 42 ++++++ server.js | 30 ++-- 7 files changed, 375 insertions(+), 36 deletions(-) delete mode 100644 comments.json create mode 100644 public/scripts/questions.js create mode 100644 questions.json diff --git a/comments.json b/comments.json deleted file mode 100644 index 7bef77ad..00000000 --- a/comments.json +++ /dev/null @@ -1,12 +0,0 @@ -[ - { - "id": 1388534400000, - "author": "Pete Hunt", - "text": "Hey there!" - }, - { - "id": 1420070400000, - "author": "Paul O’Shannessy", - "text": "React is *great*!" - } -] diff --git a/public/css/base.css b/public/css/base.css index bf382be3..aeb8814e 100644 --- a/public/css/base.css +++ b/public/css/base.css @@ -4,7 +4,7 @@ body { font-size: 15px; line-height: 1.7; margin: 0; - padding: 30px; + padding:0; } a { @@ -60,3 +60,36 @@ p, ul { ul { padding-left: 30px; } +.questionHeader { + height:100px; + width:100%; + position:fixed; + background:#fff; + padding:30px; + box-shadow: 0 4px 2px -2px #aeaeae; +} +.questionBody { + padding-top:160px; + max-width:1000px; + margin:0 auto; +} +.questionLabel { + display:block; +} +.choice label { + display:inline-block; +} +label + input[type="radio"], label + input[type="checkbox"] { + display:inline-block; + vertical-align:middle; +} +.questionForm { + width: 300px; +} +.questionForm select, .questionForm input[type="text"] { + width:100%; + display:block; + margin-bottom:10px; + padding:5px; + box-sizing:border-box; +} diff --git a/public/index.html b/public/index.html index c6494446..48b0ec0b 100644 --- a/public/index.html +++ b/public/index.html @@ -2,8 +2,7 @@ - React Tutorial - + React Questions @@ -12,11 +11,8 @@ +
- - + diff --git a/public/scripts/example.js b/public/scripts/example.js index c249427a..016b01a2 100644 --- a/public/scripts/example.js +++ b/public/scripts/example.js @@ -141,6 +141,6 @@ var CommentForm = React.createClass({ }); ReactDOM.render( - , + , document.getElementById('content') ); diff --git a/public/scripts/questions.js b/public/scripts/questions.js new file mode 100644 index 00000000..01cb55e2 --- /dev/null +++ b/public/scripts/questions.js @@ -0,0 +1,280 @@ + + +var Question = React.createClass({ + rawMarkup: function() { + var rawMarkup = marked(this.props.children.toString(), {sanitize: true}); + return { __html: rawMarkup }; + }, + + renderShortAnswer: function() { + return( +
+ + +
+ ); + + }, + renderLongAnswer: function() { + return( +
+ + +
+ ); + + }, + renderSingleChoice: function() { + var choiceNodes = this.props.choices.map(function(choice) { + return ( + + + ); + }); + return ( +
+
+ + {this.props.label} + + {choiceNodes} +
+
+ ); + + }, + renderMultipleChoice: function() { + var choiceNodes = this.props.choices.map(function(choice) { + return ( + + + ); + }); + return ( +
+
+ + {this.props.label} + + {choiceNodes} +
+
+ ); + }, + render: function() { + var answerTypes = { + short_answer: this.renderShortAnswer, + long_answer: this.renderLongAnswer, + single_choice: this.renderSingleChoice, + multiple_choice: this.renderMultipleChoice + }; + return answerTypes[this.props.type](); + } +}); + +var Choice = React.createClass({ + render: function () { + return( +
+ + +
+ ); + } +}); + +var QuestionBox = React.createClass({ + loadQuestionsFromServer: function() { + $.ajax({ + url: this.props.url, + dataType: 'json', + cache: false, + success: function(data) { + this.setState({data: data}); + }.bind(this), + error: function(xhr, status, err) { + console.error(this.props.url, status, err.toString()); + }.bind(this) + }); + }, + handleQuestionSubmit: function(question) { + var questions = this.state.data; + // Optimistically set an id on the new comment. It will be replaced by an + // id generated by the server. In a production application you would likely + // not use Date.now() for this and would have a more robust system in place. + question.id = Date.now(); + var newQuestions = questions.concat([question]); + this.setState({data: newQuestions}); + $.ajax({ + url: this.props.url, + dataType: 'json', + type: 'POST', + data: question, + success: function(data) { + this.setState({data: data}); + }.bind(this), + error: function(xhr, status, err) { + this.setState({data: questions}); + console.error(this.props.url, status, err.toString()); + }.bind(this) + }); + }, + //TODO: live preview + handleQuestionPreview: function(question) { + this.setState({previewData:question}); + }, + getInitialState: function() { + return {data: []}; + }, + componentDidMount: function() { + this.loadQuestionsFromServer(); + setInterval(this.loadQuestionsFromServer, this.props.pollInterval); + }, + render: function() { + return ( +
+
+ + +
+
+

Questions

+ +
+
+ ); + } +}); +//TODO: live preview +var QuestionPreview = React.createClass({ + getInitalState: function() { + console.log(this); + return {data:[]}; + }, + render: function() { + return ( + + + ); + } +}); + +var QuestionList = React.createClass({ + render: function() { + var questionNodes = this.props.data.map(function(question) { + if (question.choices) { + return ( + + + ); + } else { + return ( + + + ); + } + + }); + return ( +
+ {questionNodes} +
+ ); + } +}); + +var QuestionForm = React.createClass({ + getInitialState: function() { + return {label: '', text: '', type:'', showChoiceText:false}; + + }, + handleLabelChange: function(e) { + this.setState({label: e.target.value}); + }, + handleTypeChange: function(e) { + this.setState({type: e.target.value}); + if (e.target.value === "single_choice" || e.target.value === "multiple_choice") { + this.setState({showChoiceText: true}) + } else { + this.setState({showChoiceText: false}) + } + }, + handleTextChange: function(e) { + this.setState({text:e.target.value}); + this.addToChoices(e.target.value); + }, + addToChoices: function(text) { + var choices = []; + choices.push({id:choices.length,text:text.trim()}); + this.setState({choices: choices}); + }, + handleSubmit: function(e) { + e.preventDefault(); + var type = this.state.type.trim(); + var label = this.state.label.trim(); + var choices = this.state.choices; + if (!type || !label) { + return; + } + this.props.onQuestionSubmit({type: type, label: label, choices: choices}); + this.setState({type: '',label: '', text: ''}); + }, + //TODO: live preview + handlePreview: function(e) { + e.preventDefault(); + var type = this.state.type.trim(); + var label = this.state.label.trim(); + var text = this.state.text.trim(); + this.props.onQuestionChange({type:type, label:label, text:text}); + }, + showChoiceText: function() { + var choiceTextInput = ; + return choiceTextInput; + }, + //TODO: add button for multiple choice text inputs + showAddChoiceButton: function() { + var addChoiceButton = ; + return addChoiceButton; + }, + render: function() { + return ( +
+ + +
+ {this.state.showChoiceText ? this.showChoiceText() : null} +
+ +
+ ); + } +}); + +ReactDOM.render( + , + document.getElementById('content') +); diff --git a/questions.json b/questions.json new file mode 100644 index 00000000..8c4f7ff1 --- /dev/null +++ b/questions.json @@ -0,0 +1,42 @@ +[ + { + "id": 1388534400000, + "type": "short_answer", + "label": "Do you like JavaScript?", + "text": "Of Course!" + }, + { + "id": 1420070400000, + "type": "long_answer", + "label": "What do you like about it?", + "text": "React is *great*" + }, + { + "id": 1450154197569, + "type": "long_answer", + "label": "test", + "text": "long answer" + }, + { + "id": 1450218121596, + "type": "single_choice", + "label": "do you like javscript?", + "choices": [ + { + "id": "0", + "text": "Ye" + } + ] + }, + { + "id": 1450218298431, + "type": "single_choice", + "label": "what about react?", + "choices": [ + { + "id": "0", + "text": "Maybe" + } + ] + } +] diff --git a/server.js b/server.js index ac87898a..2b7f7f36 100644 --- a/server.js +++ b/server.js @@ -16,7 +16,7 @@ var express = require('express'); var bodyParser = require('body-parser'); var app = express(); -var COMMENTS_FILE = path.join(__dirname, 'comments.json'); +var QUESTIONS_FILE = path.join(__dirname, 'questions.json'); app.set('port', (process.env.PORT || 3000)); @@ -24,8 +24,8 @@ app.use('/', express.static(path.join(__dirname, 'public'))); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({extended: true})); -app.get('/api/comments', function(req, res) { - fs.readFile(COMMENTS_FILE, function(err, data) { +app.get('/api/questions', function(req, res) { + fs.readFile(QUESTIONS_FILE, function(err, data) { if (err) { console.error(err); process.exit(1); @@ -35,29 +35,29 @@ app.get('/api/comments', function(req, res) { }); }); -app.post('/api/comments', function(req, res) { - fs.readFile(COMMENTS_FILE, function(err, data) { +app.post('/api/questions', function(req, res) { + console.log(req.body); + fs.readFile(QUESTIONS_FILE, function(err, data) { if (err) { console.error(err); process.exit(1); } - var comments = JSON.parse(data); - // NOTE: In a real implementation, we would likely rely on a database or - // some other approach (e.g. UUIDs) to ensure a globally unique id. We'll - // treat Date.now() as unique-enough for our purposes. - var newComment = { + var questions = JSON.parse(data); + + var newQuestion = { id: Date.now(), - author: req.body.author, - text: req.body.text, + type: req.body.type, + label: req.body.label, + choices: req.body.choices }; - comments.push(newComment); - fs.writeFile(COMMENTS_FILE, JSON.stringify(comments, null, 4), function(err) { + questions.push(newQuestion); + fs.writeFile(QUESTIONS_FILE, JSON.stringify(questions, null, 4), function(err) { if (err) { console.error(err); process.exit(1); } res.setHeader('Cache-Control', 'no-cache'); - res.json(comments); + res.json(questions); }); }); }); From 89890397a6f0e512f45b08433057e9904067a013 Mon Sep 17 00:00:00 2001 From: nicrocs Date: Tue, 15 Dec 2015 17:15:41 -0600 Subject: [PATCH 2/6] updated readme and removing server files that I don't want to support --- README.md | 37 ++-------------- requirements.txt | 1 - server.go | 110 ----------------------------------------------- server.php | 52 ---------------------- server.pl | 36 ---------------- server.py | 36 ---------------- server.rb | 46 -------------------- 7 files changed, 4 insertions(+), 314 deletions(-) delete mode 100644 requirements.txt delete mode 100644 server.go delete mode 100644 server.php delete mode 100644 server.pl delete mode 100644 server.py delete mode 100644 server.rb diff --git a/README.md b/README.md index 4862f5df..7ce01bf2 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ -[![Deploy](https://www.herokucdn.com/deploy/button.png)](https://heroku.com/deploy) -# React Tutorial -This is the React comment box example from [the React tutorial](http://facebook.github.io/react/docs/tutorial.html). +# React Question + +This is questions/form builder example based on [the React tutorial](http://facebook.github.io/react/docs/tutorial.html). ## To use -There are several simple server implementations included. They all serve static files from `public/` and handle requests to `/api/comments` to fetch or add data. Start a server with one of the following: +There are several simple server implementations included. They all serve static files from `public/` and handle requests to `/api/questions` to fetch or add data. Start a server with Node: ### Node @@ -15,35 +15,6 @@ npm install node server.js ``` -### Python - -```sh -pip install -r requirements.txt -python server.py -``` - -### Ruby -```sh -ruby server.rb -``` - -### PHP -```sh -php server.php -``` - -### Go -```sh -go run server.go -``` - -### Perl - -```sh -cpan Mojolicious -perl server.pl -``` - And visit . Try opening multiple tabs! ## Changing the port diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 632a1efa..00000000 --- a/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -Flask==0.10.1 diff --git a/server.go b/server.go deleted file mode 100644 index 2224328d..00000000 --- a/server.go +++ /dev/null @@ -1,110 +0,0 @@ -/** - * This file provided by Facebook is for non-commercial testing and evaluation - * purposes only. Facebook reserves all rights not expressly granted. - * - * 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 - * FACEBOOK 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. - */ - -package main - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "io/ioutil" - "log" - "net/http" - "os" - "sync" - "time" -) - -type comment struct { - ID int64 `json:"id"` - Author string `json:"author"` - Text string `json:"text"` -} - -const dataFile = "./comments.json" - -var commentMutex = new(sync.Mutex) - -// Handle comments -func handleComments(w http.ResponseWriter, r *http.Request) { - // Since multiple requests could come in at once, ensure we have a lock - // around all file operations - commentMutex.Lock() - defer commentMutex.Unlock() - - // Stat the file, so we can find its current permissions - fi, err := os.Stat(dataFile) - if err != nil { - http.Error(w, fmt.Sprintf("Unable to stat the data file (%s): %s", dataFile, err), http.StatusInternalServerError) - return - } - - // Read the comments from the file. - commentData, err := ioutil.ReadFile(dataFile) - if err != nil { - http.Error(w, fmt.Sprintf("Unable to read the data file (%s): %s", dataFile, err), http.StatusInternalServerError) - return - } - - switch r.Method { - case "POST": - // Decode the JSON data - var comments []comment - if err := json.Unmarshal(commentData, &comments); err != nil { - http.Error(w, fmt.Sprintf("Unable to Unmarshal comments from data file (%s): %s", dataFile, err), http.StatusInternalServerError) - return - } - - // Add a new comment to the in memory slice of comments - comments = append(comments, comment{ID: time.Now().UnixNano() / 1000000, Author: r.FormValue("author"), Text: r.FormValue("text")}) - - // Marshal the comments to indented json. - commentData, err = json.MarshalIndent(comments, "", " ") - if err != nil { - http.Error(w, fmt.Sprintf("Unable to marshal comments to json: %s", err), http.StatusInternalServerError) - return - } - - // Write out the comments to the file, preserving permissions - err := ioutil.WriteFile(dataFile, commentData, fi.Mode()) - if err != nil { - http.Error(w, fmt.Sprintf("Unable to write comments to data file (%s): %s", dataFile, err), http.StatusInternalServerError) - return - } - - w.Header().Set("Content-Type", "application/json") - w.Header().Set("Cache-Control", "no-cache") - io.Copy(w, bytes.NewReader(commentData)) - - case "GET": - w.Header().Set("Content-Type", "application/json") - w.Header().Set("Cache-Control", "no-cache") - // stream the contents of the file to the response - io.Copy(w, bytes.NewReader(commentData)) - - default: - // Don't know the method, so error - http.Error(w, fmt.Sprintf("Unsupported method: %s", r.Method), http.StatusMethodNotAllowed) - } -} - -func main() { - port := os.Getenv("PORT") - if port == "" { - port = "3000" - } - http.HandleFunc("/api/comments", handleComments) - http.Handle("/", http.FileServer(http.Dir("./public"))) - log.Println("Server started: http://localhost:" + port) - log.Fatal(http.ListenAndServe(":"+port, nil)) -} diff --git a/server.php b/server.php deleted file mode 100644 index 6b8880c8..00000000 --- a/server.php +++ /dev/null @@ -1,52 +0,0 @@ - round(microtime(true) * 1000), - 'author' => $_POST['author'], - 'text' => $_POST['text'] - ]; - - $comments = json_encode($commentsDecoded, JSON_PRETTY_PRINT); - file_put_contents('comments.json', $comments); - } - header('Content-Type: application/json'); - header('Cache-Control: no-cache'); - echo $comments; - } else { - return false; - } -} diff --git a/server.pl b/server.pl deleted file mode 100644 index 517e1621..00000000 --- a/server.pl +++ /dev/null @@ -1,36 +0,0 @@ -# This file provided by Facebook is for non-commercial testing and evaluation -# purposes only. Facebook reserves all rights not expressly granted. -# -# 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 -# FACEBOOK 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. - -use Time::HiRes qw(gettimeofday); -use Mojolicious::Lite; -use Mojo::JSON qw(encode_json decode_json); - -app->static->paths->[0] = './public'; - -any '/' => sub { $_[0]->reply->static('index.html') }; - -any [qw(GET POST)] => '/api/comments' => sub { - my $self = shift; - my $comments = decode_json (do { local(@ARGV,$/) = 'comments.json';<> }); - - if ($self->req->method eq 'POST') - { - push @$comments, { - id => int(gettimeofday * 1000), - author => $self->param('author'), - text => $self->param('text'), - }; - open my $FILE, '>', 'comments.json'; - print $FILE encode_json($comments); - } - $self->render(json => $comments); -}; -my $port = $ENV{PORT} || 3000; -app->start('daemon', '-l', "http://*:$port"); diff --git a/server.py b/server.py deleted file mode 100644 index 451fbacd..00000000 --- a/server.py +++ /dev/null @@ -1,36 +0,0 @@ -# This file provided by Facebook is for non-commercial testing and evaluation -# purposes only. Facebook reserves all rights not expressly granted. -# -# 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 -# FACEBOOK 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. - -import json -import os -import time -from flask import Flask, Response, request - -app = Flask(__name__, static_url_path='', static_folder='public') -app.add_url_rule('/', 'root', lambda: app.send_static_file('index.html')) - -@app.route('/api/comments', methods=['GET', 'POST']) -def comments_handler(): - - with open('comments.json', 'r') as file: - comments = json.loads(file.read()) - - if request.method == 'POST': - newComment = request.form.to_dict() - newComment['id'] = int(time.time() * 1000) - comments.append(newComment) - - with open('comments.json', 'w') as file: - file.write(json.dumps(comments, indent=4, separators=(',', ': '))) - - return Response(json.dumps(comments), mimetype='application/json', headers={'Cache-Control': 'no-cache'}) - -if __name__ == '__main__': - app.run(port=int(os.environ.get("PORT",3000))) diff --git a/server.rb b/server.rb deleted file mode 100644 index eed401ae..00000000 --- a/server.rb +++ /dev/null @@ -1,46 +0,0 @@ -# This file provided by Facebook is for non-commercial testing and evaluation -# purposes only. Facebook reserves all rights not expressly granted. -# -# 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 -# FACEBOOK 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. - -require 'webrick' -require 'json' - -port = ENV['PORT'].nil? ? 3000 : ENV['PORT'].to_i - -puts "Server started: http://localhost:#{port}/" - -root = File.expand_path './public' -server = WEBrick::HTTPServer.new Port: port, DocumentRoot: root - -server.mount_proc '/api/comments' do |req, res| - comments = JSON.parse(File.read('./comments.json', encoding: 'UTF-8')) - - if req.request_method == 'POST' - # Assume it's well formed - comment = { id: (Time.now.to_f * 1000).to_i } - req.query.each do |key, value| - comment[key] = value.force_encoding('UTF-8') - end - comments << comment - File.write( - './comments.json', - JSON.pretty_generate(comments, indent: ' '), - encoding: 'UTF-8' - ) - end - - # always return json - res['Content-Type'] = 'application/json' - res['Cache-Control'] = 'no-cache' - res.body = JSON.generate(comments) -end - -trap('INT') { server.shutdown } - -server.start From 5e9728db94c32e9e1089b7244b1ae91fcd5a177c Mon Sep 17 00:00:00 2001 From: nicrocs Date: Tue, 15 Dec 2015 21:35:13 -0600 Subject: [PATCH 3/6] adding multiple choices --- public/css/base.css | 19 ++++------ public/scripts/questions.js | 70 ++++++++++++++++++------------------- 2 files changed, 42 insertions(+), 47 deletions(-) diff --git a/public/css/base.css b/public/css/base.css index aeb8814e..dd355d28 100644 --- a/public/css/base.css +++ b/public/css/base.css @@ -4,7 +4,7 @@ body { font-size: 15px; line-height: 1.7; margin: 0; - padding:0; + padding:30px; } a { @@ -60,18 +60,13 @@ p, ul { ul { padding-left: 30px; } -.questionHeader { - height:100px; - width:100%; - position:fixed; - background:#fff; - padding:30px; - box-shadow: 0 4px 2px -2px #aeaeae; +.questionFormColumn { + width:25%; + float:left; } -.questionBody { - padding-top:160px; - max-width:1000px; - margin:0 auto; +.questionListColumn { + width:75%; + float:right; } .questionLabel { display:block; diff --git a/public/scripts/questions.js b/public/scripts/questions.js index 01cb55e2..506cae76 100644 --- a/public/scripts/questions.js +++ b/public/scripts/questions.js @@ -132,16 +132,15 @@ var QuestionBox = React.createClass({ }, componentDidMount: function() { this.loadQuestionsFromServer(); - setInterval(this.loadQuestionsFromServer, this.props.pollInterval); }, render: function() { return (
-
+
-
+

Questions

@@ -186,22 +185,43 @@ var QuestionList = React.createClass({ ); } }); - +var ChoiceInput = React.createClass({ + getInitalState: function() { + return {choices:0} + }, + addChoiceInput: function() { + this.setState({choices:this.state.choices++}) + }, + render:function() { + console.log(this); + var choices = []; + if (this.props.data.type === "single_choice" || this.props.data.type === "multiple_choice"){ + for (var i = 0; i < this.state.choices; i++) { + choices.push() + } + choices.push() + } + return ( +
+ {choices} +
+ ); + } +}); var QuestionForm = React.createClass({ getInitialState: function() { - return {label: '', text: '', type:'', showChoiceText:false}; - + return {label: '', text: '', type:''}; }, handleLabelChange: function(e) { this.setState({label: e.target.value}); }, handleTypeChange: function(e) { - this.setState({type: e.target.value}); - if (e.target.value === "single_choice" || e.target.value === "multiple_choice") { - this.setState({showChoiceText: true}) - } else { - this.setState({showChoiceText: false}) - } + this.setState({type:e.target.value}) }, handleTextChange: function(e) { this.setState({text:e.target.value}); @@ -221,7 +241,7 @@ var QuestionForm = React.createClass({ return; } this.props.onQuestionSubmit({type: type, label: label, choices: choices}); - this.setState({type: '',label: '', text: ''}); + this.setState({type: '',label: ''}); }, //TODO: live preview handlePreview: function(e) { @@ -231,24 +251,6 @@ var QuestionForm = React.createClass({ var text = this.state.text.trim(); this.props.onQuestionChange({type:type, label:label, text:text}); }, - showChoiceText: function() { - var choiceTextInput = ; - return choiceTextInput; - }, - //TODO: add button for multiple choice text inputs - showAddChoiceButton: function() { - var addChoiceButton = ; - return addChoiceButton; - }, render: function() { return (
@@ -265,9 +267,7 @@ var QuestionForm = React.createClass({ value={this.state.label} onChange={this.handleLabelChange} /> -
- {this.state.showChoiceText ? this.showChoiceText() : null} -
+
); @@ -275,6 +275,6 @@ var QuestionForm = React.createClass({ }); ReactDOM.render( - , + , document.getElementById('content') ); From e9e616f00642893daa746fd087c7ef6bab51e091 Mon Sep 17 00:00:00 2001 From: nicrocs Date: Tue, 15 Dec 2015 21:52:00 -0600 Subject: [PATCH 4/6] Updating so that the choices that aren't working are removed --- public/scripts/questions.js | 9 +++------ questions.json | 28 +++++++++++++++++++++++++--- server.js | 1 - 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/public/scripts/questions.js b/public/scripts/questions.js index 506cae76..aeda8dad 100644 --- a/public/scripts/questions.js +++ b/public/scripts/questions.js @@ -80,7 +80,7 @@ var Choice = React.createClass({ render: function () { return(
- +
); @@ -151,7 +151,6 @@ var QuestionBox = React.createClass({ //TODO: live preview var QuestionPreview = React.createClass({ getInitalState: function() { - console.log(this); return {data:[]}; }, render: function() { @@ -185,6 +184,7 @@ var QuestionList = React.createClass({ ); } }); +//Todo: implement multiple choice questions var ChoiceInput = React.createClass({ getInitalState: function() { return {choices:0} @@ -193,7 +193,6 @@ var ChoiceInput = React.createClass({ this.setState({choices:this.state.choices++}) }, render:function() { - console.log(this); var choices = []; if (this.props.data.type === "single_choice" || this.props.data.type === "multiple_choice"){ for (var i = 0; i < this.state.choices; i++) { @@ -251,6 +250,7 @@ var QuestionForm = React.createClass({ var text = this.state.text.trim(); this.props.onQuestionChange({type:type, label:label, text:text}); }, + //TODO: implement multiple choice questions render: function() { return (
@@ -258,8 +258,6 @@ var QuestionForm = React.createClass({ - - -
); diff --git a/questions.json b/questions.json index 8c4f7ff1..2ffe34d9 100644 --- a/questions.json +++ b/questions.json @@ -24,19 +24,41 @@ "choices": [ { "id": "0", - "text": "Ye" + "text": "Yes" + }, + { + "id": "1", + "text": "No" } ] }, { "id": 1450218298431, - "type": "single_choice", + "type": "multiple_choice", "label": "what about react?", "choices": [ { "id": "0", "text": "Maybe" + }, + { + "id": "1", + "text": "Sometimes" + }, + { + "id": "2", + "text": "None of the above" } ] + }, + { + "id": 1450237712664, + "type": "short_answer", + "label": "Where are you from?" + }, + { + "id": 1450237724241, + "type": "long_answer", + "label": "What do you know?" } -] +] \ No newline at end of file diff --git a/server.js b/server.js index 2b7f7f36..76e62fcc 100644 --- a/server.js +++ b/server.js @@ -36,7 +36,6 @@ app.get('/api/questions', function(req, res) { }); app.post('/api/questions', function(req, res) { - console.log(req.body); fs.readFile(QUESTIONS_FILE, function(err, data) { if (err) { console.error(err); From 94eaab1008f9978cfdf6b21b72b43c9278cab62d Mon Sep 17 00:00:00 2001 From: nicrocs Date: Sun, 21 Feb 2016 13:19:25 -0600 Subject: [PATCH 5/6] refactored to make more modular --- package.json | 27 +- public/css/base.css | 9 + public/index.html | 7 +- public/scripts/components/Choice.js | 11 + public/scripts/components/ChoiceInput.js | 34 +++ public/scripts/components/LongAnswer.js | 17 ++ public/scripts/components/MultipleChoice.js | 25 ++ public/scripts/components/Question.js | 24 ++ public/scripts/components/QuestionForm.js | 89 ++++++ public/scripts/components/QuestionList.js | 31 ++ public/scripts/components/QuestionPreview.js | 27 ++ public/scripts/components/ShortAnswer.js | 17 ++ public/scripts/components/singleChoice.js | 26 ++ public/scripts/example.js | 146 --------- public/scripts/questions.js | 293 ++++--------------- questions.json | 127 ++++++-- server.js | 37 ++- webpack.config.js | 32 ++ 18 files changed, 532 insertions(+), 447 deletions(-) create mode 100644 public/scripts/components/Choice.js create mode 100644 public/scripts/components/ChoiceInput.js create mode 100644 public/scripts/components/LongAnswer.js create mode 100644 public/scripts/components/MultipleChoice.js create mode 100644 public/scripts/components/Question.js create mode 100644 public/scripts/components/QuestionForm.js create mode 100644 public/scripts/components/QuestionList.js create mode 100644 public/scripts/components/QuestionPreview.js create mode 100644 public/scripts/components/ShortAnswer.js create mode 100644 public/scripts/components/singleChoice.js delete mode 100644 public/scripts/example.js create mode 100644 webpack.config.js diff --git a/package.json b/package.json index e7491981..1f4bf187 100644 --- a/package.json +++ b/package.json @@ -1,16 +1,26 @@ { - "name": "react-tutorial", + "name": "react-questions", "version": "0.0.0", - "description": "Code from the React tutorial.", + "description": "Example code questions app.", "main": "server.js", "dependencies": { "body-parser": "^1.4.3", "express": "^4.4.5" }, - "devDependencies": {}, + "devDependencies": { + "babel-core": "^6.5.2", + "babel-loader": "^6.2.3", + "babel-preset-es2015": "^6.5.0", + "babel-preset-react": "^6.5.0", + "jquery": "^2.2.0", + "react": "^0.14.7", + "react-dom": "^0.14.7", + "webpack": "^1.12.13" + }, "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "start": "node server.js" + "build": "webpack --watch --colors --progress", + "start": "node server.js" }, "repository": { "type": "git", @@ -22,12 +32,9 @@ "comment", "example" ], - "author": "petehunt", + "author": "nicrocs", "bugs": { - "url": "https://github.com/reactjs/react-tutorial/issues" + "url": "https://github.com/nicrocs" }, - "homepage": "https://github.com/reactjs/react-tutorial", - "engines" : { - "node" : "0.12.x" - } + "homepage": "https://github.com/nicrocs/react-tutorial", } diff --git a/public/css/base.css b/public/css/base.css index dd355d28..60942bd2 100644 --- a/public/css/base.css +++ b/public/css/base.css @@ -88,3 +88,12 @@ label + input[type="radio"], label + input[type="checkbox"] { padding:5px; box-sizing:border-box; } +.question { + position:relative; + margin-bottom:20px; +} +.remove { + position:absolute; + right:0; + top:-5px; +} diff --git a/public/index.html b/public/index.html index 48b0ec0b..9bf89507 100644 --- a/public/index.html +++ b/public/index.html @@ -4,15 +4,10 @@ React Questions - - - - -
- + diff --git a/public/scripts/components/Choice.js b/public/scripts/components/Choice.js new file mode 100644 index 00000000..07942ef3 --- /dev/null +++ b/public/scripts/components/Choice.js @@ -0,0 +1,11 @@ +import React from 'react'; +var Choice = function(props) { + return ( +
+ + +
+ ); +}; + +export default Choice; diff --git a/public/scripts/components/ChoiceInput.js b/public/scripts/components/ChoiceInput.js new file mode 100644 index 00000000..5f3657ca --- /dev/null +++ b/public/scripts/components/ChoiceInput.js @@ -0,0 +1,34 @@ +import React from 'react'; +var ChoiceInput = React.createClass({ + addChoiceInput: function() { + this.props.addChoice(); + }, + handleTextChange: function(e, key) { + this.props.choiceTextChange(e.target.value, key); + }, + removeChoice: function(e, key) { + this.props.removeChoice(key); + }, + render:function() { + var choices = this.props.choices.map(function(choice) { + return ( +
+ this.handleTextChange(e, choice.id)} + /> + +
) + }, this); + return ( +
+ {choices} + +
+ ); + } +}); + +export default ChoiceInput; diff --git a/public/scripts/components/LongAnswer.js b/public/scripts/components/LongAnswer.js new file mode 100644 index 00000000..2eabc569 --- /dev/null +++ b/public/scripts/components/LongAnswer.js @@ -0,0 +1,17 @@ +import React from 'react'; +var LongAnswer = function(props) { + return( +
+ + + { props.id && + + } +
+ ); + +}; + +export default LongAnswer; diff --git a/public/scripts/components/MultipleChoice.js b/public/scripts/components/MultipleChoice.js new file mode 100644 index 00000000..2b4e55e8 --- /dev/null +++ b/public/scripts/components/MultipleChoice.js @@ -0,0 +1,25 @@ +import React from 'react'; +import Choice from './Choice.js'; +var MultipleChoice = function(props) { + var choiceNodes = props.choices.map(function(choice) { + return ( + + + ); + }); + return ( +
+
+ + {props.label} + + {choiceNodes} +
+ { props.id && + + } +
+ ); +}; + +export default MultipleChoice; diff --git a/public/scripts/components/Question.js b/public/scripts/components/Question.js new file mode 100644 index 00000000..4bc03ea8 --- /dev/null +++ b/public/scripts/components/Question.js @@ -0,0 +1,24 @@ +import React from 'react'; +import ShortAnswer from './ShortAnswer.js'; +import LongAnswer from './LongAnswer.js'; +import MultipleChoice from './MultipleChoice.js'; +import SingleChoice from './singleChoice.js'; + +var Question = React.createClass({ + render: function() { + var answerTypes = { + short_answer: ShortAnswer, + long_answer: LongAnswer, + single_choice: SingleChoice, + multiple_choice: MultipleChoice + }; + if (this.props.type) { + return answerTypes[this.props.type](this.props); + } else { + return answerTypes['short_answer'](this.props); + } + + } +}); + +export default Question; diff --git a/public/scripts/components/QuestionForm.js b/public/scripts/components/QuestionForm.js new file mode 100644 index 00000000..75953807 --- /dev/null +++ b/public/scripts/components/QuestionForm.js @@ -0,0 +1,89 @@ +import React from 'react'; +import ChoiceInput from './ChoiceInput'; +import QuestionPreview from './QuestionPreview'; +var QuestionForm = React.createClass({ + getInitialState: function() { + return {question: {}}; + }, + handleLabelChange: function(event) { + this.state.question.label = event.target.value; + this.setState({question: this.state.question}); + }, + handleTypeChange: function(e) { + this.state.question.type = e.target.value; + + if (e.target.value === "single_choice" || e.target.value === "multiple_choice") { + this.state.question.choices = [{ + id:0, + text:"" + }] + } + + this.setState({question:this.state.question}); + }, + handleChoices: function(choiceValue, id){ + var choices = this.state.question.choices.map(function(choice){ + if (choice.id === id) { + return {id: id, text:choiceValue}; + } else { + return choice + } + }); + this.state.question.choices = choices; + this.setState({question: this.state.question}); + }, + addChoice: function() { + var choiceId = this.state.question.choices.length; + this.state.question.choices.push({ + id:choiceId, + text:"" + }); + this.setState({question: this.state.question}) + }, + removeChoice: function(id) { + var choices = this.state.question.choices.filter(function(choice){ + return choice.id !== id; + }); + this.state.question.choices = choices; + this.setState({question: this.state.question}); + }, + handleSubmit: function(e) { + e.preventDefault(); + this.props.QuestionSubmit(this.state.question); + e.target.reset(); + this.setState({question:{}}); + }, + + render: function() { + var choices = (this.state.question.type === "single_choice" || this.state.question.type === "multiple_choice"); + return ( +
+ + + {choices && + + } + + + + ); + } +}); +export default QuestionForm; diff --git a/public/scripts/components/QuestionList.js b/public/scripts/components/QuestionList.js new file mode 100644 index 00000000..0ea3b082 --- /dev/null +++ b/public/scripts/components/QuestionList.js @@ -0,0 +1,31 @@ +import React from 'react'; +import Question from './Question.js'; + +var QuestionList = React.createClass({ + removeQuestion: function(event, id) { + this.props.removeQuestion(id); + }, + render: function() { + var questionNodes = this.props.data.map(function(question) { + return ( + this.removeQuestion(e, question.id)} + > + + ); + + }, this); + return ( +
+ {questionNodes} +
+ ); + } +}); + +export default QuestionList; diff --git a/public/scripts/components/QuestionPreview.js b/public/scripts/components/QuestionPreview.js new file mode 100644 index 00000000..82ad2834 --- /dev/null +++ b/public/scripts/components/QuestionPreview.js @@ -0,0 +1,27 @@ +import React from 'react'; +import Question from './Question.js'; +var QuestionPreview = React.createClass({ + render: function() { + + return ( +
+

Question Preview

+ + +
+ + ); + } +}); + +export default QuestionPreview; + +var divStyle = { + marginTop: 20, + padding:10, + border: "1px dashed #333" +}; diff --git a/public/scripts/components/ShortAnswer.js b/public/scripts/components/ShortAnswer.js new file mode 100644 index 00000000..175490d7 --- /dev/null +++ b/public/scripts/components/ShortAnswer.js @@ -0,0 +1,17 @@ +import React from 'react'; +var ShortAnswer = function(props) { + return( +
+ + + { props.id && + + } +
+ ); + +}; + +export default ShortAnswer; diff --git a/public/scripts/components/singleChoice.js b/public/scripts/components/singleChoice.js new file mode 100644 index 00000000..37dfc232 --- /dev/null +++ b/public/scripts/components/singleChoice.js @@ -0,0 +1,26 @@ +import React from 'react'; +import Choice from './Choice.js'; +var SingleChoice = function(props) { + var choiceNodes = props.choices.map(function(choice) { + return ( + + + ); + }); + return ( +
+
+ + {props.label} + + {choiceNodes} +
+ { props.id && + + } +
+ ); + +}; + +export default SingleChoice; diff --git a/public/scripts/example.js b/public/scripts/example.js deleted file mode 100644 index 016b01a2..00000000 --- a/public/scripts/example.js +++ /dev/null @@ -1,146 +0,0 @@ -/** - * This file provided by Facebook is for non-commercial testing and evaluation - * purposes only. Facebook reserves all rights not expressly granted. - * - * 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 - * FACEBOOK 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. - */ - -var Comment = React.createClass({ - rawMarkup: function() { - var rawMarkup = marked(this.props.children.toString(), {sanitize: true}); - return { __html: rawMarkup }; - }, - - render: function() { - return ( -
-

- {this.props.author} -

- -
- ); - } -}); - -var CommentBox = React.createClass({ - loadCommentsFromServer: function() { - $.ajax({ - url: this.props.url, - dataType: 'json', - cache: false, - success: function(data) { - this.setState({data: data}); - }.bind(this), - error: function(xhr, status, err) { - console.error(this.props.url, status, err.toString()); - }.bind(this) - }); - }, - handleCommentSubmit: function(comment) { - var comments = this.state.data; - // Optimistically set an id on the new comment. It will be replaced by an - // id generated by the server. In a production application you would likely - // not use Date.now() for this and would have a more robust system in place. - comment.id = Date.now(); - var newComments = comments.concat([comment]); - this.setState({data: newComments}); - $.ajax({ - url: this.props.url, - dataType: 'json', - type: 'POST', - data: comment, - success: function(data) { - this.setState({data: data}); - }.bind(this), - error: function(xhr, status, err) { - this.setState({data: comments}); - console.error(this.props.url, status, err.toString()); - }.bind(this) - }); - }, - getInitialState: function() { - return {data: []}; - }, - componentDidMount: function() { - this.loadCommentsFromServer(); - setInterval(this.loadCommentsFromServer, this.props.pollInterval); - }, - render: function() { - return ( -
-

Comments

- - -
- ); - } -}); - -var CommentList = React.createClass({ - render: function() { - var commentNodes = this.props.data.map(function(comment) { - return ( - - {comment.text} - - ); - }); - return ( -
- {commentNodes} -
- ); - } -}); - -var CommentForm = React.createClass({ - getInitialState: function() { - return {author: '', text: ''}; - }, - handleAuthorChange: function(e) { - this.setState({author: e.target.value}); - }, - handleTextChange: function(e) { - this.setState({text: e.target.value}); - }, - handleSubmit: function(e) { - e.preventDefault(); - var author = this.state.author.trim(); - var text = this.state.text.trim(); - if (!text || !author) { - return; - } - this.props.onCommentSubmit({author: author, text: text}); - this.setState({author: '', text: ''}); - }, - render: function() { - return ( -
- - - -
- ); - } -}); - -ReactDOM.render( - , - document.getElementById('content') -); diff --git a/public/scripts/questions.js b/public/scripts/questions.js index aeda8dad..c614c591 100644 --- a/public/scripts/questions.js +++ b/public/scripts/questions.js @@ -1,277 +1,84 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import QuestionList from './components/QuestionList.js'; +import QuestionForm from './components/QuestionForm.js'; - -var Question = React.createClass({ - rawMarkup: function() { - var rawMarkup = marked(this.props.children.toString(), {sanitize: true}); - return { __html: rawMarkup }; - }, - - renderShortAnswer: function() { - return( -
- - -
- ); - - }, - renderLongAnswer: function() { - return( -
- - -
- ); - - }, - renderSingleChoice: function() { - var choiceNodes = this.props.choices.map(function(choice) { - return ( - - - ); - }); - return ( -
-
- - {this.props.label} - - {choiceNodes} -
-
- ); - - }, - renderMultipleChoice: function() { - var choiceNodes = this.props.choices.map(function(choice) { - return ( - - - ); - }); - return ( -
-
- - {this.props.label} - - {choiceNodes} -
-
- ); - }, - render: function() { - var answerTypes = { - short_answer: this.renderShortAnswer, - long_answer: this.renderLongAnswer, - single_choice: this.renderSingleChoice, - multiple_choice: this.renderMultipleChoice - }; - return answerTypes[this.props.type](); - } -}); - -var Choice = React.createClass({ - render: function () { - return( -
- - -
- ); - } -}); - -var QuestionBox = React.createClass({ +var App = React.createClass({ loadQuestionsFromServer: function() { + var self = this; $.ajax({ url: this.props.url, dataType: 'json', - cache: false, - success: function(data) { - this.setState({data: data}); - }.bind(this), - error: function(xhr, status, err) { - console.error(this.props.url, status, err.toString()); - }.bind(this) - }); + }).done( + function(data){ + self.setState({data: data}); + } + ).fail( + function(xhr, status, err) { + console.error(this.props.url, status, err.toString()); + } + ); }, handleQuestionSubmit: function(question) { - var questions = this.state.data; - // Optimistically set an id on the new comment. It will be replaced by an - // id generated by the server. In a production application you would likely - // not use Date.now() for this and would have a more robust system in place. + //give the question an id question.id = Date.now(); - var newQuestions = questions.concat([question]); - this.setState({data: newQuestions}); + $.ajax({ url: this.props.url, dataType: 'json', type: 'POST', - data: question, - success: function(data) { - this.setState({data: data}); - }.bind(this), - error: function(xhr, status, err) { - this.setState({data: questions}); - console.error(this.props.url, status, err.toString()); - }.bind(this) - }); - }, - //TODO: live preview - handleQuestionPreview: function(question) { - this.setState({previewData:question}); + data: question + }).done( + function(data) { + this.setState({data:data}) + } + ).fail( + function(xhr, status, err) { + console.error(this.props.url, status, err.toString()); + } + ); }, getInitialState: function() { - return {data: []}; + return { data: [] }; }, componentDidMount: function() { this.loadQuestionsFromServer(); }, + removeQuestion: function(id) { + var self = this; + if(confirm('Are you sure you want to delete this question?')) { + $.ajax({ + url: this.props.url + "/" + id, + type: "DELETE" + }).done( + function(data) { + self.setState( { data:data } ); + } + ).fail( + function(xhr, status, err) { + console.error( this.props.url, status, err.toString() ); + } + ); + } + }, render: function() { return (
- - +

Questions

- +
); } }); -//TODO: live preview -var QuestionPreview = React.createClass({ - getInitalState: function() { - return {data:[]}; - }, - render: function() { - return ( - - - ); - } -}); - -var QuestionList = React.createClass({ - render: function() { - var questionNodes = this.props.data.map(function(question) { - if (question.choices) { - return ( - - - ); - } else { - return ( - - - ); - } - - }); - return ( -
- {questionNodes} -
- ); - } -}); -//Todo: implement multiple choice questions -var ChoiceInput = React.createClass({ - getInitalState: function() { - return {choices:0} - }, - addChoiceInput: function() { - this.setState({choices:this.state.choices++}) - }, - render:function() { - var choices = []; - if (this.props.data.type === "single_choice" || this.props.data.type === "multiple_choice"){ - for (var i = 0; i < this.state.choices; i++) { - choices.push() - } - choices.push() - } - return ( -
- {choices} -
- ); - } -}); -var QuestionForm = React.createClass({ - getInitialState: function() { - return {label: '', text: '', type:''}; - }, - handleLabelChange: function(e) { - this.setState({label: e.target.value}); - }, - handleTypeChange: function(e) { - this.setState({type:e.target.value}) - }, - handleTextChange: function(e) { - this.setState({text:e.target.value}); - this.addToChoices(e.target.value); - }, - addToChoices: function(text) { - var choices = []; - choices.push({id:choices.length,text:text.trim()}); - this.setState({choices: choices}); - }, - handleSubmit: function(e) { - e.preventDefault(); - var type = this.state.type.trim(); - var label = this.state.label.trim(); - var choices = this.state.choices; - if (!type || !label) { - return; - } - this.props.onQuestionSubmit({type: type, label: label, choices: choices}); - this.setState({type: '',label: ''}); - }, - //TODO: live preview - handlePreview: function(e) { - e.preventDefault(); - var type = this.state.type.trim(); - var label = this.state.label.trim(); - var text = this.state.text.trim(); - this.props.onQuestionChange({type:type, label:label, text:text}); - }, - //TODO: implement multiple choice questions - render: function() { - return ( -
- - - -
- ); - } -}); ReactDOM.render( - , + , document.getElementById('content') ); diff --git a/questions.json b/questions.json index 2ffe34d9..3f227606 100644 --- a/questions.json +++ b/questions.json @@ -1,64 +1,131 @@ [ { - "id": 1388534400000, - "type": "short_answer", - "label": "Do you like JavaScript?", - "text": "Of Course!" + "id": 1456000625379, + "type": "multiple_choice", + "label": "Test Choose Multi", + "choices": [ + { + "id": "0", + "text": "Choice 1" + }, + { + "id": "1", + "text": "Choice 2" + }, + { + "id": "2", + "text": "Choice 3" + } + ] }, { - "id": 1420070400000, - "type": "long_answer", - "label": "What do you like about it?", - "text": "React is *great*" + "id": 1456003359026, + "type": "single_choice", + "label": "Choose 1", + "choices": [ + { + "id": "0", + "text": "Choice 1" + }, + { + "id": "2", + "text": "Choice 3" + } + ] }, { - "id": 1450154197569, - "type": "long_answer", + "id": 1456003745055, + "type": "multiple_choice", + "label": "Test", + "choices": [ + { + "id": "0", + "text": "test 1" + }, + { + "id": "1", + "text": "test 2" + }, + { + "id": "2", + "text": "test 3" + } + ] + }, + { + "id": 1456004085566, + "type": "single_choice", "label": "test", - "text": "long answer" + "choices": [ + { + "id": "0", + "text": "You only get one" + } + ] + }, + { + "id": 1456004137688, + "type": "single_choice", + "label": "Choose 1", + "choices": [ + { + "id": "0", + "text": "Let's try again!" + } + ] }, { - "id": 1450218121596, + "id": 1456004220764, "type": "single_choice", - "label": "do you like javscript?", + "label": "which one ", "choices": [ { "id": "0", - "text": "Yes" + "text": "this one" }, { "id": "1", - "text": "No" + "text": "that one" } ] }, { - "id": 1450218298431, + "id": 1456006185410, + "type": "long_answer", + "label": "New long Question" + }, + { + "id": 1456006291834, + "type": "single_choice", + "label": "New Choosy Question", + "choices": [ + { + "id": "0", + "text": "Question 1" + }, + { + "id": "1", + "text": "Question 2" + } + ] + }, + { + "id": 1456006339898, "type": "multiple_choice", - "label": "what about react?", + "label": "New Multi Question", "choices": [ { "id": "0", - "text": "Maybe" + "text": "Choice 1" }, { "id": "1", - "text": "Sometimes" + "text": "Choice 2" }, { "id": "2", - "text": "None of the above" + "text": "Choice 3" } ] - }, - { - "id": 1450237712664, - "type": "short_answer", - "label": "Where are you from?" - }, - { - "id": 1450237724241, - "type": "long_answer", - "label": "What do you know?" } ] \ No newline at end of file diff --git a/server.js b/server.js index 76e62fcc..5806644f 100644 --- a/server.js +++ b/server.js @@ -1,14 +1,3 @@ -/** - * This file provided by Facebook is for non-commercial testing and evaluation - * purposes only. Facebook reserves all rights not expressly granted. - * - * 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 - * FACEBOOK 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. - */ var fs = require('fs'); var path = require('path'); @@ -35,7 +24,7 @@ app.get('/api/questions', function(req, res) { }); }); -app.post('/api/questions', function(req, res) { +app.post('/api/questions/', function(req, res) { fs.readFile(QUESTIONS_FILE, function(err, data) { if (err) { console.error(err); @@ -61,6 +50,30 @@ app.post('/api/questions', function(req, res) { }); }); +app.delete('/api/questions/:id', function(req, res){ + fs.readFile(QUESTIONS_FILE, function(err, data) { + if (err) { + console.error(err); + process.exit(1); + } + var questions = JSON.parse(data); + var id = req.params.id; + + var newQuestions = questions.filter(function(question){ + return question.id !== parseInt(id); + }); + + fs.writeFile(QUESTIONS_FILE, JSON.stringify(newQuestions, null, 4), function(err) { + if (err) { + console.error(err); + process.exit(1); + } + res.setHeader('Cache-Control', 'no-cache'); + res.json(newQuestions); + }); + }); +}); + app.listen(app.get('port'), function() { console.log('Server started: http://localhost:' + app.get('port') + '/'); diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 00000000..a14b1118 --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,32 @@ +var path = require('path'); +var webpack = require('webpack'); +module.exports = { + entry: './public/scripts/questions.js', + output: { + path: __dirname + "/public", + filename: 'bundle.js' + }, + module: { + loaders: [ + { + test: /\.js?$/, + include: [ + path.resolve(__dirname, "public/scripts") + ], + loader: 'babel-loader', + query: { + presets: ['es2015', 'react'] + } + + } + //{ test: /\.less$/, loader: 'style-loader!css-loader!less-loader' }, // use ! to chain loaders + //{ test: /\.css$/, loader: 'style-loader!css-loader' }, + //{ test: /\.(png|jpg)$/, loader: 'url-loader?limit=8192' } // inline base64 URLs for <=8k images, direct URLs for the rest + ] + }, + plugins: [ + new webpack.ProvidePlugin({ + $: "jquery", + }) + ] +}; From acfb531225b2a1406d2d8a33e94fd8f80ea9193f Mon Sep 17 00:00:00 2001 From: nicrocs Date: Sun, 21 Feb 2016 13:31:26 -0600 Subject: [PATCH 6/6] updating readme to include webpack instructions --- README.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7ce01bf2..e56ce924 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,21 @@ npm install node server.js ``` -And visit . Try opening multiple tabs! +And visit . + +### Webpack + +This project is using webpack to bundle javascript files. + +```sh +webpack +``` +or + +```sh +npm build +``` + ## Changing the port