Evan You
9 years ago
24 changed files with 1272 additions and 0 deletions
@ -0,0 +1,63 @@ |
|||
var apiURL = 'https://api.github.com/repos/vuejs/vue/commits?per_page=3&sha=' |
|||
var isPhantom = navigator.userAgent.indexOf('PhantomJS') > -1 |
|||
|
|||
/** |
|||
* Test mocks |
|||
*/ |
|||
|
|||
var mocks = { |
|||
master: [{sha:'111111111111', commit: {message:'one', author:{name:'Evan',date:'2014-10-15T13:52:58Z'}}},{sha:'111111111111', commit: {message:'hi', author:{name:'Evan',date:'2014-10-15T13:52:58Z'}}},{sha:'111111111111', commit: {message:'hi', author:{name:'Evan',date:'2014-10-15T13:52:58Z'}}}], |
|||
dev: [{sha:'222222222222', commit: {message:'two', author:{name:'Evan',date:'2014-10-15T13:52:58Z'}}},{sha:'111111111111', commit: {message:'hi', author:{name:'Evan',date:'2014-10-15T13:52:58Z'}}},{sha:'111111111111', commit: {message:'hi', author:{name:'Evan',date:'2014-10-15T13:52:58Z'}}}] |
|||
} |
|||
|
|||
function mockData () { |
|||
this.commits = mocks[this.currentBranch] |
|||
} |
|||
|
|||
/** |
|||
* Actual demo |
|||
*/ |
|||
|
|||
var demo = new Vue({ |
|||
|
|||
el: '#demo', |
|||
|
|||
data: { |
|||
branches: ['master', 'dev'], |
|||
currentBranch: 'master', |
|||
commits: null |
|||
}, |
|||
|
|||
created: function () { |
|||
this.fetchData() |
|||
}, |
|||
|
|||
watch: { |
|||
currentBranch: 'fetchData' |
|||
}, |
|||
|
|||
methods: { |
|||
truncate: function (v) { |
|||
var newline = v.indexOf('\n') |
|||
return newline > 0 ? v.slice(0, newline) : v |
|||
}, |
|||
formatDate: function (v) { |
|||
return v.replace(/T|Z/g, ' ') |
|||
}, |
|||
fetchData: function () { |
|||
// CasperJS fails at cross-domain XHR even with
|
|||
// --web-security=no, have to mock data here.
|
|||
if (isPhantom) { |
|||
return mockData.call(this) |
|||
} |
|||
var xhr = new XMLHttpRequest() |
|||
var self = this |
|||
xhr.open('GET', apiURL + self.currentBranch) |
|||
xhr.onload = function () { |
|||
self.commits = JSON.parse(xhr.responseText) |
|||
console.log(self.commits[0].html_url) |
|||
} |
|||
xhr.send() |
|||
} |
|||
} |
|||
}) |
@ -0,0 +1,46 @@ |
|||
<!DOCTYPE html> |
|||
<html> |
|||
<head> |
|||
<title>Vue.js commits example</title> |
|||
<style> |
|||
#demo { |
|||
font-family: 'Helvetica', Arial, sans-serif; |
|||
} |
|||
a { |
|||
text-decoration: none; |
|||
color: #f66; |
|||
} |
|||
li { |
|||
line-height: 1.5em; |
|||
margin-bottom: 20px; |
|||
} |
|||
.author, .date { |
|||
font-weight: bold; |
|||
} |
|||
</style> |
|||
<script src="../../dist/vue.js"></script> |
|||
</head> |
|||
<body> |
|||
<div id="demo"> |
|||
<h1>Latest Vue.js Commits</h1> |
|||
<template v-for="branch in branches"> |
|||
<input type="radio" |
|||
:id="branch" |
|||
:value="branch" |
|||
name="branch" |
|||
v-model="currentBranch"> |
|||
<label :for="branch">{{branch}}</label> |
|||
</template> |
|||
<p>vuejs/vue@{{currentBranch}}</p> |
|||
<ul> |
|||
<li v-for="record in commits"> |
|||
<a :href="record.html_url" target="_blank" class="commit">{{record.sha.slice(0, 7)}}</a> |
|||
- <span class="message">{{truncate(record.commit.message)}}</span><br> |
|||
by <span class="author">{{record.commit.author.name}}</span> |
|||
at <span class="date">{{formatDate(record.commit.author.date)}}</span> |
|||
</li> |
|||
</ul> |
|||
</div> |
|||
<script src="app.js"></script> |
|||
</body> |
|||
</html> |
@ -0,0 +1,104 @@ |
|||
<!DOCTYPE html> |
|||
<html lang="en"> |
|||
<head> |
|||
<meta charset="utf-8"> |
|||
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no"> |
|||
<title></title> |
|||
<script src="../../dist/vue.js"></script> |
|||
<script src="http://dynamicsjs.com/lib/dynamics.js"></script> |
|||
<link rel="stylesheet" href="style.css"> |
|||
<!-- template for the component --> |
|||
<script type="x/template" id="header-view-template"> |
|||
<div class="draggable-header-view" |
|||
@mousedown="startDrag" @touchstart="startDrag" |
|||
@mousemove="onDrag" @touchmove="onDrag" |
|||
@mouseup="stopDrag" @touchend="stopDrag" @mouseleave="stopDrag"> |
|||
<svg class="bg" width="320" height="560"> |
|||
<path :d="headerPath" fill="#3F51B5"></path> |
|||
</svg> |
|||
<div class="header"> |
|||
<slot name="header"></slot> |
|||
</div> |
|||
<div class="content" :style="contentPosition"> |
|||
<slot name="content"></slot> |
|||
</div> |
|||
</div> |
|||
</script> |
|||
</head> |
|||
<body> |
|||
|
|||
<div id="app" @touchmove.prevent> |
|||
<draggable-header-view> |
|||
<template slot="header"> |
|||
<h1>Elastic Draggable SVG Header</h1> |
|||
<p>with <a href="http://vuejs.org">Vue.js</a> + <a href="http://dynamicsjs.com">dynamics.js</a></p> |
|||
</template> |
|||
<template slot="content"> |
|||
<p>Note this is just an effect demo - there are of course many additional details if you want to use this in production, e.g. handling responsive sizes, reload threshold and content scrolling. Those are out of scope for this quick little hack. However, the idea is that you can hide them as internal details of a Vue.js component and expose a simple Web-Component-like interface.</p> |
|||
</template> |
|||
</draggable-header-view> |
|||
</div> |
|||
|
|||
<script> |
|||
Vue.component('draggable-header-view', { |
|||
template: '#header-view-template', |
|||
data: function () { |
|||
return { |
|||
dragging: false, |
|||
// quadratic bezier control point |
|||
c: { x: 160, y: 160 }, |
|||
// record drag start point |
|||
start: { x: 0, y: 0 } |
|||
} |
|||
}, |
|||
computed: { |
|||
headerPath: function () { |
|||
return 'M0,0 L320,0 320,160' + |
|||
'Q' + this.c.x + ',' + this.c.y + |
|||
' 0,160' |
|||
}, |
|||
contentPosition: function () { |
|||
var dy = this.c.y - 160 |
|||
var dampen = dy > 0 ? 2 : 4 |
|||
return { |
|||
transform: 'translate3d(0,' + dy / dampen + 'px,0)' |
|||
} |
|||
} |
|||
}, |
|||
methods: { |
|||
startDrag: function (e) { |
|||
e = e.changedTouches ? e.changedTouches[0] : e |
|||
this.dragging = true |
|||
this.start.x = e.pageX |
|||
this.start.y = e.pageY |
|||
}, |
|||
onDrag: function (e) { |
|||
e = e.changedTouches ? e.changedTouches[0] : e |
|||
if (this.dragging) { |
|||
this.c.x = 160 + (e.pageX - this.start.x) |
|||
// dampen vertical drag by a factor |
|||
var dy = e.pageY - this.start.y |
|||
var dampen = dy > 0 ? 1.5 : 4 |
|||
this.c.y = 160 + dy / dampen |
|||
} |
|||
}, |
|||
stopDrag: function () { |
|||
if (this.dragging) { |
|||
this.dragging = false |
|||
dynamics.animate(this.c, { |
|||
x: 160, |
|||
y: 160 |
|||
}, { |
|||
type: dynamics.spring, |
|||
duration: 700, |
|||
friction: 280 |
|||
}) |
|||
} |
|||
} |
|||
} |
|||
}) |
|||
|
|||
new Vue({ el: '#app' }) |
|||
</script> |
|||
</body> |
|||
</html> |
@ -0,0 +1,44 @@ |
|||
h1 { |
|||
font-weight: 300; |
|||
font-size: 1.8em; |
|||
margin-top: 0; |
|||
} |
|||
a { |
|||
color: #fff; |
|||
} |
|||
.draggable-header-view { |
|||
background-color: #fff; |
|||
box-shadow: 0 4px 16px rgba(0,0,0,.15); |
|||
width: 320px; |
|||
height: 560px; |
|||
overflow: hidden; |
|||
margin: 30px auto; |
|||
position: relative; |
|||
font-family: 'Roboto', Helvetica, Arial, sans-serif; |
|||
color: #fff; |
|||
font-size: 14px; |
|||
font-weight: 300; |
|||
-webkit-user-select: none; |
|||
-moz-user-select: none; |
|||
-ms-user-select: none; |
|||
user-select: none; |
|||
} |
|||
.draggable-header-view .bg { |
|||
position: absolute; |
|||
top: 0; |
|||
left: 0; |
|||
z-index: 0; |
|||
} |
|||
.draggable-header-view .header, .draggable-header-view .content { |
|||
position: relative; |
|||
z-index: 1; |
|||
padding: 30px; |
|||
box-sizing: border-box; |
|||
} |
|||
.draggable-header-view .header { |
|||
height: 160px; |
|||
} |
|||
.draggable-header-view .content { |
|||
color: #333; |
|||
line-height: 1.5em; |
|||
} |
@ -0,0 +1,50 @@ |
|||
var 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,}))$/ |
|||
|
|||
// Firebase ref
|
|||
var usersRef = new Firebase('https://vue-demo.firebaseIO.com/users') |
|||
|
|||
// create Vue app
|
|||
var app = new Vue({ |
|||
// element to mount to
|
|||
el: '#app', |
|||
// initial data
|
|||
data: { |
|||
newUser: { |
|||
name: '', |
|||
email: '' |
|||
} |
|||
}, |
|||
// firebase binding
|
|||
// https://github.com/vuejs/vuefire
|
|||
firebase: { |
|||
users: usersRef |
|||
}, |
|||
// computed property for form validation state
|
|||
computed: { |
|||
validation: function () { |
|||
return { |
|||
name: !!this.newUser.name.trim(), |
|||
email: emailRE.test(this.newUser.email) |
|||
} |
|||
}, |
|||
isValid: function () { |
|||
var validation = this.validation |
|||
return Object.keys(validation).every(function (key) { |
|||
return validation[key] |
|||
}) |
|||
} |
|||
}, |
|||
// methods
|
|||
methods: { |
|||
addUser: function () { |
|||
if (this.isValid) { |
|||
usersRef.push(this.newUser) |
|||
this.newUser.name = '' |
|||
this.newUser.email = '' |
|||
} |
|||
}, |
|||
removeUser: function (user) { |
|||
usersRef.child(user['.key']).remove() |
|||
} |
|||
} |
|||
}) |
@ -0,0 +1,34 @@ |
|||
<!DOCTYPE html> |
|||
<html lang="en"> |
|||
<head> |
|||
<title>Vue.js Firebase example</title> |
|||
<meta charset="utf-8"> |
|||
<link rel="stylesheet" type="text/css" href="style.css"> |
|||
<!-- Vue --> |
|||
<script src="../../dist/vue.js"></script> |
|||
<!-- Firebase --> |
|||
<script src="https://cdn.firebase.com/js/client/2.4.2/firebase.js"></script> |
|||
<!-- VueFire --> |
|||
<script src="https://cdn.jsdelivr.net/vuefire/1.0.1/vuefire.min.js"></script> |
|||
</head> |
|||
<body> |
|||
<div id="app"> |
|||
<ul> |
|||
<li class="user" v-for="user in users" transition> |
|||
<span>{{user.name}} - {{user.email}}</span> |
|||
<button v-on:click="removeUser(user)">X</button> |
|||
</li> |
|||
</ul> |
|||
<form id="form" v-on:submit.prevent="addUser"> |
|||
<input v-model="newUser.name"> |
|||
<input v-model="newUser.email"> |
|||
<input type="submit" value="Add User"> |
|||
</form> |
|||
<ul class="errors"> |
|||
<li v-show="!validation.name">Name cannot be empty.</li> |
|||
<li v-show="!validation.email">Please provide a valid email address.</li> |
|||
</ul> |
|||
</div> |
|||
<script src="app.js"></script> |
|||
</body> |
|||
</html> |
@ -0,0 +1,32 @@ |
|||
body { |
|||
font-family: Helvetica, Arial, sans-serif; |
|||
} |
|||
|
|||
ul { |
|||
padding: 0; |
|||
} |
|||
|
|||
.user { |
|||
height: 30px; |
|||
line-height: 30px; |
|||
padding: 10px; |
|||
border-top: 1px solid #eee; |
|||
overflow: hidden; |
|||
transition: all .25s ease; |
|||
} |
|||
|
|||
.user:last-child { |
|||
border-bottom: 1px solid #eee; |
|||
} |
|||
|
|||
.v-enter, .v-leave { |
|||
height: 0; |
|||
padding-top: 0; |
|||
padding-bottom: 0; |
|||
border-top-width: 0; |
|||
border-bottom-width: 0; |
|||
} |
|||
|
|||
.errors { |
|||
color: #f00; |
|||
} |
@ -0,0 +1,67 @@ |
|||
// register the grid component
|
|||
Vue.component('demo-grid', { |
|||
template: '#grid-template', |
|||
replace: true, |
|||
props: { |
|||
data: Array, |
|||
columns: Array, |
|||
filterKey: String |
|||
}, |
|||
data: function () { |
|||
var sortOrders = {} |
|||
this.columns.forEach(function (key) { |
|||
sortOrders[key] = 1 |
|||
}) |
|||
return { |
|||
sortKey: '', |
|||
sortOrders: sortOrders |
|||
} |
|||
}, |
|||
computed: { |
|||
filteredData: function () { |
|||
var sortKey = this.sortKey |
|||
var filterKey = this.filterKey && this.filterKey.toLowerCase() |
|||
var order = this.sortOrders[sortKey] || 1 |
|||
var data = this.data |
|||
if (filterKey) { |
|||
data = data.filter(function (row) { |
|||
return Object.keys(row).some(function (key) { |
|||
return String(row[key]).toLowerCase().indexOf(filterKey) > -1 |
|||
}) |
|||
}) |
|||
} |
|||
if (sortKey) { |
|||
data = data.slice().sort(function (a, b) { |
|||
a = a[sortKey] |
|||
b = b[sortKey] |
|||
return (a === b ? 0 : a > b ? 1 : -1) * order |
|||
}) |
|||
} |
|||
return data |
|||
} |
|||
}, |
|||
methods: { |
|||
capitalize: function (str) { |
|||
return str.charAt(0).toUpperCase() + str.slice(1) |
|||
}, |
|||
sortBy: function (key) { |
|||
this.sortKey = key |
|||
this.sortOrders[key] = this.sortOrders[key] * -1 |
|||
} |
|||
} |
|||
}) |
|||
|
|||
// bootstrap the demo
|
|||
var demo = new Vue({ |
|||
el: '#demo', |
|||
data: { |
|||
searchQuery: '', |
|||
gridColumns: ['name', 'power'], |
|||
gridData: [ |
|||
{ name: 'Chuck Norris', power: Infinity }, |
|||
{ name: 'Bruce Lee', power: 9000 }, |
|||
{ name: 'Jackie Chan', power: 7000 }, |
|||
{ name: 'Jet Li', power: 8000 } |
|||
] |
|||
} |
|||
}) |
@ -0,0 +1,50 @@ |
|||
<!DOCTYPE html> |
|||
<html lang="en"> |
|||
<head> |
|||
<meta charset="utf-8"> |
|||
<title>Vue.js grid component example</title> |
|||
<link rel="stylesheet" href="style.css"> |
|||
<script src="../../dist/vue.js"></script> |
|||
</head> |
|||
<body> |
|||
|
|||
<!-- component template --> |
|||
<script type="text/x-template" id="grid-template"> |
|||
<table> |
|||
<thead> |
|||
<tr> |
|||
<th v-for="key in columns" |
|||
@click="sortBy(key)" |
|||
:class="{ active: sortKey == key }"> |
|||
{{ capitalize(key) }} |
|||
<span class="arrow" :class="sortOrders[key] > 0 ? 'asc' : 'dsc'"> |
|||
</span> |
|||
</th> |
|||
</tr> |
|||
</thead> |
|||
<tbody> |
|||
<tr v-for="entry in filteredData"> |
|||
<td v-for="key in columns"> |
|||
{{entry[key]}} |
|||
</td> |
|||
</tr> |
|||
</tbody> |
|||
</table> |
|||
</script> |
|||
|
|||
<!-- demo root element --> |
|||
<div id="demo"> |
|||
<form id="search"> |
|||
Search <input name="query" v-model="searchQuery"> |
|||
</form> |
|||
<demo-grid |
|||
:data="gridData" |
|||
:columns="gridColumns" |
|||
:filter-key="searchQuery"> |
|||
</demo-grid> |
|||
</div> |
|||
|
|||
<script src="grid.js"></script> |
|||
|
|||
</body> |
|||
</html> |
@ -0,0 +1,58 @@ |
|||
body { |
|||
font-family: Helvetica Neue, Arial, sans-serif; |
|||
font-size: 14px; |
|||
color: #444; |
|||
} |
|||
|
|||
table { |
|||
border: 2px solid #42b983; |
|||
border-radius: 3px; |
|||
background-color: #fff; |
|||
} |
|||
|
|||
th { |
|||
background-color: #42b983; |
|||
color: rgba(255,255,255,0.66); |
|||
cursor: pointer; |
|||
-webkit-user-select: none; |
|||
-moz-user-select: none; |
|||
-user-select: none; |
|||
} |
|||
|
|||
td { |
|||
background-color: #f9f9f9; |
|||
} |
|||
|
|||
th, td { |
|||
min-width: 120px; |
|||
padding: 10px 20px; |
|||
} |
|||
|
|||
th.active { |
|||
color: #fff; |
|||
} |
|||
|
|||
th.active .arrow { |
|||
opacity: 1; |
|||
} |
|||
|
|||
.arrow { |
|||
display: inline-block; |
|||
vertical-align: middle; |
|||
width: 0; |
|||
height: 0; |
|||
margin-left: 5px; |
|||
opacity: 0.66; |
|||
} |
|||
|
|||
.arrow.asc { |
|||
border-left: 4px solid transparent; |
|||
border-right: 4px solid transparent; |
|||
border-bottom: 4px solid #fff; |
|||
} |
|||
|
|||
.arrow.dsc { |
|||
border-left: 4px solid transparent; |
|||
border-right: 4px solid transparent; |
|||
border-top: 4px solid #fff; |
|||
} |
@ -0,0 +1,34 @@ |
|||
<!DOCTYPE html> |
|||
<html lang="en"> |
|||
<head> |
|||
<meta charset="utf-8"> |
|||
<title>Vue.js markdown editor example</title> |
|||
<link rel="stylesheet" href="style.css"> |
|||
<script src="https://cdn.jsdelivr.net/marked/0.3.5/marked.min.js"></script> |
|||
<script src="https://cdn.jsdelivr.net/lodash/4.11.1/lodash.min.js"></script> |
|||
<script src="../../dist/vue.js"></script> |
|||
</head> |
|||
<body> |
|||
|
|||
<div id="editor"> |
|||
<textarea :value="input" @input="update"></textarea> |
|||
<div v-html="marked(input)"></div> |
|||
</div> |
|||
|
|||
<script> |
|||
new Vue({ |
|||
el: '#editor', |
|||
data: { |
|||
input: '# hello' |
|||
}, |
|||
methods: { |
|||
marked: marked, |
|||
update: function (e) { |
|||
this.input = e.target.value |
|||
} |
|||
} |
|||
}) |
|||
</script> |
|||
|
|||
</body> |
|||
</html> |
@ -0,0 +1,32 @@ |
|||
html, body, #editor { |
|||
margin: 0; |
|||
height: 100%; |
|||
font-family: 'Helvetica Neue', Arial, sans-serif; |
|||
color: #333; |
|||
} |
|||
|
|||
textarea, #editor div { |
|||
display: inline-block; |
|||
width: 49%; |
|||
height: 100%; |
|||
vertical-align: top; |
|||
-webkit-box-sizing: border-box; |
|||
-moz-box-sizing: border-box; |
|||
box-sizing: border-box; |
|||
padding: 0 20px; |
|||
} |
|||
|
|||
textarea { |
|||
border: none; |
|||
border-right: 1px solid #ccc; |
|||
resize: none; |
|||
outline: none; |
|||
background-color: #f6f6f6; |
|||
font-size: 14px; |
|||
font-family: 'Monaco', courier, monospace; |
|||
padding: 20px; |
|||
} |
|||
|
|||
code { |
|||
color: #f66; |
|||
} |
@ -0,0 +1,55 @@ |
|||
<!DOCTYPE html> |
|||
<html lang="en"> |
|||
<head> |
|||
<meta charset="utf-8"> |
|||
<title>Vue.js SVG example</title> |
|||
<link rel="stylesheet" href="style.css"> |
|||
<script src="../../dist/vue.js"></script> |
|||
</head> |
|||
<body> |
|||
|
|||
<!-- template for the polygraph component. --> |
|||
<script type="text/x-template" id="polygraph-template"> |
|||
<g> |
|||
<polygon :points="points"></polygon> |
|||
<circle cx="100" cy="100" r="80"></circle> |
|||
<axis-label |
|||
v-for="stat in stats" |
|||
:stat="stat" |
|||
:index="$index" |
|||
:total="stats.length"> |
|||
</axis-label> |
|||
</g> |
|||
</script> |
|||
|
|||
<!-- template for the axis label component. --> |
|||
<script type="text/x-template" id="axis-label-template"> |
|||
<text :x="point.x" :y="point.y">{{stat.label}}</text> |
|||
</script> |
|||
|
|||
<!-- demo root element --> |
|||
<div id="demo"> |
|||
<!-- Use the component --> |
|||
<svg width="200" height="200"> |
|||
<polygraph :stats="stats"></polygraph> |
|||
</svg> |
|||
<!-- controls --> |
|||
<div v-for="stat in stats"> |
|||
<label>{{stat.label}}</label> |
|||
<input type="range" v-model="stat.value" min="0" max="100"> |
|||
<span>{{stat.value}}</span> |
|||
<button @click="remove(stat)">X</button> |
|||
</div> |
|||
<form id="add"> |
|||
<input name="newlabel" v-model="newLabel"> |
|||
<button @click="add">Add a Stat</button> |
|||
</form> |
|||
<pre id="raw">{{json(stats)}}</pre> |
|||
</div> |
|||
|
|||
<p style="font-size:12px">* input[type="range"] requires IE10 or above.</p> |
|||
|
|||
<script src="svg.js"></script> |
|||
|
|||
</body> |
|||
</html> |
@ -0,0 +1,31 @@ |
|||
body { |
|||
font-family: Helvetica Neue, Arial, sans-serif; |
|||
} |
|||
|
|||
polygon { |
|||
fill: #42b983; |
|||
opacity: .75; |
|||
} |
|||
|
|||
circle { |
|||
fill: transparent; |
|||
stroke: #999; |
|||
} |
|||
|
|||
text { |
|||
font-family: Helvetica Neue, Arial, sans-serif; |
|||
font-size: 10px; |
|||
fill: #666; |
|||
} |
|||
|
|||
label { |
|||
display: inline-block; |
|||
margin-left: 10px; |
|||
width: 20px; |
|||
} |
|||
|
|||
#raw { |
|||
position: absolute; |
|||
top: 0; |
|||
left: 300px; |
|||
} |
@ -0,0 +1,90 @@ |
|||
// The raw data to observe
|
|||
var stats = [ |
|||
{ label: 'A', value: 100 }, |
|||
{ label: 'B', value: 100 }, |
|||
{ label: 'C', value: 100 }, |
|||
{ label: 'D', value: 100 }, |
|||
{ label: 'E', value: 100 }, |
|||
{ label: 'F', value: 100 } |
|||
] |
|||
|
|||
// A resusable polygon graph component
|
|||
Vue.component('polygraph', { |
|||
props: ['stats'], |
|||
template: '#polygraph-template', |
|||
computed: { |
|||
// a computed property for the polygon's points
|
|||
points: function () { |
|||
var total = this.stats.length |
|||
return this.stats.map(function (stat, i) { |
|||
var point = valueToPoint(stat.value, i, total) |
|||
return point.x + ',' + point.y |
|||
}).join(' ') |
|||
} |
|||
}, |
|||
components: { |
|||
// a sub component for the labels
|
|||
'axis-label': { |
|||
props: { |
|||
stat: Object, |
|||
index: Number, |
|||
total: Number |
|||
}, |
|||
template: '#axis-label-template', |
|||
computed: { |
|||
point: function () { |
|||
return valueToPoint( |
|||
+this.stat.value + 10, |
|||
this.index, |
|||
this.total |
|||
) |
|||
} |
|||
} |
|||
} |
|||
} |
|||
}) |
|||
|
|||
// math helper...
|
|||
function valueToPoint (value, index, total) { |
|||
var x = 0 |
|||
var y = -value * 0.8 |
|||
var angle = Math.PI * 2 / total * index |
|||
var cos = Math.cos(angle) |
|||
var sin = Math.sin(angle) |
|||
var tx = x * cos - y * sin + 100 |
|||
var ty = x * sin + y * cos + 100 |
|||
return { |
|||
x: tx, |
|||
y: ty |
|||
} |
|||
} |
|||
|
|||
// bootstrap the demo
|
|||
new Vue({ |
|||
el: '#demo', |
|||
data: { |
|||
newLabel: '', |
|||
stats: stats |
|||
}, |
|||
methods: { |
|||
json: function (val) { |
|||
return JSON.stringify(val, null, 2) |
|||
}, |
|||
add: function (e) { |
|||
e.preventDefault() |
|||
if (!this.newLabel) return |
|||
this.stats.push({ |
|||
label: this.newLabel, |
|||
value: 100 |
|||
}) |
|||
this.newLabel = '' |
|||
}, |
|||
remove: function (stat) { |
|||
if (this.stats.length > 3) { |
|||
this.stats.$remove(stat) |
|||
} else { |
|||
alert('Can\'t delete more!') |
|||
} |
|||
} |
|||
} |
|||
}) |
@ -0,0 +1,83 @@ |
|||
<!doctype html> |
|||
<html data-framework="vue"> |
|||
<head> |
|||
<meta charset="utf-8"> |
|||
<title>Vue.js • TodoMVC</title> |
|||
<link rel="stylesheet" href="node_modules/todomvc-app-css/index.css"> |
|||
<style> [v-cloak] { display: none; } </style> |
|||
</head> |
|||
<body> |
|||
<section class="todoapp"> |
|||
<header class="header"> |
|||
<h1>todos</h1> |
|||
<input class="new-todo" |
|||
autofocus autocomplete="off" |
|||
placeholder="What needs to be done?" |
|||
v-model="newTodo" |
|||
@keyup.enter="addTodo"> |
|||
</header> |
|||
<section class="main" v-show="todos.length" v-cloak> |
|||
<input class="toggle-all" type="checkbox" v-model="allDone"> |
|||
<ul class="todo-list"> |
|||
<li class="todo" |
|||
v-for="todo in filteredTodos" |
|||
:class="{completed: todo.completed, editing: todo == editedTodo}"> |
|||
<div class="view"> |
|||
<input class="toggle" type="checkbox" v-model="todo.completed"> |
|||
<label @dblclick="editTodo(todo)">{{todo.title}}</label> |
|||
<button class="destroy" @click="removeTodo(todo)"></button> |
|||
</div> |
|||
<input class="edit" type="text" |
|||
v-model="todo.title" |
|||
v-todo-focus="todo == editedTodo" |
|||
@blur="doneEdit(todo)" |
|||
@keyup.enter="doneEdit(todo)" |
|||
@keyup.esc="cancelEdit(todo)"> |
|||
</li> |
|||
</ul> |
|||
</section> |
|||
<footer class="footer" v-show="todos.length" v-cloak> |
|||
<span class="todo-count"> |
|||
<strong>{{ remaining }}</strong> {{ pluralize(remaining) }} left |
|||
</span> |
|||
<ul class="filters"> |
|||
<li><a href="#/all" :class="{selected: visibility == 'all'}">All</a></li> |
|||
<li><a href="#/active" :class="{selected: visibility == 'active'}">Active</a></li> |
|||
<li><a href="#/completed" :class="{selected: visibility == 'completed'}">Completed</a></li> |
|||
</ul> |
|||
<button class="clear-completed" @click="removeCompleted" v-show="todos.length > remaining"> |
|||
Clear completed |
|||
</button> |
|||
</footer> |
|||
</section> |
|||
<footer class="info"> |
|||
<p>Double-click to edit a todo</p> |
|||
<p>Written by <a href="http://evanyou.me">Evan You</a></p> |
|||
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p> |
|||
</footer> |
|||
|
|||
<!-- testing/benchmark only --> |
|||
<script> |
|||
var isPhantom = navigator.userAgent.indexOf('PhantomJS') > -1 |
|||
if (isPhantom) { |
|||
localStorage.clear() |
|||
} else { |
|||
var now = window.performance && window.performance.now |
|||
? function () { return window.performance.now() } |
|||
: Date.now |
|||
var metrics = { beforeLoad: now() } |
|||
} |
|||
</script> |
|||
<!-- end testing/bench --> |
|||
|
|||
<script src="../../dist/vue.js"></script> |
|||
<script>metrics.afterLoad = now()</script> |
|||
<script src="node_modules/director/build/director.js"></script> |
|||
<script src="js/store.js"></script> |
|||
<script>metrics.beforeRender = now()</script> |
|||
<script src="js/app.js"></script> |
|||
<script src="js/routes.js"></script> |
|||
<script>metrics.afterRender = now()</script> |
|||
<script src="perf.js"></script> |
|||
</body> |
|||
</html> |
@ -0,0 +1,125 @@ |
|||
/*global Vue, todoStorage */ |
|||
|
|||
(function (exports) { |
|||
|
|||
'use strict'; |
|||
|
|||
var filters = { |
|||
all: function (todos) { |
|||
return todos; |
|||
}, |
|||
active: function (todos) { |
|||
return todos.filter(function (todo) { |
|||
return !todo.completed; |
|||
}); |
|||
}, |
|||
completed: function (todos) { |
|||
return todos.filter(function (todo) { |
|||
return todo.completed; |
|||
}); |
|||
} |
|||
}; |
|||
|
|||
exports.app = new Vue({ |
|||
|
|||
// the root element that will be compiled
|
|||
el: '.todoapp', |
|||
|
|||
// app initial state
|
|||
data: { |
|||
todos: todoStorage.fetch(), |
|||
newTodo: '', |
|||
editedTodo: null, |
|||
visibility: 'all' |
|||
}, |
|||
|
|||
// watch todos change for localStorage persistence
|
|||
watch: { |
|||
todos: { |
|||
handler: function (todos) { |
|||
todoStorage.save(todos); |
|||
}, |
|||
deep: true |
|||
} |
|||
}, |
|||
|
|||
// computed properties
|
|||
// http://vuejs.org/guide/computed.html
|
|||
computed: { |
|||
filteredTodos: function () { |
|||
return filters[this.visibility](this.todos); |
|||
}, |
|||
remaining: function () { |
|||
return filters.active(this.todos).length; |
|||
}, |
|||
allDone: { |
|||
get: function () { |
|||
return this.remaining === 0; |
|||
}, |
|||
set: function (value) { |
|||
this.todos.forEach(function (todo) { |
|||
todo.completed = value; |
|||
}); |
|||
} |
|||
} |
|||
}, |
|||
|
|||
// methods that implement data logic.
|
|||
// note there's no DOM manipulation here at all.
|
|||
methods: { |
|||
pluralize: function (n) { |
|||
return n === 1 ? 'item' : 'items' |
|||
}, |
|||
|
|||
addTodo: function () { |
|||
var value = this.newTodo && this.newTodo.trim(); |
|||
if (!value) { |
|||
return; |
|||
} |
|||
this.todos.push({ title: value, completed: false }); |
|||
this.newTodo = ''; |
|||
}, |
|||
|
|||
removeTodo: function (todo) { |
|||
this.todos.$remove(todo); |
|||
}, |
|||
|
|||
editTodo: function (todo) { |
|||
this.beforeEditCache = todo.title; |
|||
this.editedTodo = todo; |
|||
}, |
|||
|
|||
doneEdit: function (todo) { |
|||
if (!this.editedTodo) { |
|||
return; |
|||
} |
|||
this.editedTodo = null; |
|||
todo.title = todo.title.trim(); |
|||
if (!todo.title) { |
|||
this.removeTodo(todo); |
|||
} |
|||
}, |
|||
|
|||
cancelEdit: function (todo) { |
|||
this.editedTodo = null; |
|||
todo.title = this.beforeEditCache; |
|||
}, |
|||
|
|||
removeCompleted: function () { |
|||
this.todos = filters.active(this.todos); |
|||
} |
|||
}, |
|||
|
|||
// a custom directive to wait for the DOM to be updated
|
|||
// before focusing on the input field.
|
|||
// http://vuejs.org/guide/custom-directive.html
|
|||
directives: { |
|||
'todo-focus': function (el, value) { |
|||
if (value) { |
|||
el.focus(); |
|||
} |
|||
} |
|||
} |
|||
}); |
|||
|
|||
})(window); |
@ -0,0 +1,24 @@ |
|||
/*global app, Router */ |
|||
|
|||
(function (app, Router) { |
|||
|
|||
'use strict'; |
|||
|
|||
var router = new Router(); |
|||
|
|||
['all', 'active', 'completed'].forEach(function (visibility) { |
|||
router.on(visibility, function () { |
|||
app.visibility = visibility; |
|||
}); |
|||
}); |
|||
|
|||
router.configure({ |
|||
notfound: function () { |
|||
window.location.hash = ''; |
|||
app.visibility = 'all'; |
|||
} |
|||
}); |
|||
|
|||
router.init(); |
|||
|
|||
})(app, Router); |
@ -0,0 +1,18 @@ |
|||
/*jshint unused:false */ |
|||
|
|||
(function (exports) { |
|||
|
|||
'use strict'; |
|||
|
|||
var STORAGE_KEY = 'todos-vuejs'; |
|||
|
|||
exports.todoStorage = { |
|||
fetch: function () { |
|||
return JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]'); |
|||
}, |
|||
save: function (todos) { |
|||
localStorage.setItem(STORAGE_KEY, JSON.stringify(todos)); |
|||
} |
|||
}; |
|||
|
|||
})(window); |
@ -0,0 +1,9 @@ |
|||
{ |
|||
"private": true, |
|||
"dependencies": { |
|||
"director": "^1.2.0", |
|||
"vue": "^1.0.0", |
|||
"todomvc-common": "^1.0.1", |
|||
"todomvc-app-css": "^2.0.0" |
|||
} |
|||
} |
@ -0,0 +1,78 @@ |
|||
setTimeout(function () { |
|||
|
|||
if (window.isPhantom) return |
|||
|
|||
// Initial load & render metrics
|
|||
|
|||
metrics.afterRenderAsync = now() |
|||
console.log('Vue load : ' + (metrics.afterLoad - metrics.beforeLoad).toFixed(2) + 'ms') |
|||
console.log('Render sync : ' + (metrics.afterRender - metrics.beforeRender).toFixed(2) + 'ms') |
|||
console.log('Render async : ' + (metrics.afterRenderAsync - metrics.beforeRender).toFixed(2) + 'ms') |
|||
console.log('Total sync : ' + (metrics.afterRender - metrics.beforeLoad).toFixed(2) + 'ms') |
|||
console.log('Total async : ' + (metrics.afterRenderAsync - metrics.beforeLoad).toFixed(2) + 'ms') |
|||
|
|||
// Benchmark
|
|||
// add 100 items
|
|||
// toggle them one by one
|
|||
// then delete them one by one
|
|||
|
|||
var benchSetting = window.location.search.match(/\bbenchmark=(\d+)/) |
|||
if (!benchSetting) return |
|||
|
|||
var itemsToAdd = +benchSetting[1], |
|||
render, |
|||
bench, |
|||
addTime, |
|||
toggleTime, |
|||
removeTime |
|||
|
|||
var start = now(), |
|||
last |
|||
|
|||
add() |
|||
|
|||
function add() { |
|||
last = now() |
|||
var newTodo = '12345', |
|||
todoInput = document.getElementById('new-todo') |
|||
for (var i = 0; i < itemsToAdd; i++) { |
|||
var keyupEvent = document.createEvent('Event'); |
|||
keyupEvent.initEvent('keyup', true, true); |
|||
keyupEvent.keyCode = 13; |
|||
app.newTodo = 'Something to do ' + i; |
|||
todoInput.dispatchEvent(keyupEvent) |
|||
} |
|||
setTimeout(toggle, 0) |
|||
} |
|||
|
|||
function toggle () { |
|||
addTime = now() - last |
|||
var checkboxes = document.querySelectorAll('.toggle') |
|||
for (var i = 0; i < checkboxes.length; i++) { |
|||
checkboxes[i].click() |
|||
} |
|||
last = now() |
|||
setTimeout(remove, 0) |
|||
} |
|||
|
|||
function remove () { |
|||
toggleTime = now() - last |
|||
var deleteButtons = document.querySelectorAll('.destroy'); |
|||
for (var i = 0; i < deleteButtons.length; i++) { |
|||
deleteButtons[i].click() |
|||
} |
|||
last = now() |
|||
setTimeout(report, 0) |
|||
} |
|||
|
|||
function report () { |
|||
bench = now() - start |
|||
removeTime = now() - last |
|||
console.log('\nBenchmark x ' + itemsToAdd) |
|||
console.log('add : ' + addTime.toFixed(2) + 'ms') |
|||
console.log('toggle : ' + toggleTime.toFixed(2) + 'ms') |
|||
console.log('remove : ' + removeTime.toFixed(2) + 'ms') |
|||
console.log('total : ' + bench.toFixed(2) + 'ms') |
|||
} |
|||
|
|||
}, 0) |
@ -0,0 +1,28 @@ |
|||
# Vue.js TodoMVC Example |
|||
|
|||
> Vue.js is a library for building interactive web interfaces. |
|||
It provides data-driven, nestable view components with a simple and flexible API. |
|||
|
|||
> _[Vue.js - vuejs.org](http://vuejs.org)_ |
|||
|
|||
## Learning Vue.js |
|||
The [Vue.js website](http://vuejs.org/) is a great resource to get started. |
|||
|
|||
Here are some links you may find helpful: |
|||
|
|||
* [Official Guide](http://vuejs.org/guide/) |
|||
* [API Reference](http://vuejs.org/api/) |
|||
* [Examples](http://vuejs.org/examples/) |
|||
* [Building Larger Apps with Vue.js](http://vuejs.org/guide/application.html) |
|||
|
|||
Get help from other Vue.js users: |
|||
|
|||
* [Vue.js official forum](http://forum.vuejs.org) |
|||
* [Vue.js on Twitter](https://twitter.com/vuejs) |
|||
* [Vue.js on Gitter](https://gitter.im/vuejs/vue) |
|||
|
|||
_If you have other helpful links to share, or find any of the links above no longer work, please [let us know](https://github.com/tastejs/todomvc/issues)._ |
|||
|
|||
## Credit |
|||
|
|||
This TodoMVC application was created by [Evan You](http://evanyou.me). |
@ -0,0 +1,61 @@ |
|||
<!DOCTYPE html> |
|||
<html lang="en"> |
|||
<head> |
|||
<meta charset="utf-8"> |
|||
<title>Vue.js tree-view demo</title> |
|||
<style> |
|||
body { |
|||
font-family: Menlo, Consolas, monospace; |
|||
color: #444; |
|||
} |
|||
.item { |
|||
cursor: pointer; |
|||
} |
|||
.bold { |
|||
font-weight: bold; |
|||
} |
|||
ul { |
|||
padding-left: 1em; |
|||
line-height: 1.5em; |
|||
list-style-type: dot; |
|||
} |
|||
</style> |
|||
<script src="../../dist/vue.js"></script> |
|||
</head> |
|||
<body> |
|||
|
|||
<!-- item template --> |
|||
<script type="text/x-template" id="item-template"> |
|||
<li> |
|||
<div |
|||
:class="{bold: isFolder}" |
|||
@click="toggle" |
|||
@dblclick="changeType"> |
|||
{{model.name}} |
|||
<span v-if="isFolder">[{{open ? '-' : '+'}}]</span> |
|||
</div> |
|||
<ul v-show="open" v-if="isFolder"> |
|||
<item |
|||
class="item" |
|||
v-for="model in model.children" |
|||
:model="model"> |
|||
</item> |
|||
<li @click="addChild">+</li> |
|||
</ul> |
|||
</li> |
|||
</script> |
|||
|
|||
<p>(You can double click on an item to turn it into a folder.)</p> |
|||
|
|||
<!-- the demo root element --> |
|||
<ul id="demo"> |
|||
<item |
|||
class="item" |
|||
:model="treeData"> |
|||
</item> |
|||
</ul> |
|||
|
|||
<!-- demo code --> |
|||
<script src="tree.js"></script> |
|||
</body> |
|||
</html> |
@ -0,0 +1,56 @@ |
|||
// demo data
|
|||
var data = { |
|||
name: 'My Tree', |
|||
children: [ |
|||
{ name: 'hello' } |
|||
] |
|||
} |
|||
|
|||
// define the item component
|
|||
Vue.component('item', { |
|||
template: '#item-template', |
|||
props: { |
|||
model: Object |
|||
}, |
|||
data: function () { |
|||
return { |
|||
open: false |
|||
} |
|||
}, |
|||
updated: function () { |
|||
console.log(this._vnode) |
|||
}, |
|||
computed: { |
|||
isFolder: function () { |
|||
return this.model.children && |
|||
this.model.children.length |
|||
} |
|||
}, |
|||
methods: { |
|||
toggle: function () { |
|||
if (this.isFolder) { |
|||
this.open = !this.open |
|||
} |
|||
}, |
|||
changeType: function () { |
|||
if (!this.isFolder) { |
|||
Vue.set(this.model, 'children', []) |
|||
this.addChild() |
|||
this.open = true |
|||
} |
|||
}, |
|||
addChild: function () { |
|||
this.model.children.push({ |
|||
name: 'new stuff' |
|||
}) |
|||
} |
|||
} |
|||
}) |
|||
|
|||
// boot up the demo
|
|||
var demo = new Vue({ |
|||
el: '#demo', |
|||
data: { |
|||
treeData: data |
|||
} |
|||
}) |
Loading…
Reference in new issue