parkerjgit
7/18/2018 - 6:42 AM

_refresh.md

3 ways to Use vuejs with Flask

1. Resolve Vuejs/Jinja {{}} syntax conflict by changing jinja delimitters

from flask import Flask, render_template, request

class CustomFlask(Flask):
    jinja_options = Flask.jinja_options.copy()
    jinja_options.update(dict(
    block_start_string='$$',
    block_end_string='$$',
    variable_start_string='$',
    variable_end_string='$',
    comment_start_string='$#',
    comment_end_string='#$',
))

app = CustomFlask(__name__)

@app.route("/")
def index():
return render_template("index.html")

if __name__ == "__main__":
app.run()

https://aitoehigie.wordpress.com/2016/08/01/using-flask-with-vue-js/

2. Use the Flask-Vue extension

from flask_vue import Vue

[...]

Vue(app)

or

vue = Vue()
vue.init_app(app)

3. Decouple Flask and Vue into api & client

see vuejs/_workflow.md

3. Decouple (option 2)

  1. Create Vue.js App in /frontend

  2. Move build target (i.e., /dist) to root-level outside project directory.

In frontend/config/index.js, change path of index.html and assetsRoot to top-level /dist directory.

index: path.resolve(__dirname, '../../dist/index.html'),
assetsRoot: path.resolve(__dirname, '../../dist'),
  1. Build minified App with npm run build

  2. Create Flask App in /backend

  3. When creating instance of flask app in run.py, pass in the locations of the static_folder and template_folder.

app = Flask(__name__,
            static_folder = "./dist/static",
            template_folder = "./dist")
  1. run the Flask Server in debug mode

(venv) FLASK_APP=run.py FLASK_DEBUG=1 flask run

  1. visit site at localhost:5000

  2. Create a Catch-All function in run.py to redirect all Flask routes to index.html.

@app.route('/', defaults={'path': ''})
@app.route('/<path:path>')
def catch_all(path):
    return render_template("index.html")
  1. Add a Vue component and route to handle unknown routes.

Vue Component:

<template>
  <div>
    <p>404 - Not Found</p>
  </div>
</template>

Vue Route:

const routerOptions = [
  ...,
  { path: '*', component: 'NotFound' }
]
  1. Build App again with npm run build and verify that unknown routes are handled correctly.

  2. Create Flask API endoints and test them.

  3. Use axios to perform http get/post requests and get response from api.

  4. Create rules to give access to your API endpoints for external servers with flask-cors extension.

from flask_cors import CORS
app = Flask(__name__,
            static_folder = "./dist/static",
            template_folder = "./dist")
cors = CORS(app, resources={r"/api/*": {"origins": "*"}})

Better yet, just proxy frontend dev server.

import requests

@app.route('/', defaults={'path': ''})
@app.route('/<path:path>')
def catch_all(path):
    if app.debug:
        return requests.get('http://localhost:8080/{}'.format(path)).text
    return render_template("index.html")

https://codeburst.io/full-stack-single-page-application-with-vue-js-and-flask-b1e036315532

see also:

http://stackabuse.com/single-page-apps-with-vue-js-and-flask-setting-up-vue-js/

See example templates/applications

https://github.com/ioiogoo/Vue-News-Board https://github.com/lh00000000/vue-flask-template https://github.com/JulienBalestra/vue-flask https://github.com/oleg-agapov/flask-vue-spa

How to setup Vuejs front-end

Scaffold application with CLI

seems can't use arrow-key selectors with git bash. not sure what recommended shell for windows is.

  1. install vue-cli

npm install --global vue-cli

  1. create a new project using a webpack setup

vue init webpack my-project, eg., vue init wepack app (for flask + vue app)

update for vue-cli 3!!!

vue create my-project, eg., vue create app (for flask + vue app)

  1. install dependencies and go!
cd my-project
npm install
npm run dev
  1. test

Setup Top-level components (pages)

  1. Create file for each component in src/components/

touch src/components/Quotes

  1. Create route for each in src/router/index.js
import Projects from '@/components/Quotes'

//...

