diff --git a/samples/features/json/reactjs/nodejs-comments-app/.gitignore b/samples/features/json/reactjs/nodejs-comments-app/.gitignore new file mode 100644 index 00000000..f9d85749 --- /dev/null +++ b/samples/features/json/reactjs/nodejs-comments-app/.gitignore @@ -0,0 +1,5 @@ +node_modules/* +bin/*.dll +obj/* +*.sln +*.log \ No newline at end of file diff --git a/samples/features/json/reactjs/nodejs-comments-app/app.js b/samples/features/json/reactjs/nodejs-comments-app/app.js new file mode 100644 index 00000000..dbe71ed8 --- /dev/null +++ b/samples/features/json/reactjs/nodejs-comments-app/app.js @@ -0,0 +1,21 @@ +var express = require('express'); +var bodyParser = require('body-parser'); + +var app = express(); +app.use(express.static('wwwroot')); +app.use(bodyParser.urlencoded()); +app.use('/api/comments', require('./routes/comments')); + +// catch 404 and forward to error handler +app.use(function (req, res, next) { + var err = new Error('Not Found'); + err.status = 404; + next(err); +}); +app.set('port', process.env.PORT || 3000); + +var server = app.listen(app.get('port'), function() { + console.log('Express server listening on port ' + server.address().port); +}); + +module.exports = app; diff --git a/samples/features/json/reactjs/nodejs-comments-app/bin/www b/samples/features/json/reactjs/nodejs-comments-app/bin/www new file mode 100644 index 00000000..4d9bddf0 --- /dev/null +++ b/samples/features/json/reactjs/nodejs-comments-app/bin/www @@ -0,0 +1,3 @@ +#!/usr/bin/env node +var debug = require('debug')('nodejs_express4_rest_api'); +var app = require('../app'); diff --git a/samples/features/json/reactjs/nodejs-comments-app/db.js b/samples/features/json/reactjs/nodejs-comments-app/db.js new file mode 100644 index 00000000..97e302f6 --- /dev/null +++ b/samples/features/json/reactjs/nodejs-comments-app/db.js @@ -0,0 +1,85 @@ +function createConnection() { + + var config = { + server : "", + userName: "", + password: "", + // If you're on Azure, you will need this: + options: { encrypt: true, database: 'CommentsDb' } + }; + + var Connection = require('tedious').Connection; + var connection = new Connection(config); + + return connection; +} + +function createRequest(query, connection) { + var Request = require('tedious').Request; + var req = + new Request(query, + function (err, rowCount) { + if (err) { + console.trace(err); + throw err; + } + connection && connection.close(); + console.log('Connection closed'); + }); + return req; +} + +function stream (query, connection, output, defaultContent) { + + var request = query; + if (typeof query == "string") { + request = createRequest(query, connection); + } + + var empty = true; + request.on('row', function (columns) { + if(empty) { + console.log('Response fetched from SQL Database!'); + empty = false; + } + output.write(columns[0].value); + }); + + request.on('done', function (rowCount, more, rows) { + _OnDone(empty, defaultContent, output); + }); + + request.on('doneProc', function (rowCount, more, rows) { + _OnDone(empty, defaultContent, output); + }); + + executeRequest (request, connection); +} + +function _OnDone(empty, defaultContent, output) { + if(empty) { + output.write(defaultContent); + console.log('No results from database - default content is returned.'); + } + try { + console.log('Closing Http Response output.'); + output.end(); + } catch (err) { + console.error(err); + } + } + +function executeRequest (request, connection) { + connection.on('connect', function (err) { + if (err) { + console.trace(err); + throw err; + } + connection.execSql(request); + }); +} + +module.exports.createConnection = createConnection; +module.exports.createRequest = createRequest; +module.exports.executeRequest = executeRequest; +module.exports.stream = stream; diff --git a/samples/features/json/reactjs/nodejs-comments-app/package.json b/samples/features/json/reactjs/nodejs-comments-app/package.json new file mode 100644 index 00000000..1376b6f8 --- /dev/null +++ b/samples/features/json/reactjs/nodejs-comments-app/package.json @@ -0,0 +1,19 @@ +{ + "name": "nodejs-express4-rest-api", + "version": "0.0.0", + "private": true, + "scripts": { + "start": "node ./bin/www" + }, + "description": "nodejs-express4-rest-api", + "author": { + "name": "Jovan Popovic", + "email": "jovanpop@microsoft.com" + }, + "dependencies": { + "body-parser": "^1.15.2", + "debug": "^2.2.0", + "express": "^4.14.0", + "tedious": "^1.14.0" + } +} diff --git a/samples/features/json/reactjs/nodejs-comments-app/routes/comments.js b/samples/features/json/reactjs/nodejs-comments-app/routes/comments.js new file mode 100644 index 00000000..5d686b14 --- /dev/null +++ b/samples/features/json/reactjs/nodejs-comments-app/routes/comments.js @@ -0,0 +1,34 @@ +var express = require('express'); +var router = express.Router(); + +var db = require('../db.js'); +var TYPES = require('tedious').TYPES; + +/* GET comments. */ +router.get('/', function (req, res) { + db.stream("select * from comments for json path", db.createConnection(), res, '[]'); +}); + +/* GET single comment. */ +router.get('/:id', function (req, res) { + + var conn = db.createConnection(); + + var request = db.createRequest("select * from comments where id = @id for json path, without_array_wrapper", conn); + request.addParameter('id', TYPES.Int, req.params.id); + db.stream(request, conn, res, '{}'); +}); + +/* POST create comment. */ +router.post('/', function (req, res) { + + var connection = db.createConnection(); + var request = db.createRequest("insert into Comments values (@author, @text)", connection); + + request.addParameter('author', TYPES.NVarChar, req.body.author); + request.addParameter('text', TYPES.NVarChar, req.body.text); + + db.executeRequest(request, connection); +}); + +module.exports = router; \ No newline at end of file diff --git a/samples/features/json/reactjs/nodejs-comments-app/setup/setup.sql b/samples/features/json/reactjs/nodejs-comments-app/setup/setup.sql new file mode 100644 index 00000000..f5db1fba --- /dev/null +++ b/samples/features/json/reactjs/nodejs-comments-app/setup/setup.sql @@ -0,0 +1,47 @@ +--CREATE DATABASE TodoDb; +--USE TodoDb; +DROP TABLE IF EXISTS Todo +DROP PROCEDURE IF EXISTS createTodo +DROP PROCEDURE IF EXISTS updateTodo +GO + +CREATE TABLE Todo ( + id int IDENTITY PRIMARY KEY, + title nvarchar(30) NOT NULL, + description nvarchar(4000), + completed bit, + dueDate datetime2 default (dateadd(day, 3, getdate())) +) +GO + +INSERT INTO Todo (title, description, completed, dueDate) +VALUES +('Install SQL Server 2016','Install RTM version of SQL Server 2016', 0, '2017-03-08'), +('Get new samples','Go to github and download new samples', 0, '2016-03-09'), +('Try new samples','Install new Management Studio to try samples', 0, '2016-03-12') + +GO + +create procedure dbo.createTodo(@todo nvarchar(max)) +as begin + insert into Todo + select * + from OPENJSON(@todo) + WITH ( title nvarchar(30), description nvarchar(4000), + completed bit, dueDate datetime2) +end +GO + +create procedure updateTodo(@id int, @todo nvarchar(max)) +as begin + update Todo + set title = json.title, description = json.description, + completed = json.completed, dueDate = json.dueDate + from OPENJSON( @todo ) + WITH( title nvarchar(30), description nvarchar(4000), + completed bit, dueDate datetime2) AS json + where id = @id +end +go + +select * from todo for json path \ No newline at end of file diff --git a/samples/features/json/reactjs/nodejs-comments-app/wwwroot/css/base.css b/samples/features/json/reactjs/nodejs-comments-app/wwwroot/css/base.css new file mode 100644 index 00000000..c8cc35f7 --- /dev/null +++ b/samples/features/json/reactjs/nodejs-comments-app/wwwroot/css/base.css @@ -0,0 +1,59 @@ +body { + background: #fff; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 15px; + line-height: 1.7; + margin: 0; + padding: 30px; +} + +a { + color: #4183c4; + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +code { + background-color: #f8f8f8; + border: 1px solid #ddd; + border-radius: 3px; + font-family: "Bitstream Vera Sans Mono", Consolas, Courier, monospace; + font-size: 12px; + margin: 0 2px; + padding: 0 5px; +} + +h1, h2, h3, h4 { + font-weight: bold; + margin: 0 0 15px; + padding: 0; +} + +h1 { + border-bottom: 1px solid #ddd; + font-size: 2.5em; +} + +h2 { + border-bottom: 1px solid #eee; + font-size: 2em; +} + +h3 { + font-size: 1.5em; +} + +h4 { + font-size: 1.2em; +} + +p, ul { + margin: 15px 0; +} + +ul { + padding-left: 30px; +} diff --git a/samples/features/json/reactjs/nodejs-comments-app/wwwroot/favicon.ico b/samples/features/json/reactjs/nodejs-comments-app/wwwroot/favicon.ico new file mode 100644 index 00000000..118d4df9 Binary files /dev/null and b/samples/features/json/reactjs/nodejs-comments-app/wwwroot/favicon.ico differ diff --git a/samples/features/json/reactjs/nodejs-comments-app/wwwroot/index.html b/samples/features/json/reactjs/nodejs-comments-app/wwwroot/index.html new file mode 100644 index 00000000..5af72a45 --- /dev/null +++ b/samples/features/json/reactjs/nodejs-comments-app/wwwroot/index.html @@ -0,0 +1,22 @@ + + + + + React Tutorial + + + + + + + + + +
+ + + + diff --git a/samples/features/json/reactjs/nodejs-comments-app/wwwroot/scripts/example.js b/samples/features/json/reactjs/nodejs-comments-app/wwwroot/scripts/example.js new file mode 100644 index 00000000..f2d9103c --- /dev/null +++ b/samples/features/json/reactjs/nodejs-comments-app/wwwroot/scripts/example.js @@ -0,0 +1,147 @@ +/** + * 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 md = new Remarkable(); + var rawMarkup = md.render(this.props.children.toString()); + 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') +);