{{}}
syntax conflict by changing jinja delimittersfrom 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/
from flask_vue import Vue
[...]
Vue(app)
or
vue = Vue()
vue.init_app(app)
Create Vue.js App in /frontend
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'),
Build minified App with npm run build
Create Flask App in /backend
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")
(venv) FLASK_APP=run.py FLASK_DEBUG=1 flask run
visit site at localhost:5000
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")
Vue Component:
<template>
<div>
<p>404 - Not Found</p>
</div>
</template>
Vue Route:
const routerOptions = [
...,
{ path: '*', component: 'NotFound' }
]
Build App again with npm run build
and verify that unknown routes are handled correctly.
Create Flask API endoints and test them.
Use axios to perform http get/post requests and get response from api.
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/
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
seems can't use arrow-key selectors with git bash. not sure what recommended shell for windows is.
npm install --global vue-cli
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)
cd my-project
npm install
npm run dev
src/components/
touch src/components/Quotes
src/router/index.js
import Projects from '@/components/Quotes'
//...
export default new Router({
routes: [
{
path: '/quotes',
name: 'Quotes',
component: Quotes
}
]
});
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.
npm install axios --save
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>
tbd...
npm install vuex --save
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
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
}
});
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.
in HelloWorld.vue
export default {
// data() {
// return {
// quotes: []
// }
// },
computed: {
quotes() {
return this.$store.getters.quotes
}
},
created() {
this.$store.dispatch('fetchQuotes')
}
}
Actions "commit" mutations, and are triggered with the store.dispatch
method.
export default {
//...
created() {
this.$store.dispatch('fetchQuotes')
}
}
in src/main.js:
import store from './store'
new Vue({
//...
store,
//...
})
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
No. | Course | Institution | Effort | Status |
---|---|---|---|---|
--- | Learning Vue.js | Lynda | --- | --- |
--- | Vue.js: Building an Interface | Lynda | --- | --- |
--- | Vue JS 2 - The Complete Guide | Udemy | --- | --- |
xxx | xxx | xxx | xxx | xxx |
vuex
https://github.com/mschwarzmueller/vuejs2-vuex-basics https://gist.github.com/unr/32a4b52192dd3507025603aa75e38970 https://github.com/mschwarzmueller/vuejs2-vuex-basics
learn nativescript / nativescript-vue learn firebase learn electron - cross-platform desktop applications w/ js,html,css
Concise notes (mostly copied directly and sometimes paraphrased) from vue.js guide.
[TOC]
<div id="app">
{{ message }}
</div>
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
})
v-on
to attach event listeners that invoke vue instannce methods.v-model
for two-way binding between form input and app.any type of application interface can be abstracted into a tree of components
Vue.component('todo-item', {
template: '<li>This is a todo</li>'
})
<ol>
<todo-item></todo-item>
</ol>
Pass data from parent scope into child components using Props
Use v-bind
(or :) for dynamically binding props to parent data.
Root Instance
└─ TodoList
├─ TodoItem
│ ├─ DeleteTodoButton
│ └─ EditTodoButton
└─ TodoListFooter
├─ ClearTodosButton
└─ TodoListStatistics
// Our data object
var data = { a: 1 }
// The object is added to a Vue instance
var vm = new Vue({
data: data
})
// These reference the same object!
vm.a === data.a // => true
$
.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
})
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.
Vue.js uses an HTML-based template syntax that allows you to declaratively bind the rendered DOM to the underlying Vue instance’s data
<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>
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.
<div v-bind:id="dynamicId"></div>
<span>{{ message.split('').reverse().join('') }}</span>
<div v-bind:id="'list-' + id"></div>
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.
<a v-bind:href="url"> ... </a>
<a v-on:click="doSomething"> ... </a>
<form v-on:submit.prevent="onSubmit"> ... </form>
see v-on Event Modifiers and v-model Modifiers
:
shorthands for v-bind
, e.g., <a :href="url">...</a>
@
shorthands for v-on
, e.g., <a @click="doSmth">...</a>
<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('')
}
}
})
<p>Reversed message: "{{ reverseMessage() }}"</p>
Prefer Computed Property over watch properties for most cases.
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]
}
}
}
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>
v-bind:class="[...]"
<div v-bind:class="[activeClass, errorClass]"></div>
data: {
activeClass: 'active',
errorClass: 'text-danger'
}
renders:
<div class="active text-danger"></div>
<div v-bind:class="[{ active: isActive }, errorClass]"></div>
<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.
<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)
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>
v-show
if you need to toggle something very often.<h1 v-show="ok">Hello!</h1>
v-if
has higher toggle costs whilev-show
has higher initial render costs. So preferv-show
if you need to toggle something very often, and preferv-if
if the condition is unlikely to change at runtime. (v-if vs v-show)
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>