export default new Router({
  routes: [
    {
      path: '/quotes',
      name: 'Quotes',
      component: Quotes
    }
  ]
});

Decide how you are going to do ajax calls: Axios, vue-resource or with the browser's built in fetch API.

Axios & vue-resource are high-level and easy to use. They both support promises and use XMLHttpRequest under the hood. Axios is probably the prefered library right now. Fetch is a much lower-level api that replaces XMLHttpRequest and benefits from being more explicit, at the cost of being considerably more verbose.

Setup http client with axios

  1. Install Axios

npm install axios --save

  1. Make simple api get request

from src/components/HelloWorld.vue or other component:

<template>
    <ul v-if="quotes && quotes.length">
        <li v-for="quote of quotes">
            <p><strong>{{quote.body}}</strong></p>
            <p>{{quote.author}}</p>
        </li>
    </ul>
    <ul v-else-if="errors && errors.length">
        <li v-for="error of errors">
            <p>{{error.message}}</p>
        </li>
    </ul>
</template>

<script>
import axios from 'axios';

export default {
    data() {
        return {
            quotes: [],
            errors: []
        }
    },

    created() {
        axios.get(`http://127.0.0.1:5000/api/one`)
        .then(response => {
            this.quotes = response.data
        })
        .catch(err => {
            this.errors.push(err)
        })
    }
}
</script>
  1. Make simple api post request

tbd...

Setup Vuex data store

  1. install vuex

npm install vuex --save

  1. Setup directory Structure
app/
└── src/
    └── store/
        ├── index.js          # create a store and assemble modules
        └── modules/
            ├── quotes.js     # quotes module
            └── ....          # more modules

from client/app:

mkdir src/store/
touch src/store/index.js
mkdir src/store/modules
touch src/store/modules/quotes.js
  1. Create store and assemble modules

edit index.js

import Vue from 'vue'
import Vuex from 'vuex'
import quotes from './modules/quotes'

Vue.use(Vuex)

export default new Vuex.Store({
    modules: {
        quotes
    }   
});
  1. Move api logic from vue component(s) to vuex module(s)

edit quotes.js

import axios from 'axios';

let $api = axios.create({
    baseURL: 'http://127.0.0.1:5000/api/',
    timeout: 5000,
    headers: {'Content-Type': 'application/json'}
})

export default {

    state: {
        quotes: [{
            "author" : "Nigel",
            "body" : "Just do it!"
        }],
        errors: []
    },

    getters: {
        quotes: (state) => {
            return state.quotes
        }
    },

    actions: {
        fetchQuotes: function (context) {
            $api.get(`one`)
                .then(response => response.data)
                .then((responseData) => {
                    context.commit('setQuotes', responseData)
            })
        }
    },

    mutations: {
        setQuotes: function (state, value) {
            state.quotes = value
        }
    } 
}

Here, the action makes the api get request and passes the response data to the mutation method responsible for setting our state object.

  1. replace vue component's data properties with computed properties

in HelloWorld.vue

export default {
    // data() {
    //     return {
    //         quotes: []
    //     }
    // },
    computed: {
        quotes() {
            return this.$store.getters.quotes
        }
    },
    created() {
        this.$store.dispatch('fetchQuotes')
    }
}
  1. Fetch Quotes with an api request

Actions "commit" mutations, and are triggered with the store.dispatch method.

export default {
    //...
    created() {
        this.$store.dispatch('fetchQuotes')
    }
}
  1. Pass store to the main Vue instance

in src/main.js:

import store from './store'

new Vue({
    //...
    store,
    //...
})
  1. test.

Create a web form for interacting with api

  1. create vue component for form

https://alligator.io/vuejs/rest-api-axios/ https://gist.github.com/unr/32a4b52192dd3507025603aa75e38970 https://codeburst.io/full-stack-single-page-application-with-vue-js-and-flask-b1e036315532 https://medium.com/techtrument/handling-ajax-request-in-vue-applications-using-axios-1d26c47fab0

vuejs

Concise notes (mostly copied directly and sometimes paraphrased) from vue.js guide.


[TOC]


Introdution

Declarative Rendering

