├── .gitignore ├── Procfile ├── README.md ├── app.json ├── app.py ├── db.cfg ├── requirements.txt ├── static ├── css │ └── style.css ├── js │ ├── general.js │ ├── marked.min.js │ └── vue.js ├── sfc │ ├── .babelrc │ ├── .gitignore │ ├── README.md │ ├── index.html │ ├── package.json │ ├── src │ │ ├── App.vue │ │ └── main.js │ └── webpack.config.js └── ts │ ├── .babelrc │ ├── .gitignore │ ├── README.md │ ├── index.html │ ├── package.json │ ├── src │ ├── App.vue │ ├── assets │ │ └── logo.png │ ├── main.ts │ └── vue-shims.d.ts │ ├── tsconfig.json │ └── webpack.config.js └── templates ├── example.html ├── index.html ├── layout.html ├── more.html ├── router.html ├── sfc.html ├── sqlalchemy.html ├── typescript.html ├── vue.js_v0.10.3.html └── vuex.html /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.db 3 | node_modules 4 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: gunicorn app:app --log-file=- 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flask-Vue.js 2 | 3 | 🍣 compatible Vue.js v2 🍣 4 | 5 | confrict a Jinja2 delimiter and Vue.js delimiter... 6 | 7 | => change Jinja2 delimiter 8 | 9 | ```python 10 | from flask import Flask 11 | 12 | class CustomFlask(Flask): 13 | jinja_options = Flask.jinja_options.copy() 14 | jinja_options.update(dict( 15 | block_start_string='(%', 16 | block_end_string='%)', 17 | variable_start_string='((', 18 | variable_end_string='))', 19 | comment_start_string='(#', 20 | comment_end_string='#)', 21 | )) 22 | 23 | app = CustomFlask(__name__) 24 | 25 | # 26 | # your flask code here 27 | # 28 | ``` 29 | 30 | - [different delimiters in jinja2 + flask](https://gist.github.com/lost-theory/3925738 "different delimiters in jinja2 + flask") 31 | 32 | => change Vue.js delimiter 33 | 34 | ```javascript 35 | var app = new Vue({ 36 | el: "#app", 37 | delimiters: ["[[", "]]"], 38 | data: { 39 | message: "Hello Vue!" 40 | } 41 | }) 42 | ``` 43 | 44 | - [Vue.js api/#delimiters](https://vuejs.org/v2/api/#delimiters "Vue.js api/#delimiters") 45 | 46 | # Example & Tips 47 | 48 | This repository contain some example, if you want to try it please do as follows. 49 | 50 | requirements: Flask (=> pip install Flask) 51 | 52 | ``` 53 | $ git clone https://github.com/yymm/flask-vuejs.git 54 | $ cd flask-vuejs 55 | $ python app.py 56 | ``` 57 | 58 | see localhost:5000. 59 | 60 | ## Heroku Button 61 | 62 | Try it now. 63 | 64 | [![Deploy](https://www.herokucdn.com/deploy/button.png)](https://heroku.com/deploy) 65 | 66 | ## Menu 67 | 68 | - [x] Jinja2 & Vue.js 69 | - [x] with SQLAlchemy(Flask-SQLAlchemy) 70 | - [x] more... 71 | - SPA 72 | - using CDN 73 | - [x] vue-router 74 | - using Node.js(using vue-cli 'webpack-simple') 75 | - [x] Single File Component 76 | - [ ] TypeScript with Single File Component 77 | - [x] Vuex 78 | - [x] Vue.js v0.10.3 79 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flask-vue.js", 3 | "description": "Example & Tips, Flask with Vue.js. ", 4 | "image": "heroku/python", 5 | "repository": "https://github.com/yymm/flask-vuejs", 6 | "keywords": ["python", "flask", "vue.js" ] 7 | } 8 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, render_template, jsonify, request 2 | from flask_sqlalchemy import SQLAlchemy 3 | from datetime import datetime 4 | 5 | 6 | class CustomFlask(Flask): 7 | jinja_options = Flask.jinja_options.copy() 8 | jinja_options.update(dict( 9 | block_start_string='{%', 10 | block_end_string='%}', 11 | variable_start_string='((', 12 | variable_end_string='))', 13 | comment_start_string='{#', 14 | comment_end_string='#}', 15 | )) 16 | 17 | 18 | app = CustomFlask(__name__) 19 | app.config.from_pyfile('db.cfg') 20 | db = SQLAlchemy(app) 21 | 22 | 23 | class Todo(db.Model): 24 | __tablename__ = 'todos' 25 | id = db.Column('todo_id', db.Integer, primary_key=True) 26 | title = db.Column(db.String(60)) 27 | done = db.Column(db.Boolean) 28 | pub_date = db.Column(db.DateTime) 29 | 30 | def __init__(self, title): 31 | self.title = title 32 | self.done = False 33 | self.pub_date = datetime.utcnow() 34 | 35 | def get_dict(self): 36 | return { 37 | 'id': self.id, 38 | 'title': self.title, 39 | 'done': self.done, 40 | 'pub_date': self.pub_date.strftime('%Y-%m-%d %H:%M'), 41 | } 42 | 43 | @app.route('/') 44 | def index(): 45 | return render_template('index.html') 46 | 47 | 48 | @app.route('/example') 49 | def example(): 50 | message = "Hello Flask!" 51 | return render_template('example.html', message=message) 52 | 53 | 54 | @app.route('/more') 55 | def more(): 56 | return render_template('more.html') 57 | 58 | 59 | def initialize_database(): 60 | app.logger.info('Database is not created, exec create_all() here.') 61 | db.create_all() 62 | data1 = Todo('todo1') 63 | data2 = Todo('todo2') 64 | db.session.add(data1) 65 | db.session.add(data2) 66 | db.session.commit() 67 | 68 | 69 | @app.route('/sqlalchemy') 70 | def sqlalchemy(): 71 | todos = [] 72 | try: 73 | todos = Todo.query.order_by(Todo.pub_date.desc()).all() 74 | except: 75 | initialize_database() 76 | return render_template('sqlalchemy.html', todos=todos) 77 | 78 | 79 | @app.route('/sqlalchemy/get', methods=['GET']) 80 | def sqlalchemy_get(): 81 | todos = Todo.query.order_by(Todo.pub_date.desc()).all() 82 | return jsonify(todos=[todo.get_dict() for todo in todos]) 83 | 84 | 85 | @app.route('/sqlalchemy/new', methods=['POST']) 86 | def sqlalchemy_new(): 87 | if request.json: 88 | db.session.add(Todo(request.json['title'])) 89 | db.session.commit() 90 | return jsonify(status='ok') # Oops: always ok... 91 | 92 | 93 | @app.route('/sqlalchemy/update', methods=['POST']) 94 | def sqlalchemy_update(): 95 | if request.json: 96 | todo = Todo.query.get(request.json['id']) 97 | todo.done = request.json['done'] 98 | todo.title = request.json['title'] 99 | db.session.commit() 100 | return jsonify(status='ok') # Oops: always ok... 101 | 102 | 103 | @app.route('/router') 104 | def router(): 105 | return render_template('router.html') 106 | 107 | 108 | @app.route('/sfc') 109 | def sfc(): 110 | return render_template('sfc.html') 111 | 112 | 113 | @app.route('/typescript') 114 | def typescript(): 115 | return render_template('typescript.html') 116 | 117 | 118 | @app.route('/vuex') 119 | def vuex(): 120 | return render_template('vuex.html') 121 | 122 | 123 | @app.route('/v0.10.3') 124 | def v0_10_3(): 125 | return render_template('vue.js_v0.10.3.html') 126 | 127 | 128 | if __name__ == '__main__': 129 | app.run(debug=True) 130 | -------------------------------------------------------------------------------- /db.cfg: -------------------------------------------------------------------------------- 1 | SQLALCHEMY_DATABASE_URI = 'sqlite:///test.db' 2 | SQLALCHEMY_TRACK_MODIFICATIONS = False 3 | SECRET_KEY = '\xfb\x12\xdf\xa1@i\xd6>V\xc0\xbb\x8fp\x16#Z\x0b\x81\xeb\x16' 4 | DEBUG = True 5 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Flask 2 | Flask-SQLAlchemy 3 | gunicorn 4 | -------------------------------------------------------------------------------- /static/css/style.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | font-family: 'Helvetica Neue', Arial, sans-serif; 3 | text-align: center; 4 | padding: 0; 5 | margin: 0; 6 | } 7 | 8 | *, *:before, *:after { 9 | -moz-box-sizing: border-box; 10 | -webkit-box-sizing: border-box; 11 | box-sizing: border-box; 12 | } 13 | 14 | .box { 15 | width: 100%; 16 | height: 300px; 17 | border-top: 1px solid #222; 18 | /*padding-top: 20px;*/ 19 | } 20 | 21 | #editor { 22 | margin: 0; 23 | height: inherit; 24 | font-family: 'Helvetica Neue', Arial, sans-serif; 25 | color: #333; 26 | text-align: left; 27 | } 28 | 29 | textarea, #editor div { 30 | display: inline-block; 31 | width: 49%; 32 | height: inherit; 33 | vertical-align: top; 34 | -webkit-box-sizing: border-box; 35 | -moz-box-sizing: border-box; 36 | box-sizing: border-box; 37 | padding: 0 20px; 38 | } 39 | 40 | #editor div { 41 | background-color: white; 42 | } 43 | 44 | textarea { 45 | border: none; 46 | border-right: 1px solid #ccc; 47 | resize: none; 48 | outline: none; 49 | background-color: #f6f6f6; 50 | font-size: 14px; 51 | font-family: 'Monaco', courier, monospace; 52 | padding: 20px; 53 | } 54 | 55 | code { 56 | color: #f66; 57 | } 58 | 59 | polygon { 60 | fill: #42b983; 61 | opacity: 0.75; 62 | } 63 | circle { 64 | fill: transparent; 65 | stroke: #999; 66 | } 67 | 68 | .div-column { 69 | margin: 0 30px; 70 | display: inline-block; 71 | } 72 | 73 | /* CSS Trick */ 74 | #slider { 75 | max-width: 600px; 76 | text-align: center; 77 | margin: 0 auto; 78 | } 79 | #overflow { 80 | width: 100%; 81 | overflow: hidden; 82 | } 83 | #slides .inner { 84 | width: 400%; 85 | } 86 | #slides .inner { 87 | -moz-transform: translateZ(0); 88 | transform: translateZ(0); 89 | -moz-transition: all 800ms cubic-bezier(0.770, 0.000, 0.175, 1.000); 90 | transition: all 800ms cubic-bezier(0.770, 0.000, 0.175, 1.000); 91 | -moz-transition-timing-function: cubic-bezier(0.770, 0.000, 0.175, 1.000); 92 | transition-timing-function: cubic-bezier(0.770, 0.000, 0.175, 1.000); 93 | } 94 | #slides article { 95 | width: 25%; 96 | float: left; 97 | } 98 | #slide1:checked ~ #slides .inner { 99 | margin-left: 0; 100 | } 101 | #slide2:checked ~ #slides .inner { 102 | margin-left: -100%; 103 | } 104 | #slide3:checked ~ #slides .inner { 105 | margin-left: -200%; 106 | } 107 | #slide4:checked ~ #slides .inner { 108 | margin-left: -300%; 109 | } 110 | input[type="radio"] { 111 | display: none; 112 | } 113 | 114 | label { 115 | background: #CCC; 116 | display: inline-block; 117 | cursor: pointer; 118 | width: 10px; 119 | height: 10px; 120 | border-radius: 5px; 121 | } 122 | 123 | #slide1:checked ~ label[for="slide1"], 124 | #slide2:checked ~ label[for="slide2"], 125 | #slide3:checked ~ label[for="slide3"], 126 | #slide4:checked ~ label[for="slide4"] { 127 | background: #333; 128 | } 129 | -------------------------------------------------------------------------------- /static/js/general.js: -------------------------------------------------------------------------------- 1 | Vue.config({ 2 | delimiters: ['[', ']'] 3 | }) 4 | 5 | var simple = new Vue({ 6 | el: "#simple", 7 | data: { 8 | message: 'bokete' 9 | } 10 | }) 11 | 12 | var editor = new Vue({ 13 | el: '#editor', 14 | data: { 15 | input: '# hello' 16 | }, 17 | filters: { 18 | marked: marked 19 | } 20 | }) 21 | 22 | var apiUrl = 'https://api.github.com/repos/yymm/Yoichi/commits?per_page=3&sha=' 23 | var commits = new Vue({ 24 | el: "#commits", 25 | data: { 26 | branch: "master" 27 | }, 28 | created: function(){ 29 | this.$watch('branch', function() { 30 | this.fetchData(); 31 | }) 32 | }, 33 | filters: { 34 | truncate: function(value){ 35 | var newline = value.indexOf('\n'); 36 | return newline > -1 ? value.slice(0, newline) : value; 37 | }, 38 | formatDate: function(value){ 39 | return value.replace(/T|Z/g, ' '); 40 | } 41 | }, 42 | methods: { 43 | fetchData: function(){ 44 | // self = thisは肝。 45 | // onload内のcommitsをVueオブジェクト内の参照にするために必須。 46 | // thisを使うとonload実行時のthis(=多分windowオブジェクト)になってしまう。 47 | var xhr = new XMLHttpRequest(), self = this; 48 | xhr.open('GET', apiUrl + self.branch); 49 | xhr.onload = function(){ 50 | self.commits = JSON.parse(xhr.responseText); 51 | } 52 | xhr.send(); 53 | } 54 | } 55 | }) 56 | 57 | var emailRE = emailRE = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ 58 | 59 | var adduser = new Vue({ 60 | el: "#adduser", 61 | data: { 62 | users: [], 63 | newUser: { 64 | name: '', 65 | email: '' 66 | }, 67 | validation: { 68 | name: false, 69 | email: false 70 | } 71 | }, 72 | filters: { 73 | nameValidator: function(val){ 74 | this.validation.name = !!val; 75 | return val; 76 | }, 77 | emailValidator: function(val){ 78 | this.validation.email = emailRE.test(val); 79 | return val; 80 | } 81 | }, 82 | methods: { 83 | addUser: function(e){ 84 | e.preventDefault(); 85 | if(this.validation.name && this.validation.email){ 86 | var user = this.newUser; 87 | this.users.push(user); 88 | this.newUser = {}; 89 | } 90 | } 91 | } 92 | }) 93 | 94 | var data = [ 95 | { name: 'Chuck Norris', power: Infinity }, 96 | { name: 'Goku', power: 20000 }, 97 | { name: 'Vegeta', power: 18000 }, 98 | { name: 'Freeza', power: 530000 } 99 | ] 100 | 101 | Vue.component('grid', { 102 | template: "#grid-template", 103 | replace: true, 104 | created: function(){ 105 | this.ascending = {}; 106 | }, 107 | methods: { 108 | sortBy: function(key){ 109 | var asc = this.ascending[key] = !this.ascending[key]; 110 | this.data.sort(function(a, b){ 111 | var res = a[key] > b[key]; 112 | if (asc) res = !res; 113 | return res ? 1 : -1; 114 | }); 115 | } 116 | } 117 | }) 118 | 119 | var gridcomponent = new Vue({ 120 | el: "#grid-component", 121 | data: { 122 | gridOptions: { 123 | data: data, 124 | columns: [ 125 | { header: 'Name', key: 'name' }, 126 | { header: 'Power', key: 'power' } 127 | ] 128 | } 129 | } 130 | }) 131 | 132 | var stats = [ 133 | { label: 'A', value: 100 }, 134 | { label: 'B', value: 100 }, 135 | { label: 'C', value: 100 }, 136 | { label: 'D', value: 100 }, 137 | { label: 'E', value: 100 }, 138 | { label: 'F', value: 100 } 139 | ] 140 | 141 | Vue.component('polygraph', { 142 | template: '#polygraph-template', 143 | replace: true, 144 | computed: { 145 | points: function(){ 146 | var total = this.stats.length; 147 | return this.stats.map(function(stat, i){ 148 | var point = valueToPoint(stat.value, i, total); 149 | return point.x + ',' + point.y; 150 | }).join(' ') 151 | } 152 | }, 153 | components: { 154 | 'axis-label': { 155 | computed: { 156 | point: function(){ 157 | return valueToPoint(+this.value + 10, this.$index, this.$parent.stats.length); 158 | }, 159 | x: function(){ 160 | return this.point.x; 161 | }, 162 | y: function(){ 163 | return this.point.y; 164 | } 165 | } 166 | } 167 | } 168 | }) 169 | 170 | function valueToPoint(value, index, total){ 171 | var x = 0, 172 | y = -value * 0.8, 173 | angle = Math.PI * 2 / total * index, 174 | cos = Math.cos(angle), 175 | sin = Math.sin(angle), 176 | tx = x * cos - y * sin + 100, 177 | ty = x * sin + y * cos + 100; 178 | return { 179 | x: tx, 180 | y: ty 181 | } 182 | } 183 | 184 | 185 | var svgsample = new Vue({ 186 | el: '#svg-sample', 187 | data: { 188 | newLabel: '', 189 | stats: stats 190 | }, 191 | filters: { 192 | format: function(stats){ 193 | return JSON.stringify(stats, null, 2); 194 | } 195 | }, 196 | methods: { 197 | add: function(){ 198 | if(!this.newLabel) return; 199 | this.stats.push({ 200 | label: this.newLabel, 201 | value: 100 202 | }); 203 | this.newLabel = ''; 204 | }, 205 | remove: function(stat){ 206 | if(this.stats.length > 3){ 207 | this.stats.remove(stat.$data); 208 | } 209 | } 210 | } 211 | }) 212 | 213 | 214 | Vue.component('img-slider', { 215 | template: '#img-slider-template', 216 | replace: true 217 | }) 218 | 219 | new Vue({el: '#image-slider'}) 220 | -------------------------------------------------------------------------------- /static/js/marked.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * marked - a markdown parser 3 | * Copyright (c) 2011-2013, Christopher Jeffrey. (MIT Licensed) 4 | * https://github.com/chjj/marked 5 | */ 6 | (function(){var block={newline:/^\n+/,code:/^( {4}[^\n]+\n*)+/,fences:noop,hr:/^( *[-*_]){3,} *(?:\n+|$)/,heading:/^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/,nptable:noop,lheading:/^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/,blockquote:/^( *>[^\n]+(\n[^\n]+)*\n*)+/,list:/^( *)(bull) [\s\S]+?(?:hr|\n{2,}(?! )(?!\1bull )\n*|\s*$)/,html:/^ *(?:comment|closed|closing) *(?:\n{2,}|\s*$)/,def:/^ *\[([^\]]+)\]: *]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/,table:noop,paragraph:/^((?:[^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+)\n*/,text:/^[^\n]+/};block.bullet=/(?:[*+-]|\d+\.)/;block.item=/^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/;block.item=replace(block.item,"gm")(/bull/g,block.bullet)();block.list=replace(block.list)(/bull/g,block.bullet)("hr",/\n+(?=(?: *[-*_]){3,} *(?:\n+|$))/)();block._tag="(?!(?:"+"a|em|strong|small|s|cite|q|dfn|abbr|data|time|code"+"|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo"+"|span|br|wbr|ins|del|img)\\b)\\w+(?!:/|[^\\w\\s@]*@)\\b";block.html=replace(block.html)("comment",//)("closed",/<(tag)[\s\S]+?<\/\1>/)("closing",/])*?>/)(/tag/g,block._tag)();block.paragraph=replace(block.paragraph)("hr",block.hr)("heading",block.heading)("lheading",block.lheading)("blockquote",block.blockquote)("tag","<"+block._tag)("def",block.def)();block.normal=merge({},block);block.gfm=merge({},block.normal,{fences:/^ *(`{3,}|~{3,}) *(\S+)? *\n([\s\S]+?)\s*\1 *(?:\n+|$)/,paragraph:/^/});block.gfm.paragraph=replace(block.paragraph)("(?!","(?!"+block.gfm.fences.source.replace("\\1","\\2")+"|"+block.list.source.replace("\\1","\\3")+"|")();block.tables=merge({},block.gfm,{nptable:/^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/,table:/^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/});function Lexer(options){this.tokens=[];this.tokens.links={};this.options=options||marked.defaults;this.rules=block.normal;if(this.options.gfm){if(this.options.tables){this.rules=block.tables}else{this.rules=block.gfm}}}Lexer.rules=block;Lexer.lex=function(src,options){var lexer=new Lexer(options);return lexer.lex(src)};Lexer.prototype.lex=function(src){src=src.replace(/\r\n|\r/g,"\n").replace(/\t/g," ").replace(/\u00a0/g," ").replace(/\u2424/g,"\n");return this.token(src,true)};Lexer.prototype.token=function(src,top){var src=src.replace(/^ +$/gm,""),next,loose,cap,bull,b,item,space,i,l;while(src){if(cap=this.rules.newline.exec(src)){src=src.substring(cap[0].length);if(cap[0].length>1){this.tokens.push({type:"space"})}}if(cap=this.rules.code.exec(src)){src=src.substring(cap[0].length);cap=cap[0].replace(/^ {4}/gm,"");this.tokens.push({type:"code",text:!this.options.pedantic?cap.replace(/\n+$/,""):cap});continue}if(cap=this.rules.fences.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:"code",lang:cap[2],text:cap[3]});continue}if(cap=this.rules.heading.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:"heading",depth:cap[1].length,text:cap[2]});continue}if(top&&(cap=this.rules.nptable.exec(src))){src=src.substring(cap[0].length);item={type:"table",header:cap[1].replace(/^ *| *\| *$/g,"").split(/ *\| */),align:cap[2].replace(/^ *|\| *$/g,"").split(/ *\| */),cells:cap[3].replace(/\n$/,"").split("\n")};for(i=0;i ?/gm,"");this.token(cap,top);this.tokens.push({type:"blockquote_end"});continue}if(cap=this.rules.list.exec(src)){src=src.substring(cap[0].length);bull=cap[2];this.tokens.push({type:"list_start",ordered:bull.length>1});cap=cap[0].match(this.rules.item);next=false;l=cap.length;i=0;for(;i1&&b.length>1)){src=cap.slice(i+1).join("\n")+src;i=l-1}}loose=next||/\n\n(?!\s*$)/.test(item);if(i!==l-1){next=item.charAt(item.length-1)==="\n";if(!loose)loose=next}this.tokens.push({type:loose?"loose_item_start":"list_item_start"});this.token(item,false);this.tokens.push({type:"list_item_end"})}this.tokens.push({type:"list_end"});continue}if(cap=this.rules.html.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:this.options.sanitize?"paragraph":"html",pre:cap[1]==="pre"||cap[1]==="script"||cap[1]==="style",text:cap[0]});continue}if(top&&(cap=this.rules.def.exec(src))){src=src.substring(cap[0].length);this.tokens.links[cap[1].toLowerCase()]={href:cap[2],title:cap[3]};continue}if(top&&(cap=this.rules.table.exec(src))){src=src.substring(cap[0].length);item={type:"table",header:cap[1].replace(/^ *| *\| *$/g,"").split(/ *\| */),align:cap[2].replace(/^ *|\| *$/g,"").split(/ *\| */),cells:cap[3].replace(/(?: *\| *)?\n$/,"").split("\n")};for(i=0;i])/,autolink:/^<([^ >]+(@|:\/)[^ >]+)>/,url:noop,tag:/^|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/,link:/^!?\[(inside)\]\(href\)/,reflink:/^!?\[(inside)\]\s*\[([^\]]*)\]/,nolink:/^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/,strong:/^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/,em:/^\b_((?:__|[\s\S])+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/,code:/^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/,br:/^ {2,}\n(?!\s*$)/,del:noop,text:/^[\s\S]+?(?=[\\?(?:\s+['"]([\s\S]*?)['"])?\s*/;inline.link=replace(inline.link)("inside",inline._inside)("href",inline._href)();inline.reflink=replace(inline.reflink)("inside",inline._inside)();inline.normal=merge({},inline);inline.pedantic=merge({},inline.normal,{strong:/^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,em:/^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/});inline.gfm=merge({},inline.normal,{escape:replace(inline.escape)("])","~|])")(),url:/^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/,del:/^~~(?=\S)([\s\S]*?\S)~~/,text:replace(inline.text)("]|","~]|")("|","|https?://|")()});inline.breaks=merge({},inline.gfm,{br:replace(inline.br)("{2,}","*")(),text:replace(inline.gfm.text)("{2,}","*")()});function InlineLexer(links,options){this.options=options||marked.defaults;this.links=links;this.rules=inline.normal;this.renderer=this.options.renderer||new Renderer;this.renderer.options=this.options;if(!this.links){throw new Error("Tokens array requires a `links` property.")}if(this.options.gfm){if(this.options.breaks){this.rules=inline.breaks}else{this.rules=inline.gfm}}else if(this.options.pedantic){this.rules=inline.pedantic}}InlineLexer.rules=inline;InlineLexer.output=function(src,links,options){var inline=new InlineLexer(links,options);return inline.output(src)};InlineLexer.prototype.output=function(src){var out="",link,text,href,cap;while(src){if(cap=this.rules.escape.exec(src)){src=src.substring(cap[0].length);out+=cap[1];continue}if(cap=this.rules.autolink.exec(src)){src=src.substring(cap[0].length);if(cap[2]==="@"){text=cap[1].charAt(6)===":"?this.mangle(cap[1].substring(7)):this.mangle(cap[1]);href=this.mangle("mailto:")+text}else{text=escape(cap[1]);href=text}out+=this.renderer.link(href,null,text);continue}if(cap=this.rules.url.exec(src)){src=src.substring(cap[0].length);text=escape(cap[1]);href=text;out+=this.renderer.link(href,null,text);continue}if(cap=this.rules.tag.exec(src)){src=src.substring(cap[0].length);out+=this.options.sanitize?escape(cap[0]):cap[0];continue}if(cap=this.rules.link.exec(src)){src=src.substring(cap[0].length);out+=this.outputLink(cap,{href:cap[2],title:cap[3]});continue}if((cap=this.rules.reflink.exec(src))||(cap=this.rules.nolink.exec(src))){src=src.substring(cap[0].length);link=(cap[2]||cap[1]).replace(/\s+/g," ");link=this.links[link.toLowerCase()];if(!link||!link.href){out+=cap[0].charAt(0);src=cap[0].substring(1)+src;continue}out+=this.outputLink(cap,link);continue}if(cap=this.rules.strong.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.strong(this.output(cap[2]||cap[1]));continue}if(cap=this.rules.em.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.em(this.output(cap[2]||cap[1]));continue}if(cap=this.rules.code.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.codespan(escape(cap[2],true));continue}if(cap=this.rules.br.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.br();continue}if(cap=this.rules.del.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.del(this.output(cap[1]));continue}if(cap=this.rules.text.exec(src)){src=src.substring(cap[0].length);out+=escape(this.smartypants(cap[0]));continue}if(src){throw new Error("Infinite loop on byte: "+src.charCodeAt(0))}}return out};InlineLexer.prototype.outputLink=function(cap,link){var href=escape(link.href),title=link.title?escape(link.title):null;return cap[0].charAt(0)!=="!"?this.renderer.link(href,title,this.output(cap[1])):this.renderer.image(href,title,escape(cap[1]))};InlineLexer.prototype.smartypants=function(text){if(!this.options.smartypants)return text;return text.replace(/--/g,"—").replace(/(^|[-\u2014/(\[{"\s])'/g,"$1‘").replace(/'/g,"’").replace(/(^|[-\u2014/(\[{\u2018\s])"/g,"$1“").replace(/"/g,"”").replace(/\.{3}/g,"…")};InlineLexer.prototype.mangle=function(text){var out="",l=text.length,i=0,ch;for(;i.5){ch="x"+ch.toString(16)}out+="&#"+ch+";"}return out};function Renderer(options){this.options=options||{}}Renderer.prototype.code=function(code,lang,escaped){if(this.options.highlight){var out=this.options.highlight(code,lang);if(out!=null&&out!==code){escaped=true;code=out}}if(!lang){return"
"+(escaped?code:escape(code,true))+"\n
"}return'
'+(escaped?code:escape(code,true))+"\n
\n"};Renderer.prototype.blockquote=function(quote){return"
\n"+quote+"
\n"};Renderer.prototype.html=function(html){return html};Renderer.prototype.heading=function(text,level,raw){return"'+text+"\n"};Renderer.prototype.hr=function(){return"
\n"};Renderer.prototype.list=function(body,ordered){var type=ordered?"ol":"ul";return"<"+type+">\n"+body+"\n"};Renderer.prototype.listitem=function(text){return"
  • "+text+"
  • \n"};Renderer.prototype.paragraph=function(text){return"

    "+text+"

    \n"};Renderer.prototype.table=function(header,body){return"\n"+"\n"+header+"\n"+"\n"+body+"\n"+"
    \n"};Renderer.prototype.tablerow=function(content){return"\n"+content+"\n"};Renderer.prototype.tablecell=function(content,flags){var type=flags.header?"th":"td";var tag=flags.align?"<"+type+' style="text-align:'+flags.align+'">':"<"+type+">";return tag+content+"\n"};Renderer.prototype.strong=function(text){return""+text+""};Renderer.prototype.em=function(text){return""+text+""};Renderer.prototype.codespan=function(text){return""+text+""};Renderer.prototype.br=function(){return"
    "};Renderer.prototype.del=function(text){return""+text+""};Renderer.prototype.link=function(href,title,text){if(this.options.sanitize){try{var prot=decodeURIComponent(unescape(href)).replace(/[^\w:]/g,"").toLowerCase()}catch(e){return""}if(prot.indexOf("javascript:")===0){return""}}var out='";return out};Renderer.prototype.image=function(href,title,text){var out=''+text+'/g,">").replace(/"/g,""").replace(/'/g,"'")}function unescape(html){return html.replace(/&([#\w]+);/g,function(_,n){n=n.toLowerCase();if(n==="colon")return":";if(n.charAt(0)==="#"){return n.charAt(1)==="x"?String.fromCharCode(parseInt(n.substring(2),16)):String.fromCharCode(+n.substring(1))}return""})}function replace(regex,opt){regex=regex.source;opt=opt||"";return function self(name,val){if(!name)return new RegExp(regex,opt);val=val.source||val;val=val.replace(/(^|[^\[])\^/g,"$1");regex=regex.replace(name,val);return self}}function noop(){}noop.exec=noop;function merge(obj){var i=1,target,key;for(;iAn error occured:

    "+escape(e.message+"",true)+"
    "}throw e}}marked.options=marked.setOptions=function(opt){merge(marked.defaults,opt);return marked};marked.defaults={gfm:true,tables:true,breaks:false,pedantic:false,sanitize:false,smartLists:false,silent:false,highlight:null,langPrefix:"lang-",smartypants:false,headerPrefix:"",renderer:new Renderer};marked.Parser=Parser;marked.parser=Parser.parse;marked.Renderer=Renderer;marked.Lexer=Lexer;marked.lexer=Lexer.lex;marked.InlineLexer=InlineLexer;marked.inlineLexer=InlineLexer.output;marked.parse=marked;if(typeof exports==="object"){module.exports=marked}else if(typeof define==="function"&&define.amd){define(function(){return marked})}else{this.marked=marked}}).call(function(){return this||(typeof window!=="undefined"?window:global)}()); -------------------------------------------------------------------------------- /static/js/vue.js: -------------------------------------------------------------------------------- 1 | /* 2 | Vue.js v0.10.3 3 | (c) 2014 Evan You 4 | License: MIT 5 | */ 6 | ;(function(){ 7 | 'use strict'; 8 | 9 | /** 10 | * Require the given path. 11 | * 12 | * @param {String} path 13 | * @return {Object} exports 14 | * @api public 15 | */ 16 | 17 | function require(path, parent, orig) { 18 | var resolved = require.resolve(path); 19 | 20 | // lookup failed 21 | if (null == resolved) { 22 | throwError() 23 | return 24 | } 25 | 26 | var module = require.modules[resolved]; 27 | 28 | // perform real require() 29 | // by invoking the module's 30 | // registered function 31 | if (!module._resolving && !module.exports) { 32 | var mod = {}; 33 | mod.exports = {}; 34 | mod.client = mod.component = true; 35 | module._resolving = true; 36 | module.call(this, mod.exports, require.relative(resolved), mod); 37 | delete module._resolving; 38 | module.exports = mod.exports; 39 | } 40 | 41 | function throwError () { 42 | orig = orig || path; 43 | parent = parent || 'root'; 44 | var err = new Error('Failed to require "' + orig + '" from "' + parent + '"'); 45 | err.path = orig; 46 | err.parent = parent; 47 | err.require = true; 48 | throw err; 49 | } 50 | 51 | return module.exports; 52 | } 53 | 54 | /** 55 | * Registered modules. 56 | */ 57 | 58 | require.modules = {}; 59 | 60 | /** 61 | * Registered aliases. 62 | */ 63 | 64 | require.aliases = {}; 65 | 66 | /** 67 | * Resolve `path`. 68 | * 69 | * Lookup: 70 | * 71 | * - PATH/index.js 72 | * - PATH.js 73 | * - PATH 74 | * 75 | * @param {String} path 76 | * @return {String} path or null 77 | * @api private 78 | */ 79 | 80 | require.exts = [ 81 | '', 82 | '.js', 83 | '.json', 84 | '/index.js', 85 | '/index.json' 86 | ]; 87 | 88 | require.resolve = function(path) { 89 | if (path.charAt(0) === '/') path = path.slice(1); 90 | 91 | for (var i = 0; i < 5; i++) { 92 | var fullPath = path + require.exts[i]; 93 | if (require.modules.hasOwnProperty(fullPath)) return fullPath; 94 | if (require.aliases.hasOwnProperty(fullPath)) return require.aliases[fullPath]; 95 | } 96 | }; 97 | 98 | /** 99 | * Normalize `path` relative to the current path. 100 | * 101 | * @param {String} curr 102 | * @param {String} path 103 | * @return {String} 104 | * @api private 105 | */ 106 | 107 | require.normalize = function(curr, path) { 108 | 109 | var segs = []; 110 | 111 | if ('.' != path.charAt(0)) return path; 112 | 113 | curr = curr.split('/'); 114 | path = path.split('/'); 115 | 116 | for (var i = 0; i < path.length; ++i) { 117 | if ('..' === path[i]) { 118 | curr.pop(); 119 | } else if ('.' != path[i] && '' != path[i]) { 120 | segs.push(path[i]); 121 | } 122 | } 123 | return curr.concat(segs).join('/'); 124 | }; 125 | 126 | /** 127 | * Register module at `path` with callback `definition`. 128 | * 129 | * @param {String} path 130 | * @param {Function} definition 131 | * @api private 132 | */ 133 | 134 | require.register = function(path, definition) { 135 | require.modules[path] = definition; 136 | }; 137 | 138 | /** 139 | * Alias a module definition. 140 | * 141 | * @param {String} from 142 | * @param {String} to 143 | * @api private 144 | */ 145 | 146 | require.alias = function(from, to) { 147 | if (!require.modules.hasOwnProperty(from)) { 148 | throwError() 149 | return 150 | } 151 | require.aliases[to] = from; 152 | 153 | function throwError () { 154 | throw new Error('Failed to alias "' + from + '", it does not exist'); 155 | } 156 | }; 157 | 158 | /** 159 | * Return a require function relative to the `parent` path. 160 | * 161 | * @param {String} parent 162 | * @return {Function} 163 | * @api private 164 | */ 165 | 166 | require.relative = function(parent) { 167 | var p = require.normalize(parent, '..'); 168 | 169 | /** 170 | * The relative require() itself. 171 | */ 172 | 173 | function localRequire(path) { 174 | var resolved = localRequire.resolve(path); 175 | return require(resolved, parent, path); 176 | } 177 | 178 | /** 179 | * Resolve relative to the parent. 180 | */ 181 | 182 | localRequire.resolve = function(path) { 183 | var c = path.charAt(0); 184 | if ('/' === c) return path.slice(1); 185 | if ('.' === c) return require.normalize(p, path); 186 | 187 | // resolve deps by returning 188 | // the dep in the nearest "deps" 189 | // directory 190 | var segs = parent.split('/'); 191 | var i = segs.length; 192 | while (i--) { 193 | if (segs[i] === 'deps') { 194 | break; 195 | } 196 | } 197 | path = segs.slice(0, i + 2).join('/') + '/deps/' + path; 198 | return path; 199 | }; 200 | 201 | /** 202 | * Check if module is defined at `path`. 203 | */ 204 | 205 | localRequire.exists = function(path) { 206 | return require.modules.hasOwnProperty(localRequire.resolve(path)); 207 | }; 208 | 209 | return localRequire; 210 | }; 211 | require.register("vue/src/main.js", function(exports, require, module){ 212 | var config = require('./config'), 213 | ViewModel = require('./viewmodel'), 214 | utils = require('./utils'), 215 | makeHash = utils.hash, 216 | assetTypes = ['directive', 'filter', 'partial', 'effect', 'component'] 217 | 218 | // require these so Browserify can catch them 219 | // so they can be used in Vue.require 220 | require('./observer') 221 | require('./transition') 222 | 223 | ViewModel.options = config.globalAssets = { 224 | directives : require('./directives'), 225 | filters : require('./filters'), 226 | partials : makeHash(), 227 | effects : makeHash(), 228 | components : makeHash() 229 | } 230 | 231 | /** 232 | * Expose asset registration methods 233 | */ 234 | assetTypes.forEach(function (type) { 235 | ViewModel[type] = function (id, value) { 236 | var hash = this.options[type + 's'] 237 | if (!hash) { 238 | hash = this.options[type + 's'] = makeHash() 239 | } 240 | if (!value) return hash[id] 241 | if (type === 'partial') { 242 | value = utils.toFragment(value) 243 | } else if (type === 'component') { 244 | value = utils.toConstructor(value) 245 | } else if (type === 'filter') { 246 | utils.checkFilter(value) 247 | } 248 | hash[id] = value 249 | return this 250 | } 251 | }) 252 | 253 | /** 254 | * Set config options 255 | */ 256 | ViewModel.config = function (opts, val) { 257 | if (typeof opts === 'string') { 258 | if (val === undefined) { 259 | return config[opts] 260 | } else { 261 | config[opts] = val 262 | } 263 | } else { 264 | utils.extend(config, opts) 265 | } 266 | return this 267 | } 268 | 269 | /** 270 | * Expose an interface for plugins 271 | */ 272 | ViewModel.use = function (plugin) { 273 | if (typeof plugin === 'string') { 274 | try { 275 | plugin = require(plugin) 276 | } catch (e) { 277 | utils.warn('Cannot find plugin: ' + plugin) 278 | return 279 | } 280 | } 281 | 282 | // additional parameters 283 | var args = [].slice.call(arguments, 1) 284 | args.unshift(this) 285 | 286 | if (typeof plugin.install === 'function') { 287 | plugin.install.apply(plugin, args) 288 | } else { 289 | plugin.apply(null, args) 290 | } 291 | return this 292 | } 293 | 294 | /** 295 | * Expose internal modules for plugins 296 | */ 297 | ViewModel.require = function (path) { 298 | return require('./' + path) 299 | } 300 | 301 | ViewModel.extend = extend 302 | ViewModel.nextTick = utils.nextTick 303 | 304 | /** 305 | * Expose the main ViewModel class 306 | * and add extend method 307 | */ 308 | function extend (options) { 309 | 310 | var ParentVM = this 311 | 312 | // extend data options need to be copied 313 | // on instantiation 314 | if (options.data) { 315 | options.defaultData = options.data 316 | delete options.data 317 | } 318 | 319 | // inherit options 320 | options = inheritOptions(options, ParentVM.options, true) 321 | utils.processOptions(options) 322 | 323 | var ExtendedVM = function (opts, asParent) { 324 | if (!asParent) { 325 | opts = inheritOptions(opts, options, true) 326 | } 327 | ParentVM.call(this, opts, true) 328 | } 329 | 330 | // inherit prototype props 331 | var proto = ExtendedVM.prototype = Object.create(ParentVM.prototype) 332 | utils.defProtected(proto, 'constructor', ExtendedVM) 333 | 334 | // allow extended VM to be further extended 335 | ExtendedVM.extend = extend 336 | ExtendedVM.super = ParentVM 337 | ExtendedVM.options = options 338 | 339 | // allow extended VM to add its own assets 340 | assetTypes.forEach(function (type) { 341 | ExtendedVM[type] = ViewModel[type] 342 | }) 343 | 344 | // allow extended VM to use plugins 345 | ExtendedVM.use = ViewModel.use 346 | ExtendedVM.require = ViewModel.require 347 | 348 | return ExtendedVM 349 | } 350 | 351 | /** 352 | * Inherit options 353 | * 354 | * For options such as `data`, `vms`, `directives`, 'partials', 355 | * they should be further extended. However extending should only 356 | * be done at top level. 357 | * 358 | * `proto` is an exception because it's handled directly on the 359 | * prototype. 360 | * 361 | * `el` is an exception because it's not allowed as an 362 | * extension option, but only as an instance option. 363 | */ 364 | function inheritOptions (child, parent, topLevel) { 365 | child = child || {} 366 | if (!parent) return child 367 | for (var key in parent) { 368 | if (key === 'el') continue 369 | var val = child[key], 370 | parentVal = parent[key] 371 | if (topLevel && typeof val === 'function' && parentVal) { 372 | // merge hook functions into an array 373 | child[key] = [val] 374 | if (Array.isArray(parentVal)) { 375 | child[key] = child[key].concat(parentVal) 376 | } else { 377 | child[key].push(parentVal) 378 | } 379 | } else if ( 380 | topLevel && 381 | (utils.isTrueObject(val) || utils.isTrueObject(parentVal)) 382 | && !(parentVal instanceof ViewModel) 383 | ) { 384 | // merge toplevel object options 385 | child[key] = inheritOptions(val, parentVal) 386 | } else if (val === undefined) { 387 | // inherit if child doesn't override 388 | child[key] = parentVal 389 | } 390 | } 391 | return child 392 | } 393 | 394 | module.exports = ViewModel 395 | }); 396 | require.register("vue/src/emitter.js", function(exports, require, module){ 397 | function Emitter (ctx) { 398 | this._ctx = ctx || this 399 | } 400 | 401 | var EmitterProto = Emitter.prototype 402 | 403 | EmitterProto.on = function(event, fn){ 404 | this._cbs = this._cbs || {} 405 | ;(this._cbs[event] = this._cbs[event] || []) 406 | .push(fn) 407 | return this 408 | } 409 | 410 | EmitterProto.once = function(event, fn){ 411 | var self = this 412 | this._cbs = this._cbs || {} 413 | 414 | function on () { 415 | self.off(event, on) 416 | fn.apply(this, arguments) 417 | } 418 | 419 | on.fn = fn 420 | this.on(event, on) 421 | return this 422 | } 423 | 424 | EmitterProto.off = function(event, fn){ 425 | this._cbs = this._cbs || {} 426 | 427 | // all 428 | if (!arguments.length) { 429 | this._cbs = {} 430 | return this 431 | } 432 | 433 | // specific event 434 | var callbacks = this._cbs[event] 435 | if (!callbacks) return this 436 | 437 | // remove all handlers 438 | if (arguments.length === 1) { 439 | delete this._cbs[event] 440 | return this 441 | } 442 | 443 | // remove specific handler 444 | var cb 445 | for (var i = 0; i < callbacks.length; i++) { 446 | cb = callbacks[i] 447 | if (cb === fn || cb.fn === fn) { 448 | callbacks.splice(i, 1) 449 | break 450 | } 451 | } 452 | return this 453 | } 454 | 455 | EmitterProto.emit = function(event, a, b, c){ 456 | this._cbs = this._cbs || {} 457 | var callbacks = this._cbs[event] 458 | 459 | if (callbacks) { 460 | callbacks = callbacks.slice(0) 461 | for (var i = 0, len = callbacks.length; i < len; i++) { 462 | callbacks[i].call(this._ctx, a, b, c) 463 | } 464 | } 465 | 466 | return this 467 | } 468 | 469 | module.exports = Emitter 470 | }); 471 | require.register("vue/src/config.js", function(exports, require, module){ 472 | var TextParser = require('./text-parser') 473 | 474 | module.exports = { 475 | prefix : 'v', 476 | debug : false, 477 | silent : false, 478 | enterClass : 'v-enter', 479 | leaveClass : 'v-leave', 480 | interpolate : true 481 | } 482 | 483 | Object.defineProperty(module.exports, 'delimiters', { 484 | get: function () { 485 | return TextParser.delimiters 486 | }, 487 | set: function (delimiters) { 488 | TextParser.setDelimiters(delimiters) 489 | } 490 | }) 491 | }); 492 | require.register("vue/src/utils.js", function(exports, require, module){ 493 | var config = require('./config'), 494 | toString = ({}).toString, 495 | win = window, 496 | console = win.console, 497 | timeout = win.setTimeout, 498 | def = Object.defineProperty, 499 | THIS_RE = /[^\w]this[^\w]/, 500 | OBJECT = 'object', 501 | hasClassList = 'classList' in document.documentElement, 502 | ViewModel // late def 503 | 504 | var utils = module.exports = { 505 | 506 | /** 507 | * get a value from an object keypath 508 | */ 509 | get: function (obj, key) { 510 | /* jshint eqeqeq: false */ 511 | if (key.indexOf('.') < 0) { 512 | return obj[key] 513 | } 514 | var path = key.split('.'), 515 | d = -1, l = path.length 516 | while (++d < l && obj != null) { 517 | obj = obj[path[d]] 518 | } 519 | return obj 520 | }, 521 | 522 | /** 523 | * set a value to an object keypath 524 | */ 525 | set: function (obj, key, val) { 526 | /* jshint eqeqeq: false */ 527 | if (key.indexOf('.') < 0) { 528 | obj[key] = val 529 | return 530 | } 531 | var path = key.split('.'), 532 | d = -1, l = path.length - 1 533 | while (++d < l) { 534 | if (obj[path[d]] == null) { 535 | obj[path[d]] = {} 536 | } 537 | obj = obj[path[d]] 538 | } 539 | obj[path[d]] = val 540 | }, 541 | 542 | /** 543 | * return the base segment of a keypath 544 | */ 545 | baseKey: function (key) { 546 | return key.indexOf('.') > 0 547 | ? key.split('.')[0] 548 | : key 549 | }, 550 | 551 | /** 552 | * Create a prototype-less object 553 | * which is a better hash/map 554 | */ 555 | hash: function () { 556 | return Object.create(null) 557 | }, 558 | 559 | /** 560 | * get an attribute and remove it. 561 | */ 562 | attr: function (el, type) { 563 | var attr = config.prefix + '-' + type, 564 | val = el.getAttribute(attr) 565 | if (val !== null) { 566 | el.removeAttribute(attr) 567 | } 568 | return val 569 | }, 570 | 571 | /** 572 | * Define an ienumerable property 573 | * This avoids it being included in JSON.stringify 574 | * or for...in loops. 575 | */ 576 | defProtected: function (obj, key, val, enumerable, writable) { 577 | def(obj, key, { 578 | value : val, 579 | enumerable : enumerable, 580 | writable : writable, 581 | configurable : true 582 | }) 583 | }, 584 | 585 | /** 586 | * A less bullet-proof but more efficient type check 587 | * than Object.prototype.toString 588 | */ 589 | isObject: function (obj) { 590 | return typeof obj === OBJECT && obj && !Array.isArray(obj) 591 | }, 592 | 593 | /** 594 | * A more accurate but less efficient type check 595 | */ 596 | isTrueObject: function (obj) { 597 | return toString.call(obj) === '[object Object]' 598 | }, 599 | 600 | /** 601 | * Most simple bind 602 | * enough for the usecase and fast than native bind() 603 | */ 604 | bind: function (fn, ctx) { 605 | return function (arg) { 606 | return fn.call(ctx, arg) 607 | } 608 | }, 609 | 610 | /** 611 | * Make sure null and undefined output empty string 612 | */ 613 | guard: function (value) { 614 | /* jshint eqeqeq: false, eqnull: true */ 615 | return value == null 616 | ? '' 617 | : (typeof value == 'object') 618 | ? JSON.stringify(value) 619 | : value 620 | }, 621 | 622 | /** 623 | * When setting value on the VM, parse possible numbers 624 | */ 625 | checkNumber: function (value) { 626 | return (isNaN(value) || value === null || typeof value === 'boolean') 627 | ? value 628 | : Number(value) 629 | }, 630 | 631 | /** 632 | * simple extend 633 | */ 634 | extend: function (obj, ext) { 635 | for (var key in ext) { 636 | if (obj[key] !== ext[key]) { 637 | obj[key] = ext[key] 638 | } 639 | } 640 | return obj 641 | }, 642 | 643 | /** 644 | * filter an array with duplicates into uniques 645 | */ 646 | unique: function (arr) { 647 | var hash = utils.hash(), 648 | i = arr.length, 649 | key, res = [] 650 | while (i--) { 651 | key = arr[i] 652 | if (hash[key]) continue 653 | hash[key] = 1 654 | res.push(key) 655 | } 656 | return res 657 | }, 658 | 659 | /** 660 | * Convert a string template to a dom fragment 661 | */ 662 | toFragment: function (template) { 663 | if (typeof template !== 'string') { 664 | return template 665 | } 666 | if (template.charAt(0) === '#') { 667 | var templateNode = document.getElementById(template.slice(1)) 668 | if (!templateNode) return 669 | // if its a template tag and the browser supports it, 670 | // its content is already a document fragment! 671 | if (templateNode.tagName === 'TEMPLATE' && templateNode.content) { 672 | return templateNode.content 673 | } 674 | template = templateNode.innerHTML 675 | } 676 | var node = document.createElement('div'), 677 | frag = document.createDocumentFragment(), 678 | child 679 | node.innerHTML = template.trim() 680 | /* jshint boss: true */ 681 | while (child = node.firstChild) { 682 | if (node.nodeType === 1) { 683 | frag.appendChild(child) 684 | } 685 | } 686 | return frag 687 | }, 688 | 689 | /** 690 | * Convert the object to a ViewModel constructor 691 | * if it is not already one 692 | */ 693 | toConstructor: function (obj) { 694 | ViewModel = ViewModel || require('./viewmodel') 695 | return utils.isObject(obj) 696 | ? ViewModel.extend(obj) 697 | : typeof obj === 'function' 698 | ? obj 699 | : null 700 | }, 701 | 702 | /** 703 | * Check if a filter function contains references to `this` 704 | * If yes, mark it as a computed filter. 705 | */ 706 | checkFilter: function (filter) { 707 | if (THIS_RE.test(filter.toString())) { 708 | filter.computed = true 709 | } 710 | }, 711 | 712 | /** 713 | * convert certain option values to the desired format. 714 | */ 715 | processOptions: function (options) { 716 | var components = options.components, 717 | partials = options.partials, 718 | template = options.template, 719 | filters = options.filters, 720 | key 721 | if (components) { 722 | for (key in components) { 723 | components[key] = utils.toConstructor(components[key]) 724 | } 725 | } 726 | if (partials) { 727 | for (key in partials) { 728 | partials[key] = utils.toFragment(partials[key]) 729 | } 730 | } 731 | if (filters) { 732 | for (key in filters) { 733 | utils.checkFilter(filters[key]) 734 | } 735 | } 736 | if (template) { 737 | options.template = utils.toFragment(template) 738 | } 739 | }, 740 | 741 | /** 742 | * used to defer batch updates 743 | */ 744 | nextTick: function (cb) { 745 | timeout(cb, 0) 746 | }, 747 | 748 | /** 749 | * add class for IE9 750 | * uses classList if available 751 | */ 752 | addClass: function (el, cls) { 753 | if (hasClassList) { 754 | el.classList.add(cls) 755 | } else { 756 | var cur = ' ' + el.className + ' ' 757 | if (cur.indexOf(' ' + cls + ' ') < 0) { 758 | el.className = (cur + cls).trim() 759 | } 760 | } 761 | }, 762 | 763 | /** 764 | * remove class for IE9 765 | */ 766 | removeClass: function (el, cls) { 767 | if (hasClassList) { 768 | el.classList.remove(cls) 769 | } else { 770 | var cur = ' ' + el.className + ' ', 771 | tar = ' ' + cls + ' ' 772 | while (cur.indexOf(tar) >= 0) { 773 | cur = cur.replace(tar, ' ') 774 | } 775 | el.className = cur.trim() 776 | } 777 | }, 778 | 779 | /** 780 | * Convert an object to Array 781 | * used in v-repeat and array filters 782 | */ 783 | objectToArray: function (obj) { 784 | var res = [], val, data 785 | for (var key in obj) { 786 | val = obj[key] 787 | data = utils.isObject(val) 788 | ? val 789 | : { $value: val } 790 | data.$key = key 791 | res.push(data) 792 | } 793 | return res 794 | } 795 | } 796 | 797 | enableDebug() 798 | function enableDebug () { 799 | /** 800 | * log for debugging 801 | */ 802 | utils.log = function (msg) { 803 | if (config.debug && console) { 804 | console.log(msg) 805 | } 806 | } 807 | 808 | /** 809 | * warnings, traces by default 810 | * can be suppressed by `silent` option. 811 | */ 812 | utils.warn = function (msg) { 813 | if (!config.silent && console) { 814 | console.warn(msg) 815 | if (config.debug && console.trace) { 816 | console.trace() 817 | } 818 | } 819 | } 820 | } 821 | }); 822 | require.register("vue/src/compiler.js", function(exports, require, module){ 823 | var Emitter = require('./emitter'), 824 | Observer = require('./observer'), 825 | config = require('./config'), 826 | utils = require('./utils'), 827 | Binding = require('./binding'), 828 | Directive = require('./directive'), 829 | TextParser = require('./text-parser'), 830 | DepsParser = require('./deps-parser'), 831 | ExpParser = require('./exp-parser'), 832 | ViewModel, 833 | 834 | // cache methods 835 | slice = [].slice, 836 | extend = utils.extend, 837 | hasOwn = ({}).hasOwnProperty, 838 | def = Object.defineProperty, 839 | 840 | // hooks to register 841 | hooks = [ 842 | 'created', 'ready', 843 | 'beforeDestroy', 'afterDestroy', 844 | 'attached', 'detached' 845 | ], 846 | 847 | // list of priority directives 848 | // that needs to be checked in specific order 849 | priorityDirectives = [ 850 | 'if', 851 | 'repeat', 852 | 'view', 853 | 'component' 854 | ] 855 | 856 | /** 857 | * The DOM compiler 858 | * scans a DOM node and compile bindings for a ViewModel 859 | */ 860 | function Compiler (vm, options) { 861 | 862 | var compiler = this, 863 | key, i 864 | 865 | // default state 866 | compiler.init = true 867 | compiler.destroyed = false 868 | 869 | // process and extend options 870 | options = compiler.options = options || {} 871 | utils.processOptions(options) 872 | 873 | // copy compiler options 874 | extend(compiler, options.compilerOptions) 875 | // repeat indicates this is a v-repeat instance 876 | compiler.repeat = compiler.repeat || false 877 | // expCache will be shared between v-repeat instances 878 | compiler.expCache = compiler.expCache || {} 879 | 880 | // initialize element 881 | var el = compiler.el = compiler.setupElement(options) 882 | utils.log('\nnew VM instance: ' + el.tagName + '\n') 883 | 884 | // set other compiler properties 885 | compiler.vm = el.vue_vm = vm 886 | compiler.bindings = utils.hash() 887 | compiler.dirs = [] 888 | compiler.deferred = [] 889 | compiler.computed = [] 890 | compiler.children = [] 891 | compiler.emitter = new Emitter(vm) 892 | 893 | // create bindings for computed properties 894 | if (options.methods) { 895 | for (key in options.methods) { 896 | compiler.createBinding(key) 897 | } 898 | } 899 | 900 | // create bindings for methods 901 | if (options.computed) { 902 | for (key in options.computed) { 903 | compiler.createBinding(key) 904 | } 905 | } 906 | 907 | // VM --------------------------------------------------------------------- 908 | 909 | // set VM properties 910 | vm.$ = {} 911 | vm.$el = el 912 | vm.$options = options 913 | vm.$compiler = compiler 914 | vm.$event = null 915 | 916 | // set parent & root 917 | var parentVM = options.parent 918 | if (parentVM) { 919 | compiler.parent = parentVM.$compiler 920 | parentVM.$compiler.children.push(compiler) 921 | vm.$parent = parentVM 922 | } 923 | vm.$root = getRoot(compiler).vm 924 | 925 | // DATA ------------------------------------------------------------------- 926 | 927 | // setup observer 928 | // this is necesarry for all hooks and data observation events 929 | compiler.setupObserver() 930 | 931 | // initialize data 932 | var data = compiler.data = options.data || {}, 933 | defaultData = options.defaultData 934 | if (defaultData) { 935 | for (key in defaultData) { 936 | if (!hasOwn.call(data, key)) { 937 | data[key] = defaultData[key] 938 | } 939 | } 940 | } 941 | 942 | // copy paramAttributes 943 | var params = options.paramAttributes 944 | if (params) { 945 | i = params.length 946 | while (i--) { 947 | data[params[i]] = utils.checkNumber( 948 | compiler.eval( 949 | el.getAttribute(params[i]) 950 | ) 951 | ) 952 | } 953 | } 954 | 955 | // copy data properties to vm 956 | // so user can access them in the created hook 957 | extend(vm, data) 958 | vm.$data = data 959 | 960 | // beforeCompile hook 961 | compiler.execHook('created') 962 | 963 | // the user might have swapped the data ... 964 | data = compiler.data = vm.$data 965 | 966 | // user might also set some properties on the vm 967 | // in which case we should copy back to $data 968 | var vmProp 969 | for (key in vm) { 970 | vmProp = vm[key] 971 | if ( 972 | key.charAt(0) !== '$' && 973 | data[key] !== vmProp && 974 | typeof vmProp !== 'function' 975 | ) { 976 | data[key] = vmProp 977 | } 978 | } 979 | 980 | // now we can observe the data. 981 | // this will convert data properties to getter/setters 982 | // and emit the first batch of set events, which will 983 | // in turn create the corresponding bindings. 984 | compiler.observeData(data) 985 | 986 | // COMPILE ---------------------------------------------------------------- 987 | 988 | // before compiling, resolve content insertion points 989 | if (options.template) { 990 | this.resolveContent() 991 | } 992 | 993 | // now parse the DOM and bind directives. 994 | // During this stage, we will also create bindings for 995 | // encountered keypaths that don't have a binding yet. 996 | compiler.compile(el, true) 997 | 998 | // Any directive that creates child VMs are deferred 999 | // so that when they are compiled, all bindings on the 1000 | // parent VM have been created. 1001 | i = compiler.deferred.length 1002 | while (i--) { 1003 | compiler.bindDirective(compiler.deferred[i]) 1004 | } 1005 | compiler.deferred = null 1006 | 1007 | // extract dependencies for computed properties. 1008 | // this will evaluated all collected computed bindings 1009 | // and collect get events that are emitted. 1010 | if (this.computed.length) { 1011 | DepsParser.parse(this.computed) 1012 | } 1013 | 1014 | // done! 1015 | compiler.init = false 1016 | 1017 | // post compile / ready hook 1018 | compiler.execHook('ready') 1019 | } 1020 | 1021 | var CompilerProto = Compiler.prototype 1022 | 1023 | /** 1024 | * Initialize the VM/Compiler's element. 1025 | * Fill it in with the template if necessary. 1026 | */ 1027 | CompilerProto.setupElement = function (options) { 1028 | // create the node first 1029 | var el = typeof options.el === 'string' 1030 | ? document.querySelector(options.el) 1031 | : options.el || document.createElement(options.tagName || 'div') 1032 | 1033 | var template = options.template, 1034 | child, replacer, i, attr, attrs 1035 | 1036 | if (template) { 1037 | // collect anything already in there 1038 | if (el.hasChildNodes()) { 1039 | this.rawContent = document.createElement('div') 1040 | /* jshint boss: true */ 1041 | while (child = el.firstChild) { 1042 | this.rawContent.appendChild(child) 1043 | } 1044 | } 1045 | // replace option: use the first node in 1046 | // the template directly 1047 | if (options.replace && template.childNodes.length === 1) { 1048 | replacer = template.childNodes[0].cloneNode(true) 1049 | if (el.parentNode) { 1050 | el.parentNode.insertBefore(replacer, el) 1051 | el.parentNode.removeChild(el) 1052 | } 1053 | // copy over attributes 1054 | if (el.hasAttributes()) { 1055 | i = el.attributes.length 1056 | while (i--) { 1057 | attr = el.attributes[i] 1058 | replacer.setAttribute(attr.name, attr.value) 1059 | } 1060 | } 1061 | // replace 1062 | el = replacer 1063 | } else { 1064 | el.appendChild(template.cloneNode(true)) 1065 | } 1066 | 1067 | } 1068 | 1069 | // apply element options 1070 | if (options.id) el.id = options.id 1071 | if (options.className) el.className = options.className 1072 | attrs = options.attributes 1073 | if (attrs) { 1074 | for (attr in attrs) { 1075 | el.setAttribute(attr, attrs[attr]) 1076 | } 1077 | } 1078 | 1079 | return el 1080 | } 1081 | 1082 | /** 1083 | * Deal with insertion points 1084 | * per the Web Components spec 1085 | */ 1086 | CompilerProto.resolveContent = function () { 1087 | 1088 | var outlets = slice.call(this.el.getElementsByTagName('content')), 1089 | raw = this.rawContent, 1090 | outlet, select, i, j, main 1091 | 1092 | i = outlets.length 1093 | if (i) { 1094 | // first pass, collect corresponding content 1095 | // for each outlet. 1096 | while (i--) { 1097 | outlet = outlets[i] 1098 | if (raw) { 1099 | select = outlet.getAttribute('select') 1100 | if (select) { // select content 1101 | outlet.content = 1102 | slice.call(raw.querySelectorAll(select)) 1103 | } else { // default content 1104 | main = outlet 1105 | } 1106 | } else { // fallback content 1107 | outlet.content = 1108 | slice.call(outlet.childNodes) 1109 | } 1110 | } 1111 | // second pass, actually insert the contents 1112 | for (i = 0, j = outlets.length; i < j; i++) { 1113 | outlet = outlets[i] 1114 | if (outlet === main) continue 1115 | insert(outlet, outlet.content) 1116 | } 1117 | // finally insert the main content 1118 | if (raw && main) { 1119 | insert(main, slice.call(raw.childNodes)) 1120 | } 1121 | } 1122 | 1123 | function insert (outlet, contents) { 1124 | var parent = outlet.parentNode, 1125 | i = 0, j = contents.length 1126 | for (; i < j; i++) { 1127 | parent.insertBefore(contents[i], outlet) 1128 | } 1129 | parent.removeChild(outlet) 1130 | } 1131 | 1132 | this.rawContent = null 1133 | } 1134 | 1135 | /** 1136 | * Setup observer. 1137 | * The observer listens for get/set/mutate events on all VM 1138 | * values/objects and trigger corresponding binding updates. 1139 | * It also listens for lifecycle hooks. 1140 | */ 1141 | CompilerProto.setupObserver = function () { 1142 | 1143 | var compiler = this, 1144 | bindings = compiler.bindings, 1145 | options = compiler.options, 1146 | observer = compiler.observer = new Emitter(compiler.vm) 1147 | 1148 | // a hash to hold event proxies for each root level key 1149 | // so they can be referenced and removed later 1150 | observer.proxies = {} 1151 | 1152 | // add own listeners which trigger binding updates 1153 | observer 1154 | .on('get', onGet) 1155 | .on('set', onSet) 1156 | .on('mutate', onSet) 1157 | 1158 | // register hooks 1159 | var i = hooks.length, j, hook, fns 1160 | while (i--) { 1161 | hook = hooks[i] 1162 | fns = options[hook] 1163 | if (Array.isArray(fns)) { 1164 | j = fns.length 1165 | // since hooks were merged with child at head, 1166 | // we loop reversely. 1167 | while (j--) { 1168 | registerHook(hook, fns[j]) 1169 | } 1170 | } else if (fns) { 1171 | registerHook(hook, fns) 1172 | } 1173 | } 1174 | 1175 | // broadcast attached/detached hooks 1176 | observer 1177 | .on('hook:attached', function () { 1178 | broadcast(1) 1179 | }) 1180 | .on('hook:detached', function () { 1181 | broadcast(0) 1182 | }) 1183 | 1184 | function onGet (key) { 1185 | check(key) 1186 | DepsParser.catcher.emit('get', bindings[key]) 1187 | } 1188 | 1189 | function onSet (key, val, mutation) { 1190 | observer.emit('change:' + key, val, mutation) 1191 | check(key) 1192 | bindings[key].update(val) 1193 | } 1194 | 1195 | function registerHook (hook, fn) { 1196 | observer.on('hook:' + hook, function () { 1197 | fn.call(compiler.vm) 1198 | }) 1199 | } 1200 | 1201 | function broadcast (event) { 1202 | var children = compiler.children 1203 | if (children) { 1204 | var child, i = children.length 1205 | while (i--) { 1206 | child = children[i] 1207 | if (child.el.parentNode) { 1208 | event = 'hook:' + (event ? 'attached' : 'detached') 1209 | child.observer.emit(event) 1210 | child.emitter.emit(event) 1211 | } 1212 | } 1213 | } 1214 | } 1215 | 1216 | function check (key) { 1217 | if (!bindings[key]) { 1218 | compiler.createBinding(key) 1219 | } 1220 | } 1221 | } 1222 | 1223 | CompilerProto.observeData = function (data) { 1224 | 1225 | var compiler = this, 1226 | observer = compiler.observer 1227 | 1228 | // recursively observe nested properties 1229 | Observer.observe(data, '', observer) 1230 | 1231 | // also create binding for top level $data 1232 | // so it can be used in templates too 1233 | var $dataBinding = compiler.bindings['$data'] = new Binding(compiler, '$data') 1234 | $dataBinding.update(data) 1235 | 1236 | // allow $data to be swapped 1237 | def(compiler.vm, '$data', { 1238 | get: function () { 1239 | compiler.observer.emit('get', '$data') 1240 | return compiler.data 1241 | }, 1242 | set: function (newData) { 1243 | var oldData = compiler.data 1244 | Observer.unobserve(oldData, '', observer) 1245 | compiler.data = newData 1246 | Observer.copyPaths(newData, oldData) 1247 | Observer.observe(newData, '', observer) 1248 | update() 1249 | } 1250 | }) 1251 | 1252 | // emit $data change on all changes 1253 | observer 1254 | .on('set', onSet) 1255 | .on('mutate', onSet) 1256 | 1257 | function onSet (key) { 1258 | if (key !== '$data') update() 1259 | } 1260 | 1261 | function update () { 1262 | $dataBinding.update(compiler.data) 1263 | observer.emit('change:$data', compiler.data) 1264 | } 1265 | } 1266 | 1267 | /** 1268 | * Compile a DOM node (recursive) 1269 | */ 1270 | CompilerProto.compile = function (node, root) { 1271 | var nodeType = node.nodeType 1272 | if (nodeType === 1 && node.tagName !== 'SCRIPT') { // a normal node 1273 | this.compileElement(node, root) 1274 | } else if (nodeType === 3 && config.interpolate) { 1275 | this.compileTextNode(node) 1276 | } 1277 | } 1278 | 1279 | /** 1280 | * Check for a priority directive 1281 | * If it is present and valid, return true to skip the rest 1282 | */ 1283 | CompilerProto.checkPriorityDir = function (dirname, node, root) { 1284 | var expression, directive, Ctor 1285 | if ( 1286 | dirname === 'component' && 1287 | root !== true && 1288 | (Ctor = this.resolveComponent(node, undefined, true)) 1289 | ) { 1290 | directive = this.parseDirective(dirname, '', node) 1291 | directive.Ctor = Ctor 1292 | } else { 1293 | expression = utils.attr(node, dirname) 1294 | directive = expression && this.parseDirective(dirname, expression, node) 1295 | } 1296 | if (directive) { 1297 | if (root === true) { 1298 | utils.warn( 1299 | 'Directive v-' + dirname + ' cannot be used on an already instantiated ' + 1300 | 'VM\'s root node. Use it from the parent\'s template instead.' 1301 | ) 1302 | return 1303 | } 1304 | this.deferred.push(directive) 1305 | return true 1306 | } 1307 | } 1308 | 1309 | /** 1310 | * Compile normal directives on a node 1311 | */ 1312 | CompilerProto.compileElement = function (node, root) { 1313 | 1314 | // textarea is pretty annoying 1315 | // because its value creates childNodes which 1316 | // we don't want to compile. 1317 | if (node.tagName === 'TEXTAREA' && node.value) { 1318 | node.value = this.eval(node.value) 1319 | } 1320 | 1321 | // only compile if this element has attributes 1322 | // or its tagName contains a hyphen (which means it could 1323 | // potentially be a custom element) 1324 | if (node.hasAttributes() || node.tagName.indexOf('-') > -1) { 1325 | 1326 | // skip anything with v-pre 1327 | if (utils.attr(node, 'pre') !== null) { 1328 | return 1329 | } 1330 | 1331 | var i, l, j, k 1332 | 1333 | // check priority directives. 1334 | // if any of them are present, it will take over the node with a childVM 1335 | // so we can skip the rest 1336 | for (i = 0, l = priorityDirectives.length; i < l; i++) { 1337 | if (this.checkPriorityDir(priorityDirectives[i], node, root)) { 1338 | return 1339 | } 1340 | } 1341 | 1342 | // check transition & animation properties 1343 | node.vue_trans = utils.attr(node, 'transition') 1344 | node.vue_anim = utils.attr(node, 'animation') 1345 | node.vue_effect = this.eval(utils.attr(node, 'effect')) 1346 | 1347 | var prefix = config.prefix + '-', 1348 | attrs = slice.call(node.attributes), 1349 | params = this.options.paramAttributes, 1350 | attr, isDirective, exp, directives, directive, dirname 1351 | 1352 | for (i = 0, l = attrs.length; i < l; i++) { 1353 | 1354 | attr = attrs[i] 1355 | isDirective = false 1356 | 1357 | if (attr.name.indexOf(prefix) === 0) { 1358 | // a directive - split, parse and bind it. 1359 | isDirective = true 1360 | dirname = attr.name.slice(prefix.length) 1361 | // build with multiple: true 1362 | directives = this.parseDirective(dirname, attr.value, node, true) 1363 | // loop through clauses (separated by ",") 1364 | // inside each attribute 1365 | for (j = 0, k = directives.length; j < k; j++) { 1366 | directive = directives[j] 1367 | if (dirname === 'with') { 1368 | this.bindDirective(directive, this.parent) 1369 | } else { 1370 | this.bindDirective(directive) 1371 | } 1372 | } 1373 | } else if (config.interpolate) { 1374 | // non directive attribute, check interpolation tags 1375 | exp = TextParser.parseAttr(attr.value) 1376 | if (exp) { 1377 | directive = this.parseDirective('attr', attr.name + ':' + exp, node) 1378 | if (params && params.indexOf(attr.name) > -1) { 1379 | // a param attribute... we should use the parent binding 1380 | // to avoid circular updates like size={{size}} 1381 | this.bindDirective(directive, this.parent) 1382 | } else { 1383 | this.bindDirective(directive) 1384 | } 1385 | } 1386 | } 1387 | 1388 | if (isDirective && dirname !== 'cloak') { 1389 | node.removeAttribute(attr.name) 1390 | } 1391 | } 1392 | 1393 | } 1394 | 1395 | // recursively compile childNodes 1396 | if (node.hasChildNodes()) { 1397 | slice.call(node.childNodes).forEach(this.compile, this) 1398 | } 1399 | } 1400 | 1401 | /** 1402 | * Compile a text node 1403 | */ 1404 | CompilerProto.compileTextNode = function (node) { 1405 | 1406 | var tokens = TextParser.parse(node.nodeValue) 1407 | if (!tokens) return 1408 | var el, token, directive 1409 | 1410 | for (var i = 0, l = tokens.length; i < l; i++) { 1411 | 1412 | token = tokens[i] 1413 | directive = null 1414 | 1415 | if (token.key) { // a binding 1416 | if (token.key.charAt(0) === '>') { // a partial 1417 | el = document.createComment('ref') 1418 | directive = this.parseDirective('partial', token.key.slice(1), el) 1419 | } else { 1420 | if (!token.html) { // text binding 1421 | el = document.createTextNode('') 1422 | directive = this.parseDirective('text', token.key, el) 1423 | } else { // html binding 1424 | el = document.createComment(config.prefix + '-html') 1425 | directive = this.parseDirective('html', token.key, el) 1426 | } 1427 | } 1428 | } else { // a plain string 1429 | el = document.createTextNode(token) 1430 | } 1431 | 1432 | // insert node 1433 | node.parentNode.insertBefore(el, node) 1434 | // bind directive 1435 | this.bindDirective(directive) 1436 | 1437 | } 1438 | node.parentNode.removeChild(node) 1439 | } 1440 | 1441 | /** 1442 | * Parse a directive name/value pair into one or more 1443 | * directive instances 1444 | */ 1445 | CompilerProto.parseDirective = function (name, value, el, multiple) { 1446 | var compiler = this, 1447 | definition = compiler.getOption('directives', name) 1448 | if (definition) { 1449 | // parse into AST-like objects 1450 | var asts = Directive.parse(value) 1451 | return multiple 1452 | ? asts.map(build) 1453 | : build(asts[0]) 1454 | } 1455 | function build (ast) { 1456 | return new Directive(name, ast, definition, compiler, el) 1457 | } 1458 | } 1459 | 1460 | /** 1461 | * Add a directive instance to the correct binding & viewmodel 1462 | */ 1463 | CompilerProto.bindDirective = function (directive, bindingOwner) { 1464 | 1465 | if (!directive) return 1466 | 1467 | // keep track of it so we can unbind() later 1468 | this.dirs.push(directive) 1469 | 1470 | // for empty or literal directives, simply call its bind() 1471 | // and we're done. 1472 | if (directive.isEmpty || directive.isLiteral) { 1473 | if (directive.bind) directive.bind() 1474 | return 1475 | } 1476 | 1477 | // otherwise, we got more work to do... 1478 | var binding, 1479 | compiler = bindingOwner || this, 1480 | key = directive.key 1481 | 1482 | if (directive.isExp) { 1483 | // expression bindings are always created on current compiler 1484 | binding = compiler.createBinding(key, directive) 1485 | } else { 1486 | // recursively locate which compiler owns the binding 1487 | while (compiler) { 1488 | if (compiler.hasKey(key)) { 1489 | break 1490 | } else { 1491 | compiler = compiler.parent 1492 | } 1493 | } 1494 | compiler = compiler || this 1495 | binding = compiler.bindings[key] || compiler.createBinding(key) 1496 | } 1497 | binding.dirs.push(directive) 1498 | directive.binding = binding 1499 | 1500 | var value = binding.val() 1501 | // invoke bind hook if exists 1502 | if (directive.bind) { 1503 | directive.bind(value) 1504 | } 1505 | // set initial value 1506 | directive.update(value, true) 1507 | } 1508 | 1509 | /** 1510 | * Create binding and attach getter/setter for a key to the viewmodel object 1511 | */ 1512 | CompilerProto.createBinding = function (key, directive) { 1513 | 1514 | utils.log(' created binding: ' + key) 1515 | 1516 | var compiler = this, 1517 | methods = compiler.options.methods, 1518 | isExp = directive && directive.isExp, 1519 | isFn = (directive && directive.isFn) || (methods && methods[key]), 1520 | bindings = compiler.bindings, 1521 | computed = compiler.options.computed, 1522 | binding = new Binding(compiler, key, isExp, isFn) 1523 | 1524 | if (isExp) { 1525 | // expression bindings are anonymous 1526 | compiler.defineExp(key, binding, directive) 1527 | } else if (isFn) { 1528 | bindings[key] = binding 1529 | binding.value = compiler.vm[key] = methods[key] 1530 | } else { 1531 | bindings[key] = binding 1532 | if (binding.root) { 1533 | // this is a root level binding. we need to define getter/setters for it. 1534 | if (computed && computed[key]) { 1535 | // computed property 1536 | compiler.defineComputed(key, binding, computed[key]) 1537 | } else if (key.charAt(0) !== '$') { 1538 | // normal property 1539 | compiler.defineProp(key, binding) 1540 | } else { 1541 | compiler.defineMeta(key, binding) 1542 | } 1543 | } else if (computed && computed[utils.baseKey(key)]) { 1544 | // nested path on computed property 1545 | compiler.defineExp(key, binding) 1546 | } else { 1547 | // ensure path in data so that computed properties that 1548 | // access the path don't throw an error and can collect 1549 | // dependencies 1550 | Observer.ensurePath(compiler.data, key) 1551 | var parentKey = key.slice(0, key.lastIndexOf('.')) 1552 | if (!bindings[parentKey]) { 1553 | // this is a nested value binding, but the binding for its parent 1554 | // has not been created yet. We better create that one too. 1555 | compiler.createBinding(parentKey) 1556 | } 1557 | } 1558 | } 1559 | return binding 1560 | } 1561 | 1562 | /** 1563 | * Define the getter/setter for a root-level property on the VM 1564 | * and observe the initial value 1565 | */ 1566 | CompilerProto.defineProp = function (key, binding) { 1567 | var compiler = this, 1568 | data = compiler.data, 1569 | ob = data.__emitter__ 1570 | 1571 | // make sure the key is present in data 1572 | // so it can be observed 1573 | if (!(hasOwn.call(data, key))) { 1574 | data[key] = undefined 1575 | } 1576 | 1577 | // if the data object is already observed, but the key 1578 | // is not observed, we need to add it to the observed keys. 1579 | if (ob && !(hasOwn.call(ob.values, key))) { 1580 | Observer.convertKey(data, key) 1581 | } 1582 | 1583 | binding.value = data[key] 1584 | 1585 | def(compiler.vm, key, { 1586 | get: function () { 1587 | return compiler.data[key] 1588 | }, 1589 | set: function (val) { 1590 | compiler.data[key] = val 1591 | } 1592 | }) 1593 | } 1594 | 1595 | /** 1596 | * Define a meta property, e.g. $index or $key, 1597 | * which is bindable but only accessible on the VM, 1598 | * not in the data. 1599 | */ 1600 | CompilerProto.defineMeta = function (key, binding) { 1601 | var ob = this.observer 1602 | binding.value = this.data[key] 1603 | delete this.data[key] 1604 | def(this.vm, key, { 1605 | get: function () { 1606 | if (Observer.shouldGet) ob.emit('get', key) 1607 | return binding.value 1608 | }, 1609 | set: function (val) { 1610 | ob.emit('set', key, val) 1611 | } 1612 | }) 1613 | } 1614 | 1615 | /** 1616 | * Define an expression binding, which is essentially 1617 | * an anonymous computed property 1618 | */ 1619 | CompilerProto.defineExp = function (key, binding, directive) { 1620 | var computedKey = directive && directive.computedKey, 1621 | exp = computedKey ? directive.expression : key, 1622 | getter = this.expCache[exp] 1623 | if (!getter) { 1624 | getter = this.expCache[exp] = ExpParser.parse(computedKey || key, this) 1625 | } 1626 | if (getter) { 1627 | this.markComputed(binding, getter) 1628 | } 1629 | } 1630 | 1631 | /** 1632 | * Define a computed property on the VM 1633 | */ 1634 | CompilerProto.defineComputed = function (key, binding, value) { 1635 | this.markComputed(binding, value) 1636 | def(this.vm, key, { 1637 | get: binding.value.$get, 1638 | set: binding.value.$set 1639 | }) 1640 | } 1641 | 1642 | /** 1643 | * Process a computed property binding 1644 | * so its getter/setter are bound to proper context 1645 | */ 1646 | CompilerProto.markComputed = function (binding, value) { 1647 | binding.isComputed = true 1648 | // bind the accessors to the vm 1649 | if (binding.isFn) { 1650 | binding.value = value 1651 | } else { 1652 | if (typeof value === 'function') { 1653 | value = { $get: value } 1654 | } 1655 | binding.value = { 1656 | $get: utils.bind(value.$get, this.vm), 1657 | $set: value.$set 1658 | ? utils.bind(value.$set, this.vm) 1659 | : undefined 1660 | } 1661 | } 1662 | // keep track for dep parsing later 1663 | this.computed.push(binding) 1664 | } 1665 | 1666 | /** 1667 | * Retrive an option from the compiler 1668 | */ 1669 | CompilerProto.getOption = function (type, id, silent) { 1670 | var opts = this.options, 1671 | parent = this.parent, 1672 | globalAssets = config.globalAssets, 1673 | res = (opts[type] && opts[type][id]) || ( 1674 | parent 1675 | ? parent.getOption(type, id, silent) 1676 | : globalAssets[type] && globalAssets[type][id] 1677 | ) 1678 | if (!res && !silent && typeof id === 'string') { 1679 | utils.warn('Unknown ' + type.slice(0, -1) + ': ' + id) 1680 | } 1681 | return res 1682 | } 1683 | 1684 | /** 1685 | * Emit lifecycle events to trigger hooks 1686 | */ 1687 | CompilerProto.execHook = function (event) { 1688 | event = 'hook:' + event 1689 | this.observer.emit(event) 1690 | this.emitter.emit(event) 1691 | } 1692 | 1693 | /** 1694 | * Check if a compiler's data contains a keypath 1695 | */ 1696 | CompilerProto.hasKey = function (key) { 1697 | var baseKey = utils.baseKey(key) 1698 | return hasOwn.call(this.data, baseKey) || 1699 | hasOwn.call(this.vm, baseKey) 1700 | } 1701 | 1702 | /** 1703 | * Do a one-time eval of a string that potentially 1704 | * includes bindings. It accepts additional raw data 1705 | * because we need to dynamically resolve v-component 1706 | * before a childVM is even compiled... 1707 | */ 1708 | CompilerProto.eval = function (exp, data) { 1709 | var parsed = TextParser.parseAttr(exp) 1710 | return parsed 1711 | ? ExpParser.eval(parsed, this, data) 1712 | : exp 1713 | } 1714 | 1715 | /** 1716 | * Resolve a Component constructor for an element 1717 | * with the data to be used 1718 | */ 1719 | CompilerProto.resolveComponent = function (node, data, test) { 1720 | 1721 | // late require to avoid circular deps 1722 | ViewModel = ViewModel || require('./viewmodel') 1723 | 1724 | var exp = utils.attr(node, 'component'), 1725 | tagName = node.tagName, 1726 | id = this.eval(exp, data), 1727 | tagId = (tagName.indexOf('-') > 0 && tagName.toLowerCase()), 1728 | Ctor = this.getOption('components', id || tagId, true) 1729 | 1730 | if (id && !Ctor) { 1731 | utils.warn('Unknown component: ' + id) 1732 | } 1733 | 1734 | return test 1735 | ? exp === '' 1736 | ? ViewModel 1737 | : Ctor 1738 | : Ctor || ViewModel 1739 | } 1740 | 1741 | /** 1742 | * Unbind and remove element 1743 | */ 1744 | CompilerProto.destroy = function () { 1745 | 1746 | // avoid being called more than once 1747 | // this is irreversible! 1748 | if (this.destroyed) return 1749 | 1750 | var compiler = this, 1751 | i, j, key, dir, dirs, binding, 1752 | vm = compiler.vm, 1753 | el = compiler.el, 1754 | directives = compiler.dirs, 1755 | computed = compiler.computed, 1756 | bindings = compiler.bindings, 1757 | children = compiler.children, 1758 | parent = compiler.parent 1759 | 1760 | compiler.execHook('beforeDestroy') 1761 | 1762 | // unobserve data 1763 | Observer.unobserve(compiler.data, '', compiler.observer) 1764 | 1765 | // unbind all direcitves 1766 | i = directives.length 1767 | while (i--) { 1768 | dir = directives[i] 1769 | // if this directive is an instance of an external binding 1770 | // e.g. a directive that refers to a variable on the parent VM 1771 | // we need to remove it from that binding's directives 1772 | // * empty and literal bindings do not have binding. 1773 | if (dir.binding && dir.binding.compiler !== compiler) { 1774 | dirs = dir.binding.dirs 1775 | if (dirs) { 1776 | j = dirs.indexOf(dir) 1777 | if (j > -1) dirs.splice(j, 1) 1778 | } 1779 | } 1780 | dir.unbind() 1781 | } 1782 | 1783 | // unbind all computed, anonymous bindings 1784 | i = computed.length 1785 | while (i--) { 1786 | computed[i].unbind() 1787 | } 1788 | 1789 | // unbind all keypath bindings 1790 | for (key in bindings) { 1791 | binding = bindings[key] 1792 | if (binding) { 1793 | binding.unbind() 1794 | } 1795 | } 1796 | 1797 | // destroy all children 1798 | i = children.length 1799 | while (i--) { 1800 | children[i].destroy() 1801 | } 1802 | 1803 | // remove self from parent 1804 | if (parent) { 1805 | j = parent.children.indexOf(compiler) 1806 | if (j > -1) parent.children.splice(j, 1) 1807 | } 1808 | 1809 | // finally remove dom element 1810 | if (el === document.body) { 1811 | el.innerHTML = '' 1812 | } else { 1813 | vm.$remove() 1814 | } 1815 | el.vue_vm = null 1816 | 1817 | compiler.destroyed = true 1818 | // emit destroy hook 1819 | compiler.execHook('afterDestroy') 1820 | 1821 | // finally, unregister all listeners 1822 | compiler.observer.off() 1823 | compiler.emitter.off() 1824 | } 1825 | 1826 | // Helpers -------------------------------------------------------------------- 1827 | 1828 | /** 1829 | * shorthand for getting root compiler 1830 | */ 1831 | function getRoot (compiler) { 1832 | while (compiler.parent) { 1833 | compiler = compiler.parent 1834 | } 1835 | return compiler 1836 | } 1837 | 1838 | module.exports = Compiler 1839 | }); 1840 | require.register("vue/src/viewmodel.js", function(exports, require, module){ 1841 | var Compiler = require('./compiler'), 1842 | utils = require('./utils'), 1843 | transition = require('./transition'), 1844 | Batcher = require('./batcher'), 1845 | slice = [].slice, 1846 | def = utils.defProtected, 1847 | nextTick = utils.nextTick, 1848 | 1849 | // batch $watch callbacks 1850 | watcherBatcher = new Batcher(), 1851 | watcherId = 1 1852 | 1853 | /** 1854 | * ViewModel exposed to the user that holds data, 1855 | * computed properties, event handlers 1856 | * and a few reserved methods 1857 | */ 1858 | function ViewModel (options) { 1859 | // just compile. options are passed directly to compiler 1860 | new Compiler(this, options) 1861 | } 1862 | 1863 | // All VM prototype methods are inenumerable 1864 | // so it can be stringified/looped through as raw data 1865 | var VMProto = ViewModel.prototype 1866 | 1867 | /** 1868 | * Convenience function to get a value from 1869 | * a keypath 1870 | */ 1871 | def(VMProto, '$get', function (key) { 1872 | var val = utils.get(this, key) 1873 | return val === undefined && this.$parent 1874 | ? this.$parent.$get(key) 1875 | : val 1876 | }) 1877 | 1878 | /** 1879 | * Convenience function to set an actual nested value 1880 | * from a flat key string. Used in directives. 1881 | */ 1882 | def(VMProto, '$set', function (key, value) { 1883 | utils.set(this, key, value) 1884 | }) 1885 | 1886 | /** 1887 | * watch a key on the viewmodel for changes 1888 | * fire callback with new value 1889 | */ 1890 | def(VMProto, '$watch', function (key, callback) { 1891 | // save a unique id for each watcher 1892 | var id = watcherId++, 1893 | self = this 1894 | function on () { 1895 | var args = slice.call(arguments) 1896 | watcherBatcher.push({ 1897 | id: id, 1898 | override: true, 1899 | execute: function () { 1900 | callback.apply(self, args) 1901 | } 1902 | }) 1903 | } 1904 | callback._fn = on 1905 | self.$compiler.observer.on('change:' + key, on) 1906 | }) 1907 | 1908 | /** 1909 | * unwatch a key 1910 | */ 1911 | def(VMProto, '$unwatch', function (key, callback) { 1912 | // workaround here 1913 | // since the emitter module checks callback existence 1914 | // by checking the length of arguments 1915 | var args = ['change:' + key], 1916 | ob = this.$compiler.observer 1917 | if (callback) args.push(callback._fn) 1918 | ob.off.apply(ob, args) 1919 | }) 1920 | 1921 | /** 1922 | * unbind everything, remove everything 1923 | */ 1924 | def(VMProto, '$destroy', function () { 1925 | this.$compiler.destroy() 1926 | }) 1927 | 1928 | /** 1929 | * broadcast an event to all child VMs recursively. 1930 | */ 1931 | def(VMProto, '$broadcast', function () { 1932 | var children = this.$compiler.children, 1933 | i = children.length, 1934 | child 1935 | while (i--) { 1936 | child = children[i] 1937 | child.emitter.emit.apply(child.emitter, arguments) 1938 | child.vm.$broadcast.apply(child.vm, arguments) 1939 | } 1940 | }) 1941 | 1942 | /** 1943 | * emit an event that propagates all the way up to parent VMs. 1944 | */ 1945 | def(VMProto, '$dispatch', function () { 1946 | var compiler = this.$compiler, 1947 | emitter = compiler.emitter, 1948 | parent = compiler.parent 1949 | emitter.emit.apply(emitter, arguments) 1950 | if (parent) { 1951 | parent.vm.$dispatch.apply(parent.vm, arguments) 1952 | } 1953 | }) 1954 | 1955 | /** 1956 | * delegate on/off/once to the compiler's emitter 1957 | */ 1958 | ;['emit', 'on', 'off', 'once'].forEach(function (method) { 1959 | def(VMProto, '$' + method, function () { 1960 | var emitter = this.$compiler.emitter 1961 | emitter[method].apply(emitter, arguments) 1962 | }) 1963 | }) 1964 | 1965 | // DOM convenience methods 1966 | 1967 | def(VMProto, '$appendTo', function (target, cb) { 1968 | target = query(target) 1969 | var el = this.$el 1970 | transition(el, 1, function () { 1971 | target.appendChild(el) 1972 | if (cb) nextTick(cb) 1973 | }, this.$compiler) 1974 | }) 1975 | 1976 | def(VMProto, '$remove', function (cb) { 1977 | var el = this.$el 1978 | transition(el, -1, function () { 1979 | if (el.parentNode) { 1980 | el.parentNode.removeChild(el) 1981 | } 1982 | if (cb) nextTick(cb) 1983 | }, this.$compiler) 1984 | }) 1985 | 1986 | def(VMProto, '$before', function (target, cb) { 1987 | target = query(target) 1988 | var el = this.$el 1989 | transition(el, 1, function () { 1990 | target.parentNode.insertBefore(el, target) 1991 | if (cb) nextTick(cb) 1992 | }, this.$compiler) 1993 | }) 1994 | 1995 | def(VMProto, '$after', function (target, cb) { 1996 | target = query(target) 1997 | var el = this.$el 1998 | transition(el, 1, function () { 1999 | if (target.nextSibling) { 2000 | target.parentNode.insertBefore(el, target.nextSibling) 2001 | } else { 2002 | target.parentNode.appendChild(el) 2003 | } 2004 | if (cb) nextTick(cb) 2005 | }, this.$compiler) 2006 | }) 2007 | 2008 | function query (el) { 2009 | return typeof el === 'string' 2010 | ? document.querySelector(el) 2011 | : el 2012 | } 2013 | 2014 | module.exports = ViewModel 2015 | }); 2016 | require.register("vue/src/binding.js", function(exports, require, module){ 2017 | var Batcher = require('./batcher'), 2018 | bindingBatcher = new Batcher(), 2019 | bindingId = 1 2020 | 2021 | /** 2022 | * Binding class. 2023 | * 2024 | * each property on the viewmodel has one corresponding Binding object 2025 | * which has multiple directive instances on the DOM 2026 | * and multiple computed property dependents 2027 | */ 2028 | function Binding (compiler, key, isExp, isFn) { 2029 | this.id = bindingId++ 2030 | this.value = undefined 2031 | this.isExp = !!isExp 2032 | this.isFn = isFn 2033 | this.root = !this.isExp && key.indexOf('.') === -1 2034 | this.compiler = compiler 2035 | this.key = key 2036 | this.dirs = [] 2037 | this.subs = [] 2038 | this.deps = [] 2039 | this.unbound = false 2040 | } 2041 | 2042 | var BindingProto = Binding.prototype 2043 | 2044 | /** 2045 | * Update value and queue instance updates. 2046 | */ 2047 | BindingProto.update = function (value) { 2048 | if (!this.isComputed || this.isFn) { 2049 | this.value = value 2050 | } 2051 | if (this.dirs.length || this.subs.length) { 2052 | var self = this 2053 | bindingBatcher.push({ 2054 | id: this.id, 2055 | execute: function () { 2056 | if (!self.unbound) { 2057 | self._update() 2058 | } 2059 | } 2060 | }) 2061 | } 2062 | } 2063 | 2064 | /** 2065 | * Actually update the directives. 2066 | */ 2067 | BindingProto._update = function () { 2068 | var i = this.dirs.length, 2069 | value = this.val() 2070 | while (i--) { 2071 | this.dirs[i].update(value) 2072 | } 2073 | this.pub() 2074 | } 2075 | 2076 | /** 2077 | * Return the valuated value regardless 2078 | * of whether it is computed or not 2079 | */ 2080 | BindingProto.val = function () { 2081 | return this.isComputed && !this.isFn 2082 | ? this.value.$get() 2083 | : this.value 2084 | } 2085 | 2086 | /** 2087 | * Notify computed properties that depend on this binding 2088 | * to update themselves 2089 | */ 2090 | BindingProto.pub = function () { 2091 | var i = this.subs.length 2092 | while (i--) { 2093 | this.subs[i].update() 2094 | } 2095 | } 2096 | 2097 | /** 2098 | * Unbind the binding, remove itself from all of its dependencies 2099 | */ 2100 | BindingProto.unbind = function () { 2101 | // Indicate this has been unbound. 2102 | // It's possible this binding will be in 2103 | // the batcher's flush queue when its owner 2104 | // compiler has already been destroyed. 2105 | this.unbound = true 2106 | var i = this.dirs.length 2107 | while (i--) { 2108 | this.dirs[i].unbind() 2109 | } 2110 | i = this.deps.length 2111 | var subs 2112 | while (i--) { 2113 | subs = this.deps[i].subs 2114 | var j = subs.indexOf(this) 2115 | if (j > -1) subs.splice(j, 1) 2116 | } 2117 | } 2118 | 2119 | module.exports = Binding 2120 | }); 2121 | require.register("vue/src/observer.js", function(exports, require, module){ 2122 | /* jshint proto:true */ 2123 | 2124 | var Emitter = require('./emitter'), 2125 | utils = require('./utils'), 2126 | // cache methods 2127 | def = utils.defProtected, 2128 | isObject = utils.isObject, 2129 | isArray = Array.isArray, 2130 | hasOwn = ({}).hasOwnProperty, 2131 | oDef = Object.defineProperty, 2132 | slice = [].slice, 2133 | // fix for IE + __proto__ problem 2134 | // define methods as inenumerable if __proto__ is present, 2135 | // otherwise enumerable so we can loop through and manually 2136 | // attach to array instances 2137 | hasProto = ({}).__proto__ 2138 | 2139 | // Array Mutation Handlers & Augmentations ------------------------------------ 2140 | 2141 | // The proxy prototype to replace the __proto__ of 2142 | // an observed array 2143 | var ArrayProxy = Object.create(Array.prototype) 2144 | 2145 | // intercept mutation methods 2146 | ;[ 2147 | 'push', 2148 | 'pop', 2149 | 'shift', 2150 | 'unshift', 2151 | 'splice', 2152 | 'sort', 2153 | 'reverse' 2154 | ].forEach(watchMutation) 2155 | 2156 | // Augment the ArrayProxy with convenience methods 2157 | def(ArrayProxy, '$set', function (index, data) { 2158 | return this.splice(index, 1, data)[0] 2159 | }, !hasProto) 2160 | 2161 | def(ArrayProxy, '$remove', function (index) { 2162 | if (typeof index !== 'number') { 2163 | index = this.indexOf(index) 2164 | } 2165 | if (index > -1) { 2166 | return this.splice(index, 1)[0] 2167 | } 2168 | }, !hasProto) 2169 | 2170 | /** 2171 | * Intercep a mutation event so we can emit the mutation info. 2172 | * we also analyze what elements are added/removed and link/unlink 2173 | * them with the parent Array. 2174 | */ 2175 | function watchMutation (method) { 2176 | def(ArrayProxy, method, function () { 2177 | 2178 | var args = slice.call(arguments), 2179 | result = Array.prototype[method].apply(this, args), 2180 | inserted, removed 2181 | 2182 | // determine new / removed elements 2183 | if (method === 'push' || method === 'unshift') { 2184 | inserted = args 2185 | } else if (method === 'pop' || method === 'shift') { 2186 | removed = [result] 2187 | } else if (method === 'splice') { 2188 | inserted = args.slice(2) 2189 | removed = result 2190 | } 2191 | 2192 | // link & unlink 2193 | linkArrayElements(this, inserted) 2194 | unlinkArrayElements(this, removed) 2195 | 2196 | // emit the mutation event 2197 | this.__emitter__.emit('mutate', '', this, { 2198 | method : method, 2199 | args : args, 2200 | result : result, 2201 | inserted : inserted, 2202 | removed : removed 2203 | }) 2204 | 2205 | return result 2206 | 2207 | }, !hasProto) 2208 | } 2209 | 2210 | /** 2211 | * Link new elements to an Array, so when they change 2212 | * and emit events, the owner Array can be notified. 2213 | */ 2214 | function linkArrayElements (arr, items) { 2215 | if (items) { 2216 | var i = items.length, item, owners 2217 | while (i--) { 2218 | item = items[i] 2219 | if (isWatchable(item)) { 2220 | // if object is not converted for observing 2221 | // convert it... 2222 | if (!item.__emitter__) { 2223 | convert(item) 2224 | watch(item) 2225 | } 2226 | owners = item.__emitter__.owners 2227 | if (owners.indexOf(arr) < 0) { 2228 | owners.push(arr) 2229 | } 2230 | } 2231 | } 2232 | } 2233 | } 2234 | 2235 | /** 2236 | * Unlink removed elements from the ex-owner Array. 2237 | */ 2238 | function unlinkArrayElements (arr, items) { 2239 | if (items) { 2240 | var i = items.length, item 2241 | while (i--) { 2242 | item = items[i] 2243 | if (item && item.__emitter__) { 2244 | var owners = item.__emitter__.owners 2245 | if (owners) owners.splice(owners.indexOf(arr)) 2246 | } 2247 | } 2248 | } 2249 | } 2250 | 2251 | // Object add/delete key augmentation ----------------------------------------- 2252 | 2253 | var ObjProxy = Object.create(Object.prototype) 2254 | 2255 | def(ObjProxy, '$add', function (key, val) { 2256 | if (hasOwn.call(this, key)) return 2257 | this[key] = val 2258 | convertKey(this, key) 2259 | // emit a propagating set event 2260 | this.__emitter__.emit('set', key, val, true) 2261 | }, !hasProto) 2262 | 2263 | def(ObjProxy, '$delete', function (key) { 2264 | if (!(hasOwn.call(this, key))) return 2265 | // trigger set events 2266 | this[key] = undefined 2267 | delete this[key] 2268 | this.__emitter__.emit('delete', key) 2269 | }, !hasProto) 2270 | 2271 | // Watch Helpers -------------------------------------------------------------- 2272 | 2273 | /** 2274 | * Check if a value is watchable 2275 | */ 2276 | function isWatchable (obj) { 2277 | return typeof obj === 'object' && obj && !obj.$compiler 2278 | } 2279 | 2280 | /** 2281 | * Convert an Object/Array to give it a change emitter. 2282 | */ 2283 | function convert (obj) { 2284 | if (obj.__emitter__) return true 2285 | var emitter = new Emitter() 2286 | def(obj, '__emitter__', emitter) 2287 | emitter 2288 | .on('set', function (key, val, propagate) { 2289 | if (propagate) propagateChange(obj) 2290 | }) 2291 | .on('mutate', function () { 2292 | propagateChange(obj) 2293 | }) 2294 | emitter.values = utils.hash() 2295 | emitter.owners = [] 2296 | return false 2297 | } 2298 | 2299 | /** 2300 | * Propagate an array element's change to its owner arrays 2301 | */ 2302 | function propagateChange (obj) { 2303 | var owners = obj.__emitter__.owners, 2304 | i = owners.length 2305 | while (i--) { 2306 | owners[i].__emitter__.emit('set', '', '', true) 2307 | } 2308 | } 2309 | 2310 | /** 2311 | * Watch target based on its type 2312 | */ 2313 | function watch (obj) { 2314 | if (isArray(obj)) { 2315 | watchArray(obj) 2316 | } else { 2317 | watchObject(obj) 2318 | } 2319 | } 2320 | 2321 | /** 2322 | * Augment target objects with modified 2323 | * methods 2324 | */ 2325 | function augment (target, src) { 2326 | if (hasProto) { 2327 | target.__proto__ = src 2328 | } else { 2329 | for (var key in src) { 2330 | def(target, key, src[key]) 2331 | } 2332 | } 2333 | } 2334 | 2335 | /** 2336 | * Watch an Object, recursive. 2337 | */ 2338 | function watchObject (obj) { 2339 | augment(obj, ObjProxy) 2340 | for (var key in obj) { 2341 | convertKey(obj, key) 2342 | } 2343 | } 2344 | 2345 | /** 2346 | * Watch an Array, overload mutation methods 2347 | * and add augmentations by intercepting the prototype chain 2348 | */ 2349 | function watchArray (arr) { 2350 | augment(arr, ArrayProxy) 2351 | linkArrayElements(arr, arr) 2352 | } 2353 | 2354 | /** 2355 | * Define accessors for a property on an Object 2356 | * so it emits get/set events. 2357 | * Then watch the value itself. 2358 | */ 2359 | function convertKey (obj, key) { 2360 | var keyPrefix = key.charAt(0) 2361 | if (keyPrefix === '$' || keyPrefix === '_') { 2362 | return 2363 | } 2364 | // emit set on bind 2365 | // this means when an object is observed it will emit 2366 | // a first batch of set events. 2367 | var emitter = obj.__emitter__, 2368 | values = emitter.values 2369 | 2370 | init(obj[key]) 2371 | 2372 | oDef(obj, key, { 2373 | enumerable: true, 2374 | configurable: true, 2375 | get: function () { 2376 | var value = values[key] 2377 | // only emit get on tip values 2378 | if (pub.shouldGet) { 2379 | emitter.emit('get', key) 2380 | } 2381 | return value 2382 | }, 2383 | set: function (newVal) { 2384 | var oldVal = values[key] 2385 | unobserve(oldVal, key, emitter) 2386 | copyPaths(newVal, oldVal) 2387 | // an immediate property should notify its parent 2388 | // to emit set for itself too 2389 | init(newVal, true) 2390 | } 2391 | }) 2392 | 2393 | function init (val, propagate) { 2394 | values[key] = val 2395 | emitter.emit('set', key, val, propagate) 2396 | if (isArray(val)) { 2397 | emitter.emit('set', key + '.length', val.length, propagate) 2398 | } 2399 | observe(val, key, emitter) 2400 | } 2401 | } 2402 | 2403 | /** 2404 | * When a value that is already converted is 2405 | * observed again by another observer, we can skip 2406 | * the watch conversion and simply emit set event for 2407 | * all of its properties. 2408 | */ 2409 | function emitSet (obj) { 2410 | var emitter = obj && obj.__emitter__ 2411 | if (!emitter) return 2412 | if (isArray(obj)) { 2413 | emitter.emit('set', 'length', obj.length) 2414 | } else { 2415 | var key, val 2416 | for (key in obj) { 2417 | val = obj[key] 2418 | emitter.emit('set', key, val) 2419 | emitSet(val) 2420 | } 2421 | } 2422 | } 2423 | 2424 | /** 2425 | * Make sure all the paths in an old object exists 2426 | * in a new object. 2427 | * So when an object changes, all missing keys will 2428 | * emit a set event with undefined value. 2429 | */ 2430 | function copyPaths (newObj, oldObj) { 2431 | if (!isObject(newObj) || !isObject(oldObj)) { 2432 | return 2433 | } 2434 | var path, oldVal, newVal 2435 | for (path in oldObj) { 2436 | if (!(hasOwn.call(newObj, path))) { 2437 | oldVal = oldObj[path] 2438 | if (isArray(oldVal)) { 2439 | newObj[path] = [] 2440 | } else if (isObject(oldVal)) { 2441 | newVal = newObj[path] = {} 2442 | copyPaths(newVal, oldVal) 2443 | } else { 2444 | newObj[path] = undefined 2445 | } 2446 | } 2447 | } 2448 | } 2449 | 2450 | /** 2451 | * walk along a path and make sure it can be accessed 2452 | * and enumerated in that object 2453 | */ 2454 | function ensurePath (obj, key) { 2455 | var path = key.split('.'), sec 2456 | for (var i = 0, d = path.length - 1; i < d; i++) { 2457 | sec = path[i] 2458 | if (!obj[sec]) { 2459 | obj[sec] = {} 2460 | if (obj.__emitter__) convertKey(obj, sec) 2461 | } 2462 | obj = obj[sec] 2463 | } 2464 | if (isObject(obj)) { 2465 | sec = path[i] 2466 | if (!(hasOwn.call(obj, sec))) { 2467 | obj[sec] = undefined 2468 | if (obj.__emitter__) convertKey(obj, sec) 2469 | } 2470 | } 2471 | } 2472 | 2473 | // Main API Methods ----------------------------------------------------------- 2474 | 2475 | /** 2476 | * Observe an object with a given path, 2477 | * and proxy get/set/mutate events to the provided observer. 2478 | */ 2479 | function observe (obj, rawPath, observer) { 2480 | 2481 | if (!isWatchable(obj)) return 2482 | 2483 | var path = rawPath ? rawPath + '.' : '', 2484 | alreadyConverted = convert(obj), 2485 | emitter = obj.__emitter__ 2486 | 2487 | // setup proxy listeners on the parent observer. 2488 | // we need to keep reference to them so that they 2489 | // can be removed when the object is un-observed. 2490 | observer.proxies = observer.proxies || {} 2491 | var proxies = observer.proxies[path] = { 2492 | get: function (key) { 2493 | observer.emit('get', path + key) 2494 | }, 2495 | set: function (key, val, propagate) { 2496 | if (key) observer.emit('set', path + key, val) 2497 | // also notify observer that the object itself changed 2498 | // but only do so when it's a immediate property. this 2499 | // avoids duplicate event firing. 2500 | if (rawPath && propagate) { 2501 | observer.emit('set', rawPath, obj, true) 2502 | } 2503 | }, 2504 | mutate: function (key, val, mutation) { 2505 | // if the Array is a root value 2506 | // the key will be null 2507 | var fixedPath = key ? path + key : rawPath 2508 | observer.emit('mutate', fixedPath, val, mutation) 2509 | // also emit set for Array's length when it mutates 2510 | var m = mutation.method 2511 | if (m !== 'sort' && m !== 'reverse') { 2512 | observer.emit('set', fixedPath + '.length', val.length) 2513 | } 2514 | } 2515 | } 2516 | 2517 | // attach the listeners to the child observer. 2518 | // now all the events will propagate upwards. 2519 | emitter 2520 | .on('get', proxies.get) 2521 | .on('set', proxies.set) 2522 | .on('mutate', proxies.mutate) 2523 | 2524 | if (alreadyConverted) { 2525 | // for objects that have already been converted, 2526 | // emit set events for everything inside 2527 | emitSet(obj) 2528 | } else { 2529 | watch(obj) 2530 | } 2531 | } 2532 | 2533 | /** 2534 | * Cancel observation, turn off the listeners. 2535 | */ 2536 | function unobserve (obj, path, observer) { 2537 | 2538 | if (!obj || !obj.__emitter__) return 2539 | 2540 | path = path ? path + '.' : '' 2541 | var proxies = observer.proxies[path] 2542 | if (!proxies) return 2543 | 2544 | // turn off listeners 2545 | obj.__emitter__ 2546 | .off('get', proxies.get) 2547 | .off('set', proxies.set) 2548 | .off('mutate', proxies.mutate) 2549 | 2550 | // remove reference 2551 | observer.proxies[path] = null 2552 | } 2553 | 2554 | // Expose API ----------------------------------------------------------------- 2555 | 2556 | var pub = module.exports = { 2557 | 2558 | // whether to emit get events 2559 | // only enabled during dependency parsing 2560 | shouldGet : false, 2561 | 2562 | observe : observe, 2563 | unobserve : unobserve, 2564 | ensurePath : ensurePath, 2565 | copyPaths : copyPaths, 2566 | watch : watch, 2567 | convert : convert, 2568 | convertKey : convertKey 2569 | } 2570 | }); 2571 | require.register("vue/src/directive.js", function(exports, require, module){ 2572 | var dirId = 1, 2573 | ARG_RE = /^[\w\$-]+$/, 2574 | FILTER_TOKEN_RE = /[^\s'"]+|'[^']+'|"[^"]+"/g, 2575 | NESTING_RE = /^\$(parent|root)\./, 2576 | SINGLE_VAR_RE = /^[\w\.$]+$/, 2577 | QUOTE_RE = /"/g 2578 | 2579 | /** 2580 | * Directive class 2581 | * represents a single directive instance in the DOM 2582 | */ 2583 | function Directive (name, ast, definition, compiler, el) { 2584 | 2585 | this.id = dirId++ 2586 | this.name = name 2587 | this.compiler = compiler 2588 | this.vm = compiler.vm 2589 | this.el = el 2590 | this.computeFilters = false 2591 | this.key = ast.key 2592 | this.arg = ast.arg 2593 | this.expression = ast.expression 2594 | 2595 | var isEmpty = this.expression === '' 2596 | 2597 | // mix in properties from the directive definition 2598 | if (typeof definition === 'function') { 2599 | this[isEmpty ? 'bind' : '_update'] = definition 2600 | } else { 2601 | for (var prop in definition) { 2602 | if (prop === 'unbind' || prop === 'update') { 2603 | this['_' + prop] = definition[prop] 2604 | } else { 2605 | this[prop] = definition[prop] 2606 | } 2607 | } 2608 | } 2609 | 2610 | // empty expression, we're done. 2611 | if (isEmpty || this.isEmpty) { 2612 | this.isEmpty = true 2613 | return 2614 | } 2615 | 2616 | this.expression = ( 2617 | this.isLiteral 2618 | ? compiler.eval(this.expression) 2619 | : this.expression 2620 | ).trim() 2621 | 2622 | var filters = ast.filters, 2623 | filter, fn, i, l, computed 2624 | if (filters) { 2625 | this.filters = [] 2626 | for (i = 0, l = filters.length; i < l; i++) { 2627 | filter = filters[i] 2628 | fn = this.compiler.getOption('filters', filter.name) 2629 | if (fn) { 2630 | filter.apply = fn 2631 | this.filters.push(filter) 2632 | if (fn.computed) { 2633 | computed = true 2634 | } 2635 | } 2636 | } 2637 | } 2638 | 2639 | if (!this.filters || !this.filters.length) { 2640 | this.filters = null 2641 | } 2642 | 2643 | if (computed) { 2644 | this.computedKey = Directive.inlineFilters(this.key, this.filters) 2645 | this.filters = null 2646 | } 2647 | 2648 | this.isExp = 2649 | computed || 2650 | !SINGLE_VAR_RE.test(this.key) || 2651 | NESTING_RE.test(this.key) 2652 | 2653 | } 2654 | 2655 | var DirProto = Directive.prototype 2656 | 2657 | /** 2658 | * called when a new value is set 2659 | * for computed properties, this will only be called once 2660 | * during initialization. 2661 | */ 2662 | DirProto.update = function (value, init) { 2663 | if (init || value !== this.value || (value && typeof value === 'object')) { 2664 | this.value = value 2665 | if (this._update) { 2666 | this._update( 2667 | this.filters && !this.computeFilters 2668 | ? this.applyFilters(value) 2669 | : value, 2670 | init 2671 | ) 2672 | } 2673 | } 2674 | } 2675 | 2676 | /** 2677 | * pipe the value through filters 2678 | */ 2679 | DirProto.applyFilters = function (value) { 2680 | var filtered = value, filter 2681 | for (var i = 0, l = this.filters.length; i < l; i++) { 2682 | filter = this.filters[i] 2683 | filtered = filter.apply.apply(this.vm, [filtered].concat(filter.args)) 2684 | } 2685 | return filtered 2686 | } 2687 | 2688 | /** 2689 | * Unbind diretive 2690 | */ 2691 | DirProto.unbind = function () { 2692 | // this can be called before the el is even assigned... 2693 | if (!this.el || !this.vm) return 2694 | if (this._unbind) this._unbind() 2695 | this.vm = this.el = this.binding = this.compiler = null 2696 | } 2697 | 2698 | // Exposed static methods ----------------------------------------------------- 2699 | 2700 | /** 2701 | * Parse a directive string into an Array of 2702 | * AST-like objects representing directives 2703 | */ 2704 | Directive.parse = function (str) { 2705 | 2706 | var inSingle = false, 2707 | inDouble = false, 2708 | curly = 0, 2709 | square = 0, 2710 | paren = 0, 2711 | begin = 0, 2712 | argIndex = 0, 2713 | dirs = [], 2714 | dir = {}, 2715 | lastFilterIndex = 0, 2716 | arg 2717 | 2718 | for (var c, i = 0, l = str.length; i < l; i++) { 2719 | c = str.charAt(i) 2720 | if (inSingle) { 2721 | // check single quote 2722 | if (c === "'") inSingle = !inSingle 2723 | } else if (inDouble) { 2724 | // check double quote 2725 | if (c === '"') inDouble = !inDouble 2726 | } else if (c === ',' && !paren && !curly && !square) { 2727 | // reached the end of a directive 2728 | pushDir() 2729 | // reset & skip the comma 2730 | dir = {} 2731 | begin = argIndex = lastFilterIndex = i + 1 2732 | } else if (c === ':' && !dir.key && !dir.arg) { 2733 | // argument 2734 | arg = str.slice(begin, i).trim() 2735 | if (ARG_RE.test(arg)) { 2736 | argIndex = i + 1 2737 | dir.arg = str.slice(begin, i).trim() 2738 | } 2739 | } else if (c === '|' && str.charAt(i + 1) !== '|' && str.charAt(i - 1) !== '|') { 2740 | if (dir.key === undefined) { 2741 | // first filter, end of key 2742 | lastFilterIndex = i + 1 2743 | dir.key = str.slice(argIndex, i).trim() 2744 | } else { 2745 | // already has filter 2746 | pushFilter() 2747 | } 2748 | } else if (c === '"') { 2749 | inDouble = true 2750 | } else if (c === "'") { 2751 | inSingle = true 2752 | } else if (c === '(') { 2753 | paren++ 2754 | } else if (c === ')') { 2755 | paren-- 2756 | } else if (c === '[') { 2757 | square++ 2758 | } else if (c === ']') { 2759 | square-- 2760 | } else if (c === '{') { 2761 | curly++ 2762 | } else if (c === '}') { 2763 | curly-- 2764 | } 2765 | } 2766 | if (i === 0 || begin !== i) { 2767 | pushDir() 2768 | } 2769 | 2770 | function pushDir () { 2771 | dir.expression = str.slice(begin, i).trim() 2772 | if (dir.key === undefined) { 2773 | dir.key = str.slice(argIndex, i).trim() 2774 | } else if (lastFilterIndex !== begin) { 2775 | pushFilter() 2776 | } 2777 | if (i === 0 || dir.key) { 2778 | dirs.push(dir) 2779 | } 2780 | } 2781 | 2782 | function pushFilter () { 2783 | var exp = str.slice(lastFilterIndex, i).trim(), 2784 | filter 2785 | if (exp) { 2786 | filter = {} 2787 | var tokens = exp.match(FILTER_TOKEN_RE) 2788 | filter.name = tokens[0] 2789 | filter.args = tokens.length > 1 ? tokens.slice(1) : null 2790 | } 2791 | if (filter) { 2792 | (dir.filters = dir.filters || []).push(filter) 2793 | } 2794 | lastFilterIndex = i + 1 2795 | } 2796 | 2797 | return dirs 2798 | } 2799 | 2800 | /** 2801 | * Inline computed filters so they become part 2802 | * of the expression 2803 | */ 2804 | Directive.inlineFilters = function (key, filters) { 2805 | var args, filter 2806 | for (var i = 0, l = filters.length; i < l; i++) { 2807 | filter = filters[i] 2808 | args = filter.args 2809 | ? ',"' + filter.args.map(escapeQuote).join('","') + '"' 2810 | : '' 2811 | key = 'this.$compiler.getOption("filters", "' + 2812 | filter.name + 2813 | '").call(this,' + 2814 | key + args + 2815 | ')' 2816 | } 2817 | return key 2818 | } 2819 | 2820 | /** 2821 | * Convert double quotes to single quotes 2822 | * so they don't mess up the generated function body 2823 | */ 2824 | function escapeQuote (v) { 2825 | return v.indexOf('"') > -1 2826 | ? v.replace(QUOTE_RE, '\'') 2827 | : v 2828 | } 2829 | 2830 | module.exports = Directive 2831 | }); 2832 | require.register("vue/src/exp-parser.js", function(exports, require, module){ 2833 | var utils = require('./utils'), 2834 | STR_SAVE_RE = /"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'/g, 2835 | STR_RESTORE_RE = /"(\d+)"/g, 2836 | NEWLINE_RE = /\n/g, 2837 | CTOR_RE = new RegExp('constructor'.split('').join('[\'"+, ]*')), 2838 | UNICODE_RE = /\\u\d\d\d\d/ 2839 | 2840 | // Variable extraction scooped from https://github.com/RubyLouvre/avalon 2841 | 2842 | var KEYWORDS = 2843 | // keywords 2844 | 'break,case,catch,continue,debugger,default,delete,do,else,false' + 2845 | ',finally,for,function,if,in,instanceof,new,null,return,switch,this' + 2846 | ',throw,true,try,typeof,var,void,while,with,undefined' + 2847 | // reserved 2848 | ',abstract,boolean,byte,char,class,const,double,enum,export,extends' + 2849 | ',final,float,goto,implements,import,int,interface,long,native' + 2850 | ',package,private,protected,public,short,static,super,synchronized' + 2851 | ',throws,transient,volatile' + 2852 | // ECMA 5 - use strict 2853 | ',arguments,let,yield' + 2854 | // allow using Math in expressions 2855 | ',Math', 2856 | 2857 | KEYWORDS_RE = new RegExp(["\\b" + KEYWORDS.replace(/,/g, '\\b|\\b') + "\\b"].join('|'), 'g'), 2858 | REMOVE_RE = /\/\*(?:.|\n)*?\*\/|\/\/[^\n]*\n|\/\/[^\n]*$|'[^']*'|"[^"]*"|[\s\t\n]*\.[\s\t\n]*[$\w\.]+/g, 2859 | SPLIT_RE = /[^\w$]+/g, 2860 | NUMBER_RE = /\b\d[^,]*/g, 2861 | BOUNDARY_RE = /^,+|,+$/g 2862 | 2863 | /** 2864 | * Strip top level variable names from a snippet of JS expression 2865 | */ 2866 | function getVariables (code) { 2867 | code = code 2868 | .replace(REMOVE_RE, '') 2869 | .replace(SPLIT_RE, ',') 2870 | .replace(KEYWORDS_RE, '') 2871 | .replace(NUMBER_RE, '') 2872 | .replace(BOUNDARY_RE, '') 2873 | return code 2874 | ? code.split(/,+/) 2875 | : [] 2876 | } 2877 | 2878 | /** 2879 | * A given path could potentially exist not on the 2880 | * current compiler, but up in the parent chain somewhere. 2881 | * This function generates an access relationship string 2882 | * that can be used in the getter function by walking up 2883 | * the parent chain to check for key existence. 2884 | * 2885 | * It stops at top parent if no vm in the chain has the 2886 | * key. It then creates any missing bindings on the 2887 | * final resolved vm. 2888 | */ 2889 | function traceScope (path, compiler, data) { 2890 | var rel = '', 2891 | dist = 0, 2892 | self = compiler 2893 | 2894 | if (data && utils.get(data, path) !== undefined) { 2895 | // hack: temporarily attached data 2896 | return '$temp.' 2897 | } 2898 | 2899 | while (compiler) { 2900 | if (compiler.hasKey(path)) { 2901 | break 2902 | } else { 2903 | compiler = compiler.parent 2904 | dist++ 2905 | } 2906 | } 2907 | if (compiler) { 2908 | while (dist--) { 2909 | rel += '$parent.' 2910 | } 2911 | if (!compiler.bindings[path] && path.charAt(0) !== '$') { 2912 | compiler.createBinding(path) 2913 | } 2914 | } else { 2915 | self.createBinding(path) 2916 | } 2917 | return rel 2918 | } 2919 | 2920 | /** 2921 | * Create a function from a string... 2922 | * this looks like evil magic but since all variables are limited 2923 | * to the VM's data it's actually properly sandboxed 2924 | */ 2925 | function makeGetter (exp, raw) { 2926 | var fn 2927 | try { 2928 | fn = new Function(exp) 2929 | } catch (e) { 2930 | utils.warn('Error parsing expression: ' + raw) 2931 | } 2932 | return fn 2933 | } 2934 | 2935 | /** 2936 | * Escape a leading dollar sign for regex construction 2937 | */ 2938 | function escapeDollar (v) { 2939 | return v.charAt(0) === '$' 2940 | ? '\\' + v 2941 | : v 2942 | } 2943 | 2944 | /** 2945 | * Parse and return an anonymous computed property getter function 2946 | * from an arbitrary expression, together with a list of paths to be 2947 | * created as bindings. 2948 | */ 2949 | exports.parse = function (exp, compiler, data) { 2950 | // unicode and 'constructor' are not allowed for XSS security. 2951 | if (UNICODE_RE.test(exp) || CTOR_RE.test(exp)) { 2952 | utils.warn('Unsafe expression: ' + exp) 2953 | return 2954 | } 2955 | // extract variable names 2956 | var vars = getVariables(exp) 2957 | if (!vars.length) { 2958 | return makeGetter('return ' + exp, exp) 2959 | } 2960 | vars = utils.unique(vars) 2961 | 2962 | var accessors = '', 2963 | has = utils.hash(), 2964 | strings = [], 2965 | // construct a regex to extract all valid variable paths 2966 | // ones that begin with "$" are particularly tricky 2967 | // because we can't use \b for them 2968 | pathRE = new RegExp( 2969 | "[^$\\w\\.](" + 2970 | vars.map(escapeDollar).join('|') + 2971 | ")[$\\w\\.]*\\b", 'g' 2972 | ), 2973 | body = (' ' + exp) 2974 | .replace(STR_SAVE_RE, saveStrings) 2975 | .replace(pathRE, replacePath) 2976 | .replace(STR_RESTORE_RE, restoreStrings) 2977 | 2978 | body = accessors + 'return ' + body 2979 | 2980 | function saveStrings (str) { 2981 | var i = strings.length 2982 | // escape newlines in strings so the expression 2983 | // can be correctly evaluated 2984 | strings[i] = str.replace(NEWLINE_RE, '\\n') 2985 | return '"' + i + '"' 2986 | } 2987 | 2988 | function replacePath (path) { 2989 | // keep track of the first char 2990 | var c = path.charAt(0) 2991 | path = path.slice(1) 2992 | var val = 'this.' + traceScope(path, compiler, data) + path 2993 | if (!has[path]) { 2994 | accessors += val + ';' 2995 | has[path] = 1 2996 | } 2997 | // don't forget to put that first char back 2998 | return c + val 2999 | } 3000 | 3001 | function restoreStrings (str, i) { 3002 | return strings[i] 3003 | } 3004 | 3005 | return makeGetter(body, exp) 3006 | } 3007 | 3008 | /** 3009 | * Evaluate an expression in the context of a compiler. 3010 | * Accepts additional data. 3011 | */ 3012 | exports.eval = function (exp, compiler, data) { 3013 | var getter = exports.parse(exp, compiler, data), res 3014 | if (getter) { 3015 | // hack: temporarily attach the additional data so 3016 | // it can be accessed in the getter 3017 | compiler.vm.$temp = data 3018 | res = getter.call(compiler.vm) 3019 | delete compiler.vm.$temp 3020 | } 3021 | return res 3022 | } 3023 | }); 3024 | require.register("vue/src/text-parser.js", function(exports, require, module){ 3025 | var openChar = '{', 3026 | endChar = '}', 3027 | ESCAPE_RE = /[-.*+?^${}()|[\]\/\\]/g, 3028 | BINDING_RE = buildInterpolationRegex(), 3029 | // lazy require 3030 | Directive 3031 | 3032 | function buildInterpolationRegex () { 3033 | var open = escapeRegex(openChar), 3034 | end = escapeRegex(endChar) 3035 | return new RegExp(open + open + open + '?(.+?)' + end + '?' + end + end) 3036 | } 3037 | 3038 | function escapeRegex (str) { 3039 | return str.replace(ESCAPE_RE, '\\$&') 3040 | } 3041 | 3042 | function setDelimiters (delimiters) { 3043 | exports.delimiters = delimiters 3044 | openChar = delimiters[0] 3045 | endChar = delimiters[1] 3046 | BINDING_RE = buildInterpolationRegex() 3047 | } 3048 | 3049 | /** 3050 | * Parse a piece of text, return an array of tokens 3051 | * token types: 3052 | * 1. plain string 3053 | * 2. object with key = binding key 3054 | * 3. object with key & html = true 3055 | */ 3056 | function parse (text) { 3057 | if (!BINDING_RE.test(text)) return null 3058 | var m, i, token, match, tokens = [] 3059 | /* jshint boss: true */ 3060 | while (m = text.match(BINDING_RE)) { 3061 | i = m.index 3062 | if (i > 0) tokens.push(text.slice(0, i)) 3063 | token = { key: m[1].trim() } 3064 | match = m[0] 3065 | token.html = 3066 | match.charAt(2) === openChar && 3067 | match.charAt(match.length - 3) === endChar 3068 | tokens.push(token) 3069 | text = text.slice(i + m[0].length) 3070 | } 3071 | if (text.length) tokens.push(text) 3072 | return tokens 3073 | } 3074 | 3075 | /** 3076 | * Parse an attribute value with possible interpolation tags 3077 | * return a Directive-friendly expression 3078 | * 3079 | * e.g. a {{b}} c => "a " + b + " c" 3080 | */ 3081 | function parseAttr (attr) { 3082 | Directive = Directive || require('./directive') 3083 | var tokens = parse(attr) 3084 | if (!tokens) return null 3085 | if (tokens.length === 1) return tokens[0].key 3086 | var res = [], token 3087 | for (var i = 0, l = tokens.length; i < l; i++) { 3088 | token = tokens[i] 3089 | res.push( 3090 | token.key 3091 | ? inlineFilters(token.key) 3092 | : ('"' + token + '"') 3093 | ) 3094 | } 3095 | return res.join('+') 3096 | } 3097 | 3098 | /** 3099 | * Inlines any possible filters in a binding 3100 | * so that we can combine everything into a huge expression 3101 | */ 3102 | function inlineFilters (key) { 3103 | if (key.indexOf('|') > -1) { 3104 | var dirs = Directive.parse(key), 3105 | dir = dirs && dirs[0] 3106 | if (dir && dir.filters) { 3107 | key = Directive.inlineFilters( 3108 | dir.key, 3109 | dir.filters 3110 | ) 3111 | } 3112 | } 3113 | return '(' + key + ')' 3114 | } 3115 | 3116 | exports.parse = parse 3117 | exports.parseAttr = parseAttr 3118 | exports.setDelimiters = setDelimiters 3119 | exports.delimiters = [openChar, endChar] 3120 | }); 3121 | require.register("vue/src/deps-parser.js", function(exports, require, module){ 3122 | var Emitter = require('./emitter'), 3123 | utils = require('./utils'), 3124 | Observer = require('./observer'), 3125 | catcher = new Emitter() 3126 | 3127 | /** 3128 | * Auto-extract the dependencies of a computed property 3129 | * by recording the getters triggered when evaluating it. 3130 | */ 3131 | function catchDeps (binding) { 3132 | if (binding.isFn) return 3133 | utils.log('\n- ' + binding.key) 3134 | var got = utils.hash() 3135 | binding.deps = [] 3136 | catcher.on('get', function (dep) { 3137 | var has = got[dep.key] 3138 | if ( 3139 | // avoid duplicate bindings 3140 | (has && has.compiler === dep.compiler) || 3141 | // avoid repeated items as dependency 3142 | // only when the binding is from self or the parent chain 3143 | (dep.compiler.repeat && !isParentOf(dep.compiler, binding.compiler)) 3144 | ) { 3145 | return 3146 | } 3147 | got[dep.key] = dep 3148 | utils.log(' - ' + dep.key) 3149 | binding.deps.push(dep) 3150 | dep.subs.push(binding) 3151 | }) 3152 | binding.value.$get() 3153 | catcher.off('get') 3154 | } 3155 | 3156 | /** 3157 | * Test if A is a parent of or equals B 3158 | */ 3159 | function isParentOf (a, b) { 3160 | while (b) { 3161 | if (a === b) { 3162 | return true 3163 | } 3164 | b = b.parent 3165 | } 3166 | } 3167 | 3168 | module.exports = { 3169 | 3170 | /** 3171 | * the observer that catches events triggered by getters 3172 | */ 3173 | catcher: catcher, 3174 | 3175 | /** 3176 | * parse a list of computed property bindings 3177 | */ 3178 | parse: function (bindings) { 3179 | utils.log('\nparsing dependencies...') 3180 | Observer.shouldGet = true 3181 | bindings.forEach(catchDeps) 3182 | Observer.shouldGet = false 3183 | utils.log('\ndone.') 3184 | } 3185 | 3186 | } 3187 | }); 3188 | require.register("vue/src/filters.js", function(exports, require, module){ 3189 | var utils = require('./utils'), 3190 | get = utils.get, 3191 | slice = [].slice, 3192 | QUOTE_RE = /^'.*'$/, 3193 | filters = module.exports = utils.hash() 3194 | 3195 | /** 3196 | * 'abc' => 'Abc' 3197 | */ 3198 | filters.capitalize = function (value) { 3199 | if (!value && value !== 0) return '' 3200 | value = value.toString() 3201 | return value.charAt(0).toUpperCase() + value.slice(1) 3202 | } 3203 | 3204 | /** 3205 | * 'abc' => 'ABC' 3206 | */ 3207 | filters.uppercase = function (value) { 3208 | return (value || value === 0) 3209 | ? value.toString().toUpperCase() 3210 | : '' 3211 | } 3212 | 3213 | /** 3214 | * 'AbC' => 'abc' 3215 | */ 3216 | filters.lowercase = function (value) { 3217 | return (value || value === 0) 3218 | ? value.toString().toLowerCase() 3219 | : '' 3220 | } 3221 | 3222 | /** 3223 | * 12345 => $12,345.00 3224 | */ 3225 | filters.currency = function (value, sign) { 3226 | if (!value && value !== 0) return '' 3227 | sign = sign || '$' 3228 | var s = Math.floor(value).toString(), 3229 | i = s.length % 3, 3230 | h = i > 0 ? (s.slice(0, i) + (s.length > 3 ? ',' : '')) : '', 3231 | f = '.' + value.toFixed(2).slice(-2) 3232 | return sign + h + s.slice(i).replace(/(\d{3})(?=\d)/g, '$1,') + f 3233 | } 3234 | 3235 | /** 3236 | * args: an array of strings corresponding to 3237 | * the single, double, triple ... forms of the word to 3238 | * be pluralized. When the number to be pluralized 3239 | * exceeds the length of the args, it will use the last 3240 | * entry in the array. 3241 | * 3242 | * e.g. ['single', 'double', 'triple', 'multiple'] 3243 | */ 3244 | filters.pluralize = function (value) { 3245 | var args = slice.call(arguments, 1) 3246 | return args.length > 1 3247 | ? (args[value - 1] || args[args.length - 1]) 3248 | : (args[value - 1] || args[0] + 's') 3249 | } 3250 | 3251 | /** 3252 | * A special filter that takes a handler function, 3253 | * wraps it so it only gets triggered on specific keypresses. 3254 | * 3255 | * v-on only 3256 | */ 3257 | 3258 | var keyCodes = { 3259 | enter : 13, 3260 | tab : 9, 3261 | 'delete' : 46, 3262 | up : 38, 3263 | left : 37, 3264 | right : 39, 3265 | down : 40, 3266 | esc : 27 3267 | } 3268 | 3269 | filters.key = function (handler, key) { 3270 | if (!handler) return 3271 | var code = keyCodes[key] 3272 | if (!code) { 3273 | code = parseInt(key, 10) 3274 | } 3275 | return function (e) { 3276 | if (e.keyCode === code) { 3277 | return handler.call(this, e) 3278 | } 3279 | } 3280 | } 3281 | 3282 | /** 3283 | * Filter filter for v-repeat 3284 | */ 3285 | filters.filterBy = function (arr, searchKey, delimiter, dataKey) { 3286 | 3287 | // allow optional `in` delimiter 3288 | // because why not 3289 | if (delimiter && delimiter !== 'in') { 3290 | dataKey = delimiter 3291 | } 3292 | 3293 | // get the search string 3294 | var search = stripQuotes(searchKey) || this.$get(searchKey) 3295 | if (!search) return arr 3296 | search = search.toLowerCase() 3297 | 3298 | // get the optional dataKey 3299 | dataKey = dataKey && (stripQuotes(dataKey) || this.$get(dataKey)) 3300 | 3301 | // convert object to array 3302 | if (!Array.isArray(arr)) { 3303 | arr = utils.objectToArray(arr) 3304 | } 3305 | 3306 | return arr.filter(function (item) { 3307 | return dataKey 3308 | ? contains(get(item, dataKey), search) 3309 | : contains(item, search) 3310 | }) 3311 | 3312 | } 3313 | 3314 | filters.filterBy.computed = true 3315 | 3316 | /** 3317 | * Sort fitler for v-repeat 3318 | */ 3319 | filters.orderBy = function (arr, sortKey, reverseKey) { 3320 | 3321 | var key = stripQuotes(sortKey) || this.$get(sortKey) 3322 | if (!key) return arr 3323 | 3324 | // convert object to array 3325 | if (!Array.isArray(arr)) { 3326 | arr = utils.objectToArray(arr) 3327 | } 3328 | 3329 | var order = 1 3330 | if (reverseKey) { 3331 | if (reverseKey === '-1') { 3332 | order = -1 3333 | } else if (reverseKey.charAt(0) === '!') { 3334 | reverseKey = reverseKey.slice(1) 3335 | order = this.$get(reverseKey) ? 1 : -1 3336 | } else { 3337 | order = this.$get(reverseKey) ? -1 : 1 3338 | } 3339 | } 3340 | 3341 | // sort on a copy to avoid mutating original array 3342 | return arr.slice().sort(function (a, b) { 3343 | a = get(a, key) 3344 | b = get(b, key) 3345 | return a === b ? 0 : a > b ? order : -order 3346 | }) 3347 | 3348 | } 3349 | 3350 | filters.orderBy.computed = true 3351 | 3352 | // Array filter helpers ------------------------------------------------------- 3353 | 3354 | /** 3355 | * String contain helper 3356 | */ 3357 | function contains (val, search) { 3358 | /* jshint eqeqeq: false */ 3359 | if (utils.isObject(val)) { 3360 | for (var key in val) { 3361 | if (contains(val[key], search)) { 3362 | return true 3363 | } 3364 | } 3365 | } else if (val != null) { 3366 | return val.toString().toLowerCase().indexOf(search) > -1 3367 | } 3368 | } 3369 | 3370 | /** 3371 | * Test whether a string is in quotes, 3372 | * if yes return stripped string 3373 | */ 3374 | function stripQuotes (str) { 3375 | if (QUOTE_RE.test(str)) { 3376 | return str.slice(1, -1) 3377 | } 3378 | } 3379 | }); 3380 | require.register("vue/src/transition.js", function(exports, require, module){ 3381 | var endEvents = sniffEndEvents(), 3382 | config = require('./config'), 3383 | // batch enter animations so we only force the layout once 3384 | Batcher = require('./batcher'), 3385 | batcher = new Batcher(), 3386 | // cache timer functions 3387 | setTO = window.setTimeout, 3388 | clearTO = window.clearTimeout, 3389 | // exit codes for testing 3390 | codes = { 3391 | CSS_E : 1, 3392 | CSS_L : 2, 3393 | JS_E : 3, 3394 | JS_L : 4, 3395 | CSS_SKIP : -1, 3396 | JS_SKIP : -2, 3397 | JS_SKIP_E : -3, 3398 | JS_SKIP_L : -4, 3399 | INIT : -5, 3400 | SKIP : -6 3401 | } 3402 | 3403 | // force layout before triggering transitions/animations 3404 | batcher._preFlush = function () { 3405 | /* jshint unused: false */ 3406 | var f = document.body.offsetHeight 3407 | } 3408 | 3409 | /** 3410 | * stage: 3411 | * 1 = enter 3412 | * 2 = leave 3413 | */ 3414 | var transition = module.exports = function (el, stage, cb, compiler) { 3415 | 3416 | var changeState = function () { 3417 | cb() 3418 | compiler.execHook(stage > 0 ? 'attached' : 'detached') 3419 | } 3420 | 3421 | if (compiler.init) { 3422 | changeState() 3423 | return codes.INIT 3424 | } 3425 | 3426 | var hasTransition = el.vue_trans === '', 3427 | hasAnimation = el.vue_anim === '', 3428 | effectId = el.vue_effect 3429 | 3430 | if (effectId) { 3431 | return applyTransitionFunctions( 3432 | el, 3433 | stage, 3434 | changeState, 3435 | effectId, 3436 | compiler 3437 | ) 3438 | } else if (hasTransition || hasAnimation) { 3439 | return applyTransitionClass( 3440 | el, 3441 | stage, 3442 | changeState, 3443 | hasAnimation 3444 | ) 3445 | } else { 3446 | changeState() 3447 | return codes.SKIP 3448 | } 3449 | 3450 | } 3451 | 3452 | transition.codes = codes 3453 | 3454 | /** 3455 | * Togggle a CSS class to trigger transition 3456 | */ 3457 | function applyTransitionClass (el, stage, changeState, hasAnimation) { 3458 | 3459 | if (!endEvents.trans) { 3460 | changeState() 3461 | return codes.CSS_SKIP 3462 | } 3463 | 3464 | // if the browser supports transition, 3465 | // it must have classList... 3466 | var onEnd, 3467 | classList = el.classList, 3468 | existingCallback = el.vue_trans_cb, 3469 | enterClass = config.enterClass, 3470 | leaveClass = config.leaveClass, 3471 | endEvent = hasAnimation ? endEvents.anim : endEvents.trans 3472 | 3473 | // cancel unfinished callbacks and jobs 3474 | if (existingCallback) { 3475 | el.removeEventListener(endEvent, existingCallback) 3476 | classList.remove(enterClass) 3477 | classList.remove(leaveClass) 3478 | el.vue_trans_cb = null 3479 | } 3480 | 3481 | if (stage > 0) { // enter 3482 | 3483 | // set to enter state before appending 3484 | classList.add(enterClass) 3485 | // append 3486 | changeState() 3487 | // trigger transition 3488 | if (!hasAnimation) { 3489 | batcher.push({ 3490 | execute: function () { 3491 | classList.remove(enterClass) 3492 | } 3493 | }) 3494 | } else { 3495 | onEnd = function (e) { 3496 | if (e.target === el) { 3497 | el.removeEventListener(endEvent, onEnd) 3498 | el.vue_trans_cb = null 3499 | classList.remove(enterClass) 3500 | } 3501 | } 3502 | el.addEventListener(endEvent, onEnd) 3503 | el.vue_trans_cb = onEnd 3504 | } 3505 | return codes.CSS_E 3506 | 3507 | } else { // leave 3508 | 3509 | if (el.offsetWidth || el.offsetHeight) { 3510 | // trigger hide transition 3511 | classList.add(leaveClass) 3512 | onEnd = function (e) { 3513 | if (e.target === el) { 3514 | el.removeEventListener(endEvent, onEnd) 3515 | el.vue_trans_cb = null 3516 | // actually remove node here 3517 | changeState() 3518 | classList.remove(leaveClass) 3519 | } 3520 | } 3521 | // attach transition end listener 3522 | el.addEventListener(endEvent, onEnd) 3523 | el.vue_trans_cb = onEnd 3524 | } else { 3525 | // directly remove invisible elements 3526 | changeState() 3527 | } 3528 | return codes.CSS_L 3529 | 3530 | } 3531 | 3532 | } 3533 | 3534 | function applyTransitionFunctions (el, stage, changeState, effectId, compiler) { 3535 | 3536 | var funcs = compiler.getOption('effects', effectId) 3537 | if (!funcs) { 3538 | changeState() 3539 | return codes.JS_SKIP 3540 | } 3541 | 3542 | var enter = funcs.enter, 3543 | leave = funcs.leave, 3544 | timeouts = el.vue_timeouts 3545 | 3546 | // clear previous timeouts 3547 | if (timeouts) { 3548 | var i = timeouts.length 3549 | while (i--) { 3550 | clearTO(timeouts[i]) 3551 | } 3552 | } 3553 | 3554 | timeouts = el.vue_timeouts = [] 3555 | function timeout (cb, delay) { 3556 | var id = setTO(function () { 3557 | cb() 3558 | timeouts.splice(timeouts.indexOf(id), 1) 3559 | if (!timeouts.length) { 3560 | el.vue_timeouts = null 3561 | } 3562 | }, delay) 3563 | timeouts.push(id) 3564 | } 3565 | 3566 | if (stage > 0) { // enter 3567 | if (typeof enter !== 'function') { 3568 | changeState() 3569 | return codes.JS_SKIP_E 3570 | } 3571 | enter(el, changeState, timeout) 3572 | return codes.JS_E 3573 | } else { // leave 3574 | if (typeof leave !== 'function') { 3575 | changeState() 3576 | return codes.JS_SKIP_L 3577 | } 3578 | leave(el, changeState, timeout) 3579 | return codes.JS_L 3580 | } 3581 | 3582 | } 3583 | 3584 | /** 3585 | * Sniff proper transition end event name 3586 | */ 3587 | function sniffEndEvents () { 3588 | var el = document.createElement('vue'), 3589 | defaultEvent = 'transitionend', 3590 | events = { 3591 | 'transition' : defaultEvent, 3592 | 'mozTransition' : defaultEvent, 3593 | 'webkitTransition' : 'webkitTransitionEnd' 3594 | }, 3595 | ret = {} 3596 | for (var name in events) { 3597 | if (el.style[name] !== undefined) { 3598 | ret.trans = events[name] 3599 | break 3600 | } 3601 | } 3602 | ret.anim = el.style.animation === '' 3603 | ? 'animationend' 3604 | : 'webkitAnimationEnd' 3605 | return ret 3606 | } 3607 | }); 3608 | require.register("vue/src/batcher.js", function(exports, require, module){ 3609 | var utils = require('./utils') 3610 | 3611 | function Batcher () { 3612 | this.reset() 3613 | } 3614 | 3615 | var BatcherProto = Batcher.prototype 3616 | 3617 | BatcherProto.push = function (job) { 3618 | if (!job.id || !this.has[job.id]) { 3619 | this.queue.push(job) 3620 | this.has[job.id] = job 3621 | if (!this.waiting) { 3622 | this.waiting = true 3623 | utils.nextTick(utils.bind(this.flush, this)) 3624 | } 3625 | } else if (job.override) { 3626 | var oldJob = this.has[job.id] 3627 | oldJob.cancelled = true 3628 | this.queue.push(job) 3629 | this.has[job.id] = job 3630 | } 3631 | } 3632 | 3633 | BatcherProto.flush = function () { 3634 | // before flush hook 3635 | if (this._preFlush) this._preFlush() 3636 | // do not cache length because more jobs might be pushed 3637 | // as we execute existing jobs 3638 | for (var i = 0; i < this.queue.length; i++) { 3639 | var job = this.queue[i] 3640 | if (!job.cancelled) { 3641 | job.execute() 3642 | } 3643 | } 3644 | this.reset() 3645 | } 3646 | 3647 | BatcherProto.reset = function () { 3648 | this.has = utils.hash() 3649 | this.queue = [] 3650 | this.waiting = false 3651 | } 3652 | 3653 | module.exports = Batcher 3654 | }); 3655 | require.register("vue/src/directives/index.js", function(exports, require, module){ 3656 | var utils = require('../utils'), 3657 | config = require('../config'), 3658 | transition = require('../transition'), 3659 | directives = module.exports = utils.hash() 3660 | 3661 | /** 3662 | * Nest and manage a Child VM 3663 | */ 3664 | directives.component = { 3665 | isLiteral: true, 3666 | bind: function () { 3667 | if (!this.el.vue_vm) { 3668 | this.childVM = new this.Ctor({ 3669 | el: this.el, 3670 | parent: this.vm 3671 | }) 3672 | } 3673 | }, 3674 | unbind: function () { 3675 | if (this.childVM) { 3676 | this.childVM.$destroy() 3677 | } 3678 | } 3679 | } 3680 | 3681 | /** 3682 | * Binding HTML attributes 3683 | */ 3684 | directives.attr = { 3685 | bind: function () { 3686 | var params = this.vm.$options.paramAttributes 3687 | this.isParam = params && params.indexOf(this.arg) > -1 3688 | }, 3689 | update: function (value) { 3690 | if (value || value === 0) { 3691 | this.el.setAttribute(this.arg, value) 3692 | } else { 3693 | this.el.removeAttribute(this.arg) 3694 | } 3695 | if (this.isParam) { 3696 | this.vm[this.arg] = utils.checkNumber(value) 3697 | } 3698 | } 3699 | } 3700 | 3701 | /** 3702 | * Binding textContent 3703 | */ 3704 | directives.text = { 3705 | bind: function () { 3706 | this.attr = this.el.nodeType === 3 3707 | ? 'nodeValue' 3708 | : 'textContent' 3709 | }, 3710 | update: function (value) { 3711 | this.el[this.attr] = utils.guard(value) 3712 | } 3713 | } 3714 | 3715 | /** 3716 | * Binding CSS display property 3717 | */ 3718 | directives.show = function (value) { 3719 | var el = this.el, 3720 | target = value ? '' : 'none', 3721 | change = function () { 3722 | el.style.display = target 3723 | } 3724 | transition(el, value ? 1 : -1, change, this.compiler) 3725 | } 3726 | 3727 | /** 3728 | * Binding CSS classes 3729 | */ 3730 | directives['class'] = function (value) { 3731 | if (this.arg) { 3732 | utils[value ? 'addClass' : 'removeClass'](this.el, this.arg) 3733 | } else { 3734 | if (this.lastVal) { 3735 | utils.removeClass(this.el, this.lastVal) 3736 | } 3737 | if (value) { 3738 | utils.addClass(this.el, value) 3739 | this.lastVal = value 3740 | } 3741 | } 3742 | } 3743 | 3744 | /** 3745 | * Only removed after the owner VM is ready 3746 | */ 3747 | directives.cloak = { 3748 | isEmpty: true, 3749 | bind: function () { 3750 | var el = this.el 3751 | this.compiler.observer.once('hook:ready', function () { 3752 | el.removeAttribute(config.prefix + '-cloak') 3753 | }) 3754 | } 3755 | } 3756 | 3757 | /** 3758 | * Store a reference to self in parent VM's $ 3759 | */ 3760 | directives.ref = { 3761 | isLiteral: true, 3762 | bind: function () { 3763 | var id = this.expression 3764 | if (id) { 3765 | this.vm.$parent.$[id] = this.vm 3766 | } 3767 | }, 3768 | unbind: function () { 3769 | var id = this.expression 3770 | if (id) { 3771 | delete this.vm.$parent.$[id] 3772 | } 3773 | } 3774 | } 3775 | 3776 | directives.on = require('./on') 3777 | directives.repeat = require('./repeat') 3778 | directives.model = require('./model') 3779 | directives['if'] = require('./if') 3780 | directives['with'] = require('./with') 3781 | directives.html = require('./html') 3782 | directives.style = require('./style') 3783 | directives.partial = require('./partial') 3784 | directives.view = require('./view') 3785 | }); 3786 | require.register("vue/src/directives/if.js", function(exports, require, module){ 3787 | var utils = require('../utils') 3788 | 3789 | /** 3790 | * Manages a conditional child VM 3791 | */ 3792 | module.exports = { 3793 | 3794 | bind: function () { 3795 | 3796 | this.parent = this.el.parentNode 3797 | this.ref = document.createComment('vue-if') 3798 | this.Ctor = this.compiler.resolveComponent(this.el) 3799 | 3800 | // insert ref 3801 | this.parent.insertBefore(this.ref, this.el) 3802 | this.parent.removeChild(this.el) 3803 | 3804 | if (utils.attr(this.el, 'view')) { 3805 | utils.warn( 3806 | 'Conflict: v-if cannot be used together with v-view. ' + 3807 | 'Just set v-view\'s binding value to empty string to empty it.' 3808 | ) 3809 | } 3810 | if (utils.attr(this.el, 'repeat')) { 3811 | utils.warn( 3812 | 'Conflict: v-if cannot be used together with v-repeat. ' + 3813 | 'Use `v-show` or the `filterBy` filter instead.' 3814 | ) 3815 | } 3816 | }, 3817 | 3818 | update: function (value) { 3819 | 3820 | if (!value) { 3821 | this._unbind() 3822 | } else if (!this.childVM) { 3823 | this.childVM = new this.Ctor({ 3824 | el: this.el.cloneNode(true), 3825 | parent: this.vm 3826 | }) 3827 | if (this.compiler.init) { 3828 | this.parent.insertBefore(this.childVM.$el, this.ref) 3829 | } else { 3830 | this.childVM.$before(this.ref) 3831 | } 3832 | } 3833 | 3834 | }, 3835 | 3836 | unbind: function () { 3837 | if (this.childVM) { 3838 | this.childVM.$destroy() 3839 | this.childVM = null 3840 | } 3841 | } 3842 | } 3843 | }); 3844 | require.register("vue/src/directives/repeat.js", function(exports, require, module){ 3845 | var utils = require('../utils'), 3846 | config = require('../config') 3847 | 3848 | /** 3849 | * Binding that manages VMs based on an Array 3850 | */ 3851 | module.exports = { 3852 | 3853 | bind: function () { 3854 | 3855 | this.identifier = '$r' + this.id 3856 | 3857 | // a hash to cache the same expressions on repeated instances 3858 | // so they don't have to be compiled for every single instance 3859 | this.expCache = utils.hash() 3860 | 3861 | var el = this.el, 3862 | ctn = this.container = el.parentNode 3863 | 3864 | // extract child Id, if any 3865 | this.childId = this.compiler.eval(utils.attr(el, 'ref')) 3866 | 3867 | // create a comment node as a reference node for DOM insertions 3868 | this.ref = document.createComment(config.prefix + '-repeat-' + this.key) 3869 | ctn.insertBefore(this.ref, el) 3870 | ctn.removeChild(el) 3871 | 3872 | this.collection = null 3873 | this.vms = null 3874 | 3875 | }, 3876 | 3877 | update: function (collection) { 3878 | 3879 | if (!Array.isArray(collection)) { 3880 | if (utils.isObject(collection)) { 3881 | collection = utils.objectToArray(collection) 3882 | } else { 3883 | utils.warn('v-repeat only accepts Array or Object values.') 3884 | } 3885 | } 3886 | 3887 | // keep reference of old data and VMs 3888 | // so we can reuse them if possible 3889 | this.oldVMs = this.vms 3890 | this.oldCollection = this.collection 3891 | collection = this.collection = collection || [] 3892 | 3893 | var isObject = collection[0] && utils.isObject(collection[0]) 3894 | this.vms = this.oldCollection 3895 | ? this.diff(collection, isObject) 3896 | : this.init(collection, isObject) 3897 | 3898 | if (this.childId) { 3899 | this.vm.$[this.childId] = this.vms 3900 | } 3901 | 3902 | }, 3903 | 3904 | init: function (collection, isObject) { 3905 | var vm, vms = [] 3906 | for (var i = 0, l = collection.length; i < l; i++) { 3907 | vm = this.build(collection[i], i, isObject) 3908 | vms.push(vm) 3909 | if (this.compiler.init) { 3910 | this.container.insertBefore(vm.$el, this.ref) 3911 | } else { 3912 | vm.$before(this.ref) 3913 | } 3914 | } 3915 | return vms 3916 | }, 3917 | 3918 | /** 3919 | * Diff the new array with the old 3920 | * and determine the minimum amount of DOM manipulations. 3921 | */ 3922 | diff: function (newCollection, isObject) { 3923 | 3924 | var i, l, item, vm, 3925 | oldIndex, 3926 | targetNext, 3927 | currentNext, 3928 | nextEl, 3929 | ctn = this.container, 3930 | oldVMs = this.oldVMs, 3931 | vms = [] 3932 | 3933 | vms.length = newCollection.length 3934 | 3935 | // first pass, collect new reused and new created 3936 | for (i = 0, l = newCollection.length; i < l; i++) { 3937 | item = newCollection[i] 3938 | if (isObject) { 3939 | item.$index = i 3940 | if (item.__emitter__ && item.__emitter__[this.identifier]) { 3941 | // this piece of data is being reused. 3942 | // record its final position in reused vms 3943 | item.$reused = true 3944 | } else { 3945 | vms[i] = this.build(item, i, isObject) 3946 | } 3947 | } else { 3948 | // we can't attach an identifier to primitive values 3949 | // so have to do an indexOf... 3950 | oldIndex = indexOf(oldVMs, item) 3951 | if (oldIndex > -1) { 3952 | // record the position on the existing vm 3953 | oldVMs[oldIndex].$reused = true 3954 | oldVMs[oldIndex].$data.$index = i 3955 | } else { 3956 | vms[i] = this.build(item, i, isObject) 3957 | } 3958 | } 3959 | } 3960 | 3961 | // second pass, collect old reused and destroy unused 3962 | for (i = 0, l = oldVMs.length; i < l; i++) { 3963 | vm = oldVMs[i] 3964 | item = this.arg 3965 | ? vm.$data[this.arg] 3966 | : vm.$data 3967 | if (item.$reused) { 3968 | vm.$reused = true 3969 | delete item.$reused 3970 | } 3971 | if (vm.$reused) { 3972 | // update the index to latest 3973 | vm.$index = item.$index 3974 | // the item could have had a new key 3975 | if (item.$key && item.$key !== vm.$key) { 3976 | vm.$key = item.$key 3977 | } 3978 | vms[vm.$index] = vm 3979 | } else { 3980 | // this one can be destroyed. 3981 | if (item.__emitter__) { 3982 | delete item.__emitter__[this.identifier] 3983 | } 3984 | vm.$destroy() 3985 | } 3986 | } 3987 | 3988 | // final pass, move/insert DOM elements 3989 | i = vms.length 3990 | while (i--) { 3991 | vm = vms[i] 3992 | item = vm.$data 3993 | targetNext = vms[i + 1] 3994 | if (vm.$reused) { 3995 | nextEl = vm.$el.nextSibling 3996 | // destroyed VMs' element might still be in the DOM 3997 | // due to transitions 3998 | while (!nextEl.vue_vm && nextEl !== this.ref) { 3999 | nextEl = nextEl.nextSibling 4000 | } 4001 | currentNext = nextEl.vue_vm 4002 | if (currentNext !== targetNext) { 4003 | if (!targetNext) { 4004 | ctn.insertBefore(vm.$el, this.ref) 4005 | } else { 4006 | nextEl = targetNext.$el 4007 | // new VMs' element might not be in the DOM yet 4008 | // due to transitions 4009 | while (!nextEl.parentNode) { 4010 | targetNext = vms[nextEl.vue_vm.$index + 1] 4011 | nextEl = targetNext 4012 | ? targetNext.$el 4013 | : this.ref 4014 | } 4015 | ctn.insertBefore(vm.$el, nextEl) 4016 | } 4017 | } 4018 | delete vm.$reused 4019 | delete item.$index 4020 | delete item.$key 4021 | } else { // a new vm 4022 | vm.$before(targetNext ? targetNext.$el : this.ref) 4023 | } 4024 | } 4025 | 4026 | return vms 4027 | }, 4028 | 4029 | build: function (data, index, isObject) { 4030 | 4031 | // wrap non-object values 4032 | var raw, alias, 4033 | wrap = !isObject || this.arg 4034 | if (wrap) { 4035 | raw = data 4036 | alias = this.arg || '$value' 4037 | data = {} 4038 | data[alias] = raw 4039 | } 4040 | data.$index = index 4041 | 4042 | var el = this.el.cloneNode(true), 4043 | Ctor = this.compiler.resolveComponent(el, data), 4044 | vm = new Ctor({ 4045 | el: el, 4046 | data: data, 4047 | parent: this.vm, 4048 | compilerOptions: { 4049 | repeat: true, 4050 | expCache: this.expCache 4051 | } 4052 | }) 4053 | 4054 | if (isObject) { 4055 | // attach an ienumerable identifier to the raw data 4056 | (raw || data).__emitter__[this.identifier] = true 4057 | } 4058 | 4059 | if (wrap) { 4060 | var self = this, 4061 | sync = function (val) { 4062 | self.lock = true 4063 | self.collection.$set(vm.$index, val) 4064 | self.lock = false 4065 | } 4066 | vm.$compiler.observer.on('change:' + alias, sync) 4067 | } 4068 | 4069 | return vm 4070 | 4071 | }, 4072 | 4073 | unbind: function () { 4074 | if (this.childId) { 4075 | delete this.vm.$[this.childId] 4076 | } 4077 | if (this.vms) { 4078 | var i = this.vms.length 4079 | while (i--) { 4080 | this.vms[i].$destroy() 4081 | } 4082 | } 4083 | } 4084 | } 4085 | 4086 | // Helpers -------------------------------------------------------------------- 4087 | 4088 | /** 4089 | * Find an object or a wrapped data object 4090 | * from an Array 4091 | */ 4092 | function indexOf (vms, obj) { 4093 | for (var vm, i = 0, l = vms.length; i < l; i++) { 4094 | vm = vms[i] 4095 | if (!vm.$reused && vm.$value === obj) { 4096 | return i 4097 | } 4098 | } 4099 | return -1 4100 | } 4101 | }); 4102 | require.register("vue/src/directives/on.js", function(exports, require, module){ 4103 | var utils = require('../utils') 4104 | 4105 | /** 4106 | * Binding for event listeners 4107 | */ 4108 | module.exports = { 4109 | 4110 | isFn: true, 4111 | 4112 | bind: function () { 4113 | this.context = this.binding.isExp 4114 | ? this.vm 4115 | : this.binding.compiler.vm 4116 | }, 4117 | 4118 | update: function (handler) { 4119 | if (typeof handler !== 'function') { 4120 | utils.warn('Directive "v-on:' + this.expression + '" expects a method.') 4121 | return 4122 | } 4123 | this._unbind() 4124 | var vm = this.vm, 4125 | context = this.context 4126 | this.handler = function (e) { 4127 | e.targetVM = vm 4128 | context.$event = e 4129 | var res = handler.call(context, e) 4130 | context.$event = null 4131 | return res 4132 | } 4133 | this.el.addEventListener(this.arg, this.handler) 4134 | }, 4135 | 4136 | unbind: function () { 4137 | this.el.removeEventListener(this.arg, this.handler) 4138 | } 4139 | } 4140 | }); 4141 | require.register("vue/src/directives/model.js", function(exports, require, module){ 4142 | var utils = require('../utils'), 4143 | isIE9 = navigator.userAgent.indexOf('MSIE 9.0') > 0, 4144 | filter = [].filter 4145 | 4146 | /** 4147 | * Returns an array of values from a multiple select 4148 | */ 4149 | function getMultipleSelectOptions (select) { 4150 | return filter 4151 | .call(select.options, function (option) { 4152 | return option.selected 4153 | }) 4154 | .map(function (option) { 4155 | return option.value || option.text 4156 | }) 4157 | } 4158 | 4159 | /** 4160 | * Two-way binding for form input elements 4161 | */ 4162 | module.exports = { 4163 | 4164 | bind: function () { 4165 | 4166 | var self = this, 4167 | el = self.el, 4168 | type = el.type, 4169 | tag = el.tagName 4170 | 4171 | self.lock = false 4172 | self.ownerVM = self.binding.compiler.vm 4173 | 4174 | // determine what event to listen to 4175 | self.event = 4176 | (self.compiler.options.lazy || 4177 | tag === 'SELECT' || 4178 | type === 'checkbox' || type === 'radio') 4179 | ? 'change' 4180 | : 'input' 4181 | 4182 | // determine the attribute to change when updating 4183 | self.attr = type === 'checkbox' 4184 | ? 'checked' 4185 | : (tag === 'INPUT' || tag === 'SELECT' || tag === 'TEXTAREA') 4186 | ? 'value' 4187 | : 'innerHTML' 4188 | 4189 | // select[multiple] support 4190 | if(tag === 'SELECT' && el.hasAttribute('multiple')) { 4191 | this.multi = true 4192 | } 4193 | 4194 | var compositionLock = false 4195 | self.cLock = function () { 4196 | compositionLock = true 4197 | } 4198 | self.cUnlock = function () { 4199 | compositionLock = false 4200 | } 4201 | el.addEventListener('compositionstart', this.cLock) 4202 | el.addEventListener('compositionend', this.cUnlock) 4203 | 4204 | // attach listener 4205 | self.set = self.filters 4206 | ? function () { 4207 | if (compositionLock) return 4208 | // if this directive has filters 4209 | // we need to let the vm.$set trigger 4210 | // update() so filters are applied. 4211 | // therefore we have to record cursor position 4212 | // so that after vm.$set changes the input 4213 | // value we can put the cursor back at where it is 4214 | var cursorPos 4215 | try { cursorPos = el.selectionStart } catch (e) {} 4216 | 4217 | self._set() 4218 | 4219 | // since updates are async 4220 | // we need to reset cursor position async too 4221 | utils.nextTick(function () { 4222 | if (cursorPos !== undefined) { 4223 | el.setSelectionRange(cursorPos, cursorPos) 4224 | } 4225 | }) 4226 | } 4227 | : function () { 4228 | if (compositionLock) return 4229 | // no filters, don't let it trigger update() 4230 | self.lock = true 4231 | 4232 | self._set() 4233 | 4234 | utils.nextTick(function () { 4235 | self.lock = false 4236 | }) 4237 | } 4238 | el.addEventListener(self.event, self.set) 4239 | 4240 | // fix shit for IE9 4241 | // since it doesn't fire input on backspace / del / cut 4242 | if (isIE9) { 4243 | self.onCut = function () { 4244 | // cut event fires before the value actually changes 4245 | utils.nextTick(function () { 4246 | self.set() 4247 | }) 4248 | } 4249 | self.onDel = function (e) { 4250 | if (e.keyCode === 46 || e.keyCode === 8) { 4251 | self.set() 4252 | } 4253 | } 4254 | el.addEventListener('cut', self.onCut) 4255 | el.addEventListener('keyup', self.onDel) 4256 | } 4257 | }, 4258 | 4259 | _set: function () { 4260 | this.ownerVM.$set( 4261 | this.key, this.multi 4262 | ? getMultipleSelectOptions(this.el) 4263 | : this.el[this.attr] 4264 | ) 4265 | }, 4266 | 4267 | update: function (value, init) { 4268 | /* jshint eqeqeq: false */ 4269 | // sync back inline value if initial data is undefined 4270 | if (init && value === undefined) { 4271 | return this._set() 4272 | } 4273 | if (this.lock) return 4274 | var el = this.el 4275 | if (el.tagName === 'SELECT') { // select dropdown 4276 | el.selectedIndex = -1 4277 | if(this.multi && Array.isArray(value)) { 4278 | value.forEach(this.updateSelect, this) 4279 | } else { 4280 | this.updateSelect(value) 4281 | } 4282 | } else if (el.type === 'radio') { // radio button 4283 | el.checked = value == el.value 4284 | } else if (el.type === 'checkbox') { // checkbox 4285 | el.checked = !!value 4286 | } else { 4287 | el[this.attr] = utils.guard(value) 4288 | } 4289 | }, 4290 | 4291 | updateSelect: function (value) { 4292 | /* jshint eqeqeq: false */ 4293 | // setting 38 | {% if todo.done %} 39 | (( todo.title )) 40 | {% else %} 41 | (( todo.title )) 42 | {% endif %} 43 | (( todo.pub_date.strftime('%Y-%m-%d %H:%M') )) 44 | 45 | 46 | {% endfor %} 47 | 48 | 49 | 50 |
    51 |
    Python
    52 | from flask import Flask, render_template, jsonify, request 53 | from flask_sqlalchemy import SQLAlchemy 54 | from datetime import datetime 55 | 56 | 57 | app = CustomFlask(__name__) 58 | app.config.from_pyfile('db.cfg') 59 | db = SQLAlchemy(app) 60 | 61 | 62 | class Todo(db.Model): 63 | __tablename__ = 'todos' 64 | id = db.Column('todo_id', db.Integer, primary_key=True) 65 | title = db.Column(db.String(60)) 66 | done = db.Column(db.Boolean) 67 | pub_date = db.Column(db.DateTime) 68 | 69 | def __init__(self, title): 70 | self.title = title 71 | self.done = False 72 | self.pub_date = datetime.utcnow() 73 | 74 | def get_dict(self): 75 | return { 76 | 'id': self.id, 77 | 'title': self.title, 78 | 'done': self.done, 79 | 'pub_date': self.pub_date.strftime('%Y-%m-%d %H:%M'), 80 | } 81 |
    82 |
    Python
    83 | def initialize_database(): 84 | app.logger.info('Database is not created, exec create_all() here.') 85 | db.create_all() 86 | data1 = Todo('todo1') 87 | data2 = Todo('todo2') 88 | db.session.add(data1) 89 | db.session.add(data2) 90 | db.session.commit() 91 | 92 | 93 | @app.route('/sqlalchemy') 94 | def sqlalchemy(): 95 | todos = [] 96 | try: 97 | todos = Todo.query.order_by(Todo.pub_date.desc()).all() 98 | except: 99 | initialize_database() 100 | print(todos) 101 | return render_template('sqlalchemy.html', todos=todos) 102 |
    103 |
    db.cfg
    104 | SQLALCHEMY_DATABASE_URI = 'sqlite:///test.db' 105 | SQLALCHEMY_TRACK_MODIFICATIONS = False 106 | SECRET_KEY = 'secret' 107 | DEBUG = True 108 |
    109 |
    HTML
    110 | {% for todo in todos %} 111 | <div class="todo"> 112 | <span class="title"> 113 | <input type="checkbox" (( " checked" if todo.done )) disabled></input> 114 | {% if todo.done %} 115 | <strike>(( todo.title ))</strike> 116 | {% else %} 117 | <span>(( todo.title ))</span> 118 | {% endif %} 119 | <span class="datetime">(( todo.pub_date.strftime('%Y-%m-%d %H:%M') ))</span> 120 | </span> 121 | </div> 122 | {% endfor %} 123 |
    124 |
    125 | 126 |

    Vue.js rendered TODO list via Ajax(axios).

    127 | 128 |
    129 |
    130 | 131 | 132 |
    133 | 134 | 135 | 136 | [[ todo.pub_date ]] 137 | 138 | 139 |
    140 |
    141 |
    142 | 143 |
    144 |
    Python
    145 | @app.route('/sqlalchemy/get', methods=['GET']) 146 | def sqlalchemy_get(): 147 | todos = Todo.query.order_by(Todo.pub_date.desc()).all() 148 | return jsonify(todos=[todo.get_dict() for todo in todos]) 149 | 150 | 151 | @app.route('/sqlalchemy/new', methods=['POST']) 152 | def sqlalchemy_new(): 153 | if request.json: 154 | db.session.add(Todo(request.json['title'])) 155 | db.session.commit() 156 | return jsonify(status='ok') # Oops: always ok... 157 | 158 | 159 | @app.route('/sqlalchemy/update', methods=['POST']) 160 | def sqlalchemy_update(): 161 | if request.json: 162 | todo = Todo.query.get(request.json['id']) 163 | todo.done = request.json['done'] 164 | todo.title = request.json['title'] 165 | db.session.commit() 166 | return jsonify(status='ok') # Oops: always ok... 167 |
    168 |
    JavaScript
    169 | var todo = new Vue({ 170 | el: "#todos-vue", 171 | delimiters: ["[[", "]]"], 172 | data: { 173 | todos: [], 174 | newTitle: '' 175 | }, 176 | mounted: function() { 177 | this.updateTodoList() 178 | }, 179 | methods: { 180 | updateTodoList: function() { 181 | axios.get('/sqlalchemy/get') 182 | .then(function(response) { 183 | this.todos = response.data.todos 184 | }.bind(this)) 185 | }, 186 | newTodo: function() { 187 | axios.post('/sqlalchemy/new', { title: this.newTitle }) 188 | .then(function(response) { 189 | this.updateTodoList() 190 | }.bind(this)) 191 | }, 192 | updateTodo: function(index) { 193 | axios.post('/sqlalchemy/update', this.todos[index]) 194 | .then(function(response) { 195 | this.updateTodoList() 196 | }.bind(this)) 197 | } 198 | } 199 | } 200 |
    201 |
    HTML
    202 | <div id="todos-vue"> 203 | <div> 204 | <label for="new">New task: </label> 205 | <input type="text" v-model="newTitle" id="new" @keyup.enter="newTodo"></input> 206 | <div class="todo" v-for="(todo, index) in todos" :key="todo.id"> 207 | <span class="title"> 208 | <input type="checkbox" v-model="todo.done"></input> 209 | <input type="text" v-model="todo.title" :disabled="todo.done"></input> 210 | <span class="datetime">[[ todo.pub_date ]]</span> 211 | <button @click.once="updateTodo(index)">Update</button> 212 | </span> 213 | </div> 214 | </div> 215 | </div> 216 |
    217 |
    218 | 219 | 252 | {% endblock %} 253 | -------------------------------------------------------------------------------- /templates/typescript.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | 3 | {% block script %} 4 | {% endblock %} 5 | 6 | {% block style %} 7 | {% endblock %} 8 | 9 | {% block body %} 10 |
    11 | 12 | {% endblock %} 13 | -------------------------------------------------------------------------------- /templates/vue.js_v0.10.3.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {% block head %} 4 | 5 | 6 | 7 | {% endblock %} 8 | 9 | {% block body %} 10 |

    vue.js with flask sample.

    11 |
    12 |

    Simple example

    13 |

    [[message]]

    14 |
    15 | 16 |
    17 |
    18 |

    Github commits

    19 | 20 | 21 |
    22 | 23 | 24 |
    34 |
    35 |
    36 |

    Add user sample

    37 |
    38 |
      39 |
    • 40 | [[name]] - [[email]] 41 |
    • 42 |
    43 |
    44 | 45 | 46 | 47 |
    48 |
      49 |
    • Name can not be empty.
    • 50 |
    • Please provide a valid email address.
    • 51 |
    52 |
    53 |
    54 |
    55 |

    Template sample - grid component

    56 |
    57 |
    58 |
    59 |
    60 |
    61 |

    SVG sample

    62 |
    63 |
    64 | 65 |
    66 |
    67 |
    68 | 69 | 70 | [[value]] 71 | 72 |
    73 |
    74 | 75 |
    76 |
    77 |
    78 |
    79 |

    Image Slider

    80 |
    81 | 82 | 83 | 84 | 85 | 86 | 87 |
    88 |
    89 |
    90 |

    Markdown Editor

    91 |
    92 | 93 |
    94 |
    95 |
    96 | 101 | 115 | 124 | 153 | 154 | 155 | {% endblock %} 156 | -------------------------------------------------------------------------------- /templates/vuex.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | 3 | {% block script %} 4 | 5 | 6 | 7 | {% endblock %} 8 | 9 | {% block style %} 10 | 43 | {% endblock %} 44 | 45 | {% block body %} 46 |

    Vuex simple example

    47 | 48 |
    49 | 50 |
    51 |
    JavaScript
    52 | const store = new Vuex.Store({ 53 | state: { 54 | todos: [{ 55 | text: "Excample Task 1", 56 | done: false, 57 | created: 0 58 | }, { 59 | text: "Excample Task 2", 60 | done: true, 61 | created: 1 62 | }] 63 | }, 64 | mutations: { 65 | addTodo: (state, payload) => state.todos.unshift(payload), 66 | toggleState: (state, payload) => { 67 | payload.done = !payload.done 68 | } 69 | } 70 | }) 71 | 72 | new Vue({ 73 | el: '#vuex', 74 | template: ` 75 | <div id="app"> 76 | <form @submit.prevent class="todo-form"> 77 | <input class="todo-input" type="text" v-model="contents"> 78 | <button type="submit" @click="addTodo(contents)">Add</button> 79 | </form> 80 | <div class="todos"> 81 | <transition-group name="flip-list" tag="div"> 82 | <div v-for="todo in todos" v-bind:key="todo.created" class="item" @click="toggleState(todo)" :class="{'done': todo.done}">{{todo.text}}</div> 83 | </transition-group> 84 | </div> 85 | </div> 86 | `, 87 | data: { 88 | contents: "" 89 | }, 90 | computed: { 91 | todos() { 92 | return store.state.todos.slice().sort(function (x, y) { return (x.done === y.done) ? y.created - x.created : x.done ? 1 : -1; }) 93 | } 94 | }, 95 | methods: { 96 | addTodo(contents) { 97 | if (this.contents === '') { 98 | return; 99 | } 100 | this.contents = ''; 101 | store.commit('addTodo', { text: contents, done: false, created: new Date().getTime() }) 102 | }, 103 | toggleState(item) { 104 | store.commit('toggleState', item) 105 | } 106 | } 107 | }) 108 |
    109 |
    110 |
    111 | 112 | 170 | {% endblock %} 171 | --------------------------------------------------------------------------------