vueJS

Initializing

new Vue({
    el: '#app',
    data: {
        foods: [],
        food_name: ''
    },
    ready: function() {

    },
    methods: {
        addFood: function() {
            var food_name = this.food_name;

            this.foods.push({
                name: food_name
            });
        }
    }
})

The "View"

<div id="app">
    <ul>
        <li v-for="food in foods">
            {{ food.name }}
        </li>
    </ul>

    <form @submit.prevent="addFood()">
        <input type="text" v-model="food_name">

        <button class="button" type="submit" @click.prevent="addFood()">
    </form>
</div>

Methods

new Vue({
    el: '#app',
    data: {
        foods: [],
        food_name: ''
    },
    methods: {
        addFood: function() {
            var food_name = this.food_name;

            this.foods.push({
                name: food_name
            });
        },
        deleteFood: function(index) {
            this.foods = _.without(this.foods, this.foods[index]);
        }
    }
})

The "View"

<div id="app">
    <ul>
        <li v-for="food in foods">
            {{ food.name }} <a href="" @click.prevent="deleteFood($index)">X</a>
        </li>
    </ul>

    <form @submit.prevent="addFood()">
        <input type="text" v-model="food_name">

        <button class="button" type="submit" @click.prevent="addFood()">
    </form>
</div>

Callbacks

new Vue({
    el: '#app',
    data: {
        foods: [],
        food_name: ''
    },
    ready: function() {
        var _this = this;

        $.get('/foods')
            .success(function(results) {
                _this.foods = results;
            }, 'json');
    },
    methods: {
        addFood: function() {
            var food_name = this.food_name;

            this.foods.push({
                name: food_name
            });
        }
    }
})

Custom Filters

// Price filter
Vue.filter('price', {
	read: function(val) {
		return typeof val != 'undefined' ? '$' + parseFloat(val).toFixed(2) : '';
	},
	write: function(val, oldVal) {
		var number = + val.replace(/[^\d.]/g, '')

		return isNaN(number) ? 0 : parseFloat(number.toFixed(2));
	}
});

new Vue({
    el: '#app',
    data: {
        foods: [],
        food_name: '',
        food_price: 0
    },
    ready: function() {
        var _this = this;

        $.get('/foods')
            .success(function(results) {
                _this.foods = results;
            }, 'json');
    },
    methods: {
        addFood: function() {
            var food_name = this.food_name
                food_price = this.food_price;

            this.foods.push({
                name: food_name,
                price: food_price
            });
        }
    }
})

The "View"

<div id="app">
    <ul>
        <li v-for="food in foods">
            {{ food.name }} - {{ food.price | price }} <a href="" @click.prevent="deleteFood($index)">X</a>
        </li>
    </ul>

    <form @submit.prevent="addFood()">
        <input type="text" v-model="food_name"><br />
        <input type="text" v-model="food_price" price><br />

        <button class="button" type="submit" @click.prevent="addFood()">
    </form>
</div>

Components / Routing

var FormRepeater = Vue.extend({
	template: '#form-repeater',
	data: function() {
		return {
			children: [],
			child: {
				name: '',
				dominant_hand: ''
			}
		}
	},
	ready: function() {
		if ($('#children').length) {
			this.$set('children', JSON.parse($('#children').html()));
		}

		if (!this.children.length) {
			var child = jQuery.extend({}, this.child);

			this.children.push(child);
		}
	},
	methods: {
		addChild: function() {
			var child = jQuery.extend({}, this.child);

			this.children.push(child);
		},
		removeChild: function(index) {
			var children = _.without(this.children, this.children[index]);

			this.$set('children', children);
		},
		submitChildren: function() {
			console.log(this.children);
		}
	}
});

var ShoppingCart = Vue.extend({
	template: '#shopping-cart',
	data: function() {
		return {
			products: [],
			cart_items: [],
			loading: false
		}
	},
	ready: function() {
		var _this = this;

		$.get('http://vue_api.local/order', function(order) {
			_this.products = order.products;
			_this.cart_items = order.cart_items;
		}, 'json');
	},
	methods: {
		addProduct: function(index) {
			var product = $.extend({}, this.products[index]);

			this.loading = true;

			var quantity = parseInt(product.quantity);
			if (quantity >= 1) {
				var find = _.findIndex(this.cart_items, { id: product.id });

				if (find === -1) {
					product.quantity = quantity;

					this.cart_items.push(product);
				} else {
					product.quantity = quantity + this.cart_items[index].quantity;

					this.cart_items.$set(index, product);
				}

				this.products[index].quantity = '';

				this.saveProgress('Product has been added.');
			}
		},
		removeProduct: function(index) {
			this.cart_items = _.without(this.cart_items, this.cart_items[index]);

			this.saveProgress('Product has been removed.');
		},
		saveProgress: function(text) {
			var cart_items = JSON.stringify(this.cart_items),
				_this = this;

			$.post('http://vue_api.local/saveOrder', { cart: cart_items }, function(response) {
				if (response.status) {
					$.growl.notice({ title: '', message: text });
				}

				_this.loading = false;
			}, 'json');
		},
		getTotal: function() {
			return 0;
		}
	}
});