The big deal: declaratively render, and reactively bind, data to the DOM using template syntax.

<div id="app">
  {{ message }}
</div>
var app = new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue!'
  }
})

Conditionals and Loops

Handling user Input

  1. Use v-on to attach event listeners that invoke vue instannce methods.
  2. "all DOM manipulations are handled by Vue"
  3. Use v-model for two-way binding between form input and app.

Composing with Components

any type of application interface can be abstracted into a tree of components

  1. A component is a Vue instance with pre-defined options. After registering a component, you can compose instances of it inside other components template!
Vue.component('todo-item', {
  template: '<li>This is a todo</li>'
})
<ol>
  <todo-item></todo-item>
</ol>

https://vuejs.org/v2/guide

  1. Pass data from parent scope into child components using Props

  2. Use v-bind (or :) for dynamically binding props to parent data.


The Vue Instance

  1. A Vue application consists of a root Vue instance created with new Vue, typically organized into a tree of nested, reusable components.
Root Instance
└─ TodoList
   ├─ TodoItem
   │  ├─ DeleteTodoButton
   │  └─ EditTodoButton
   └─ TodoListFooter
      ├─ ClearTodosButton
      └─ TodoListStatistics
  1. When a Vue instance is created, it adds all the properties found in its data object to Vue’s reactivity system. Note, data properties are only reactive if they existed when the instance was created. Any changes to reactive properties will automatically trigger view update.
// Our data object
var data = { a: 1 }
// The object is added to a Vue instance
var vm = new Vue({
  data: data
})
  1. Data properties of vue instance are accessed with dot syntax.
// These reference the same object!
vm.a === data.a // => true
  1. Vue instance also exposes instance properties and instance methods, accessed by prefixing $.
var data = { a: 1 }
var vm = new Vue({
  el: '#example',
  data: data
})

vm.$data === data // => true
vm.$el === document.getElementById('example') // => true

// $watch is an instance method
vm.$watch('a', function (newValue, oldValue) {
  // This callback will be called when `vm.a` changes
})

Instance Lifecycle Hooks

  1. Use lifecycle hooks, e.g. created, to add code at specific stages. lifecycle hooks are called with their this context pointing to the Vue instance invoking it.
new Vue({
  data: {
    a: 1
  },
  created: function () {
    // `this` points to the vm instance
    console.log('a is: ' + this.a)
  }
})

see lifecycle diagram for flow chart of available lifecycle hooks.


Template Syntax

Vue.js uses an HTML-based template syntax that allows you to declaratively bind the rendered DOM to the underlying Vue instance’s data

Interpolations

  1. The most basic form of data binding is text interpolation using the “Mustache” syntax
<div id="app">
    <span>{{ msg }}</span>
</div>
var app = new Vue({
  el: '#app',
  data: {
    msg: 'Hello!'
  }
})

It will also be updated whenever the data object’s msg property changes unless v-once directive is used.

<span v-once>This will never change: {{ msg }}</span>

  1. Use v-html directive to output raw HTML

<span v-html="rawHtml"></span>

The contents of the span will be replaced with the value of the rawHtml property, interpreted as plain HTML - data bindings are ignored.

  1. Mustaches cannot be used inside HTML attributes. Instead, use a v-bind directive.

<div v-bind:id="dynamicId"></div>

  1. JavaScript expressions can be used inside all data bindings!
<span>{{ message.split('').reverse().join('') }}</span>
<div v-bind:id="'list-' + id"></div>

Directives

Directives are special attributes, expected to be a single JavaScript expression, responsible for reactively applying side effects to the DOM when the value of its expression changes.

  1. Some directives take an argument, denoted by a colon after the directive name.
<a v-bind:href="url"> ... </a>
<a v-on:click="doSomething"> ... </a>
  1. Modifiers are special postfixes which indicate that a directive should be bound in some special way.

<form v-on:submit.prevent="onSubmit"> ... </form>

see v-on Event Modifiers and v-model Modifiers

Shorthands

  1. Use : shorthands for v-bind, e.g., <a :href="url">...</a>
  2. Use @ shorthands for v-on, e.g., <a @click="doSmth">...</a>

