├── .bowerrc ├── .gitignore ├── LICENSE ├── Procfile ├── README.md ├── app ├── __init__.py ├── static │ ├── css │ │ ├── main.css │ │ └── styles.css │ └── jsx │ │ ├── components │ │ └── App.js │ │ └── main.js └── templates │ └── base.html ├── bower.json └── requirements.txt /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "app/static/libs" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | app/static/libs/ 2 | app/static/js/ 3 | node_modules 4 | .DS_Store 5 | .module-cache 6 | Gruntfile.js 7 | package.json -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Abhinay Omkar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: gunicorn app:app --log-file=- -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Flask React 2 | =========== 3 | 4 | Introduction 5 | ------------ 6 | Boilerplate to create a simple web app with Flask and React. Other fontend libraries included Twitter Bootstrap, jQuery, Lodash, Require.js & Font Awesome. 7 | 8 | Demo 9 | ---- 10 | Deployed on Heroku: [flask-react.herokuapp.com](http://flask-react.herokuapp.com) 11 | 12 | Installation 13 | ------------ 14 | * Install python dependencies 15 | 16 | pip install flask requests 17 | 18 | * Install required frontend libraries using [bower](http://bower.io/#install-bower). 19 | 20 | bower install 21 | 22 | * Transform JSX to JS using [React tool](http://facebook.github.io/react/docs/tooling-integration.html#productionizing-precompiled-jsx) for development purpose 23 | 24 | jsx --watch app/static/jsx app/static/js 25 | 26 | * Run Flask server 27 | 28 | python app/main.py 29 | 30 | * Start coding! :) 31 | 32 | Author 33 | ------ 34 | Abhinay Omkar 35 | 36 | License 37 | ------- 38 | MIT 39 | 40 | TODO 41 | ---- 42 | 43 | - Migrate to ES6. Get rid of deprecated JSX. 44 | - Support server side rendering of React components 45 | - Use webpack & gulp for packaging and building. 46 | - Use PostCSS. 47 | - Add deploy instructions. 48 | -------------------------------------------------------------------------------- /app/__init__.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import os 3 | import json 4 | 5 | from flask import Flask, Response, request, session, g, redirect, url_for, abort, render_template, flash 6 | from urlparse import urljoin 7 | 8 | app = Flask(__name__) 9 | 10 | @app.route("/") 11 | def index(): 12 | return render_template('base.html') 13 | 14 | @app.route('/api/', methods=['GET']) 15 | def api(path): 16 | clientToken = '' 17 | clientSecret = '' 18 | baseUrl = 'https://suggestqueries.google.com' 19 | accessToken = '' 20 | 21 | # Authentication 22 | # 23 | # session.auth = EdgeGridAuth ( 24 | # client_token = clientToken, 25 | # client_secret = clientSecret, 26 | # access_token = accessToken 27 | # ) 28 | 29 | r = requests.get(urljoin(baseUrl, path), params=request.args) 30 | 31 | if r.status_code != requests.codes.ok: 32 | return None 33 | 34 | return Response(r.content, mimetype='application/json') 35 | 36 | if __name__ == "__main__": 37 | app.run(debug=True) 38 | -------------------------------------------------------------------------------- /app/static/css/main.css: -------------------------------------------------------------------------------- 1 | @import '../libs/bootstrap/dist/css/bootstrap.min.css'; 2 | @import '../libs/font-awesome/css/font-awesome.css'; 3 | 4 | @import 'styles.css'; -------------------------------------------------------------------------------- /app/static/css/styles.css: -------------------------------------------------------------------------------- 1 | .r-container { 2 | margin: auto; 3 | width: 400px; 4 | margin-top: 200px; 5 | } 6 | 7 | .r-container h1 { 8 | font-family: 'Helvetica Nueu', Helvetica, Arial, sans-serif; 9 | font-weight: 300; 10 | font-size: 16px; 11 | color: #3F3F3F; 12 | } 13 | 14 | .r-container > input { 15 | width: 100%; 16 | font-size: 15px; 17 | font-weight: 300; 18 | border: 1px solid #cdcdcd; 19 | padding: 6px; 20 | outline: 0; 21 | } 22 | 23 | .r-container > input:hover, .r-container > input:active, .r-container > input:focus { 24 | border: 1px solid #bbb; 25 | } 26 | 27 | .r-container > ul.list { 28 | margin: 0; 29 | padding: 0; 30 | list-style: none; 31 | border-left: 1px solid #cdcdcd; 32 | border-bottom: 1px solid #cdcdcd; 33 | border-right: 1px solid #cdcdcd; 34 | box-shadow: 1px 1px 3px #cdcdcd; 35 | overflow: scroll; 36 | } 37 | 38 | .r-container > ul.list a { 39 | cursor: default; 40 | color: #333; 41 | padding-left: 7px; 42 | padding-right: 7px; 43 | display: block; 44 | } 45 | 46 | .r-container > ul.list a:hover { 47 | text-decoration: none; 48 | background: #f1f1f1; 49 | } -------------------------------------------------------------------------------- /app/static/jsx/components/App.js: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | 3 | define([ 4 | 'react', 5 | ], function(React) { 6 | var App = React.createClass({ 7 | getInitialState: function() { 8 | return { 9 | suggestions: [] 10 | } 11 | }, 12 | 13 | getAutoSuggestions: function(keywords) { 14 | /* 15 | @keywords - array of keywords 16 | 17 | Returns: 18 | JSON Response 19 | */ 20 | 21 | var self = this; 22 | 23 | if (_.isArray(keywords)) { 24 | this._xhr = $.get('/api/complete/search?client=firefox&q=' + keywords.join('+'), function(data) { 25 | self.setState({suggestions: data[1]}) 26 | }); 27 | } 28 | else { 29 | return false; 30 | } 31 | }, 32 | 33 | handleChange: function(e) { 34 | var keywords = e.target.value.match(/\S+/g), 35 | self = this; 36 | 37 | // clear timeout if timeout is already set 38 | if (this._t) { 39 | clearTimeout(this._t); 40 | } 41 | 42 | // abort any ajax calls 43 | if (this._xhr) { 44 | this._xhr.abort(); 45 | } 46 | 47 | // if no keywords then set the suggestions to empty array 48 | if (keywords === null) { 49 | this.setState({suggestions: []}); 50 | } 51 | else { 52 | this._t = setTimeout(function() { self.getAutoSuggestions(keywords) }, 500); 53 | } 54 | }, 55 | 56 | render: function() { 57 | var suggestions = []; 58 | 59 | _.forEach(this.state.suggestions, function(s, i) { 60 | suggestions.push(
  • {s}
  • ); 61 | }) 62 | 63 | return (
    64 |

    Google Auto Suggestion

    65 | 66 | {suggestions.length > 0 ?
      {suggestions}
    : null} 67 |
    ); 68 | } 69 | }); 70 | 71 | return App; 72 | }); -------------------------------------------------------------------------------- /app/static/jsx/main.js: -------------------------------------------------------------------------------- 1 | require.config({ 2 | paths: { 3 | react: '../libs/react/react', 4 | jquery: '../libs/jquery/dist/jquery.min', 5 | lodash: '../libs/lodash/dist/lodash.min', 6 | bootstrap: '../libs/bootstrap/dist/js/bootstrap.min' 7 | }, 8 | 9 | shim: { 10 | react: { 11 | exports: 'React' 12 | }, 13 | 14 | jquery: { 15 | exports: '$' 16 | }, 17 | 18 | lodash: { 19 | exports: '_' 20 | }, 21 | 22 | bootstrap: { 23 | deps: ['jquery'] 24 | } 25 | } 26 | }); 27 | 28 | require([ 29 | 'react', 30 | 'components/App', 31 | 'jquery', 32 | 'lodash', 33 | 'bootstrap' 34 | ], 35 | function(React, App) { 36 | React.renderComponent(App(), document.getElementById('app')); 37 | }); -------------------------------------------------------------------------------- /app/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
    14 | 15 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Flask React", 3 | "version": "0.1.0", 4 | "authors": [ 5 | "Abhinay Omkar " 6 | ], 7 | "license": "MIT", 8 | "ignore": [ 9 | "**/.*", 10 | "node_modules", 11 | "bower_components", 12 | "libs", 13 | "test", 14 | "tests" 15 | ], 16 | "dependencies": { 17 | "bootstrap": "~3.2.0", 18 | "jquery": "~2.1.1", 19 | "requirejs": "~2.1.15", 20 | "react": "~0.11.1", 21 | "font-awesome": "~4.2.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Flask==0.10.1 2 | Jinja2==2.7.3 3 | MarkupSafe==0.23 4 | Werkzeug==0.9.6 5 | gunicorn==19.1.1 6 | itsdangerous==0.24 7 | requests==2.4.1 8 | wsgiref==0.1.2 9 | --------------------------------------------------------------------------------