// Price filter
Vue.filter('price', {
	read: function(val) {
		return typeof val != 'undefined' ? '$' + parseFloat(val).toFixed(2) : '';
	},
	write: function(val, oldVal) {
		var number = + val.replace(/[^\d.]/g, '')

		return isNaN(number) ? 0 : parseFloat(number.toFixed(2));
	}
});

var app = Vue.extend({});
var router = new VueRouter();

router.map({
	'/form-repeater': {
		component: FormRepeater
	},
	'/shopping-cart': {
		component: ShoppingCart
	}
});

router.start(app, '#app');

The "View"

<html>
	<head>
		<!-- Google Fonts -->
		<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,300italic,700,700italic">

		<!-- CSS Reset -->
		<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/3.0.3/normalize.css">

		<!-- Milligram CSS minified -->
		<link rel="stylesheet" href="/sandbox/vue/bower_components/milligram/dist/milligram.min.css">

		<link rel="stylesheet" href="app.css">
		<link rel="stylesheet" href="jquery.growl.css">

		<!-- You should properly set the path from the main file. -->

	</head>
	<body class="container">
		<div id="app" class="row">
			<div class="column">
				<h1>VueJS Examples</h1>

				<ul>
					<li>
						<a v-link="{ path: '/form-repeater' }">
							repeater items with form
						</a>
					</li>
					<li>
						<a v-link="{ path: '/shopping-cart' }">
							shopping cart
						</a>
					</li>
					<li>
						<a v-link="{ path: '' }">

						</a>
					</li>
				</ul>

				<router-view></router-view>

				<script type="x/template" id="form-repeater">
					<div class="row">
						<div class="column-10">
							<a href="" @click.prevent="addChild()" class="button">Add Child</a>
						</div>
					</div>

					<form method="post" @submit.prevent="submitChildren()">
						<div class="row">
							<div class="column-50" v-for="child in children">
								<h2>Child #{{ $index }}</h2>

								<label>Name</label>

								<input type="text" name="data[{{ $index }}][name]" v-model="children[$index].name"><br/>

								<label>Dominant Hand</label>

								<label>Left Hand</label>
								<input type="radio" name="data[{{ $index }}][dominant_hand]" v-model="children[$index].dominant_hand" value="Left Hand"><br/>

								<label>Right Hand</label>
								<input type="radio" name="data[{{ $index }}][dominant_hand]" v-model="children[$index].dominant_hand" value="Right Hand"><br/>

								<a href="" @click.prevent="removeChild($index)" v-if="$index != 0">Delete</a>
							</div>
						</div>
						<div class="row">
							<button type="submit" class="button">Submit</button>
						</div>
					</form>
				</script>

				<script type="x/template" id="shopping-cart">
					<h1>Products</h1>

					<h4>Cart</h4>

					<table>
						<thead>
							<th>Name</th>
							<th>Price</th>
							<th>Quantity</th>
							<th></th>
						</thead>
						<tbody>
							<tr v-for="product in products">
								<td>{{ product.name }}</td>
								<td>{{ product.price | price }}</td>
								<td>
									<div class="column column-20">
										<input type="number" name="products[{{ $index }}][quantity]" v-model="products[$index].quantity" price>
									</div>
								</td>
								<td>
									<a class="button" @click.prevent="addProduct($index)">+</a>
								</td>
							</tr>
						</tbody>
					</table>

					<h4>Total: {{ getTotal() | price }}</h4>
				</script>
			</div>
		</div>
	</body>

	<script type="text/javascript" src="vue.min.js"></script>
	<script type="text/javascript" src="//code.jquery.com/jquery-2.2.0.min.js"></script>
	<script type="text/javascript" src="//cdn.jsdelivr.net/vue.router/0.7.10/vue-router.min.js"></script>
	<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
	<script type="text/javascript" src="jquery.growl.js"></script>
</html>

Pros / Cons

Pros

  • Easier to get into vs Angular 1
  • Better LTS support vs Angular 1 (it appears)
  • Super easy-to-use syntax, the layout of methods, data, filters, etc. just makes sense.
  • Data binding is a lot faster/better than angular 1.

Cons

  • Less overall support/users than Angular 1, and likely, 2.
  • Not as mature as Angular.
  • Doesn't come with things such as http.

Resources

vueJS

By Charlie Page

vueJS

  • 731