Computed Properties

  1. Prefer Computed Properties over In-template expressions for complex logic.
<div id="example">
  <p>Original message: "{{ message }}"</p>
  <p>Computed reversed message: "{{ reversedMessage }}"</p>
</div>
var vm = new Vue({
  el: '#example',
  data: {
    message: 'Hello'
  },
  computed: {
    // a computed getter
    reversedMessage: function () {
      // `this` points to the vm instance
      return this.message.split('').reverse().join('')
    }
  }
})
  1. A Computed Property will only re-evaluate when some of its dependencies have changed. For this reason, it is preferable to invoking a method in the expression:

<p>Reversed message: "{{ reverseMessage() }}"</p>

  1. Prefer Computed Property over watch properties for most cases.

  2. Computed properties are implicitly getters. Specify Computed Getters/Setter explicitly with get and set.

computed: {
  fullName: {
    // getter
    get: function () {
      return this.firstName + ' ' + this.lastName
    },
    // setter
    set: function (newValue) {
      var names = newValue.split(' ')
      this.firstName = names[0]
      this.lastName = names[names.length - 1]
    }
  }
}
  1. User Watchers when you want to perform asynchronous or expensive operations in response to changing data or asynchronous operations (i.e., accessing an API).

Class and Style Binding

  1. Toggle classes conditionally with Object Syntax class binding v-bind:class="{...}"
<!--inline object bindings-->
<div class="a" v-bind:class="{ active: isActive, error: hasError }">a</div>
<!--or bind to property object-->
<div class="b" v-bind:class="classObject">b</div>
<!--or bind to a computed property that returns an object-->
<div class="b" v-bind:class="computedClassObject">b</div>
data: {
  isActive: true,
  hasError: false,
  classObject: {
    active: false,
    error: true
  }
  computed: {
    computedClassObject: function () {
      return {
        active: this.isActive && !this.error,
        'text-danger': this.error && this.error.type === 'fatal'
      }
    }
  }
}

renders:

<div class="a active"></div>
<div class="b error"></div>
  1. Apply multiple classes with array syntax class binding v-bind:class="[...]"
<div v-bind:class="[activeClass, errorClass]"></div>
data: {
  activeClass: 'active',
  errorClass: 'text-danger'
}

renders:

<div class="active text-danger"></div>
  1. Combine object syntax inside array syntax.
<div v-bind:class="[{ active: isActive }, errorClass]"></div>
  1. Class bindings work same with components
<my-component v-bind:class="{ active: isActive }"></my-component>
Vue.component('my-component', {
  template: '<p class="foo bar">Hi</p>'
})

renders:

<p class="foo bar active">Hi</p>

note template class don't get overwritten.

  1. Bind inline Styles Too!
<div v-bind:style="styleObject"></div>
data: {
  styleObject: {
    color: 'red',
    fontSize: '13px'
  }
}

Note, Vue has some ways of helping you deal with vendor prefixes, etc.. See auto-prefixing and multiple values

(see The debate around "Do we even need CSS anymore?"" and CSS in JS)


Conditional Rendering

  1. Insert conditional blocks with v-if, v-else and v-else-if
<h1 v-if="ok">Yes</h1>
<h1 v-else>No</h1>
<template v-if="ok">
  <h1>Title</h1>
  <p>Paragraph 1</p>
  <p>Paragraph 2</p>
</template>
<div v-if="type === 'A'">A</div>
<div v-else-if="type === 'B'">B</div>
<div v-else-if="type === 'C'">C</div>
<div v-else>Not A/B/C</div>
  1. Prefer v-show if you need to toggle something very often.
<h1 v-show="ok">Hello!</h1>

v-if has higher toggle costs while v-show has higher initial render costs. So prefer v-show if you need to toggle something very often, and prefer v-if if the condition is unlikely to change at runtime. (v-if vs v-show)


List Rendering

  1. use v-for to iterate items in/of a parent array
<ul id="example-2">
  <li v-for="(item, index) in items">
    {{ index }}. {{ item.message }}
  </li>
</ul>