AngularJS实例GutHub是一款简单管理应用,主要的特性如下:
1.两列布局
2.在左侧有一个导航条
3.允许你创建新菜谱
4.允许你浏览现有的菜谱列表
主视图位于右侧,主视图将会根据具体的URL刷新,可以显示菜谱列表、菜谱项目详情,以及用来添加或编辑菜谱的表单。
本文源码下载地址:https://github.com/johnnymitrevski/gutHub
工程结构图:
controllers.js
//Setup the GutHub angular module var app = angular.module('guthub', ['guthub.directives', 'guthub.services']); //Update the baseUrl to match the directory the app is deployed on the web server. Leave blank if '/'. var baseUrl = ""; /** * Specify the various routes we have in our application. For each route we specify: * 1) The controller that backs it up. * 2) The template to load. * 3) The resolve object (Optional). This tells angular JS that each of the resolve keys need to be meet * before the route can be displayed to the user. For us, we want to load all the recipe data before we * display the page. If the resolve function returns a $q (promise) then AngularJs is smart enough to wait * for the promise to get resolved before it proceeds. That means wait till the server responds. */ app.config(['$routeProvider', function($routeProvider) { $routeProvider.when('/', { controller: 'ListCtrl', resolve: { recipes: function(MultiRecipeLoader) { return MultiRecipeLoader(); } }, templateUrl: baseUrl + '/GutHub/views/list.html' }).when('/edit/:recipeId', { controller: 'EditCtrl', resolve: { recipe: function(RecipeLoader) { return RecipeLoader(); } }, templateUrl: baseUrl + '/GutHub/views/recipeForm.html' }).when('/view/:recipeId', { controller: 'ViewCtrl', resolve: { recipe: function(RecipeLoader) { return RecipeLoader(); } }, templateUrl: baseUrl + '/GutHub/views/viewRecipe.html' }).when('/new', { controller: 'NewCtrl', templateUrl:baseUrl + '/GutHub/views/recipeForm.html' }).otherwise({redirectTo: '/'}); }]); /** * List the recipes */ app.controller('ListCtrl',['$scope','recipes',function($scope, recipes) { //$scope.recipes = recipes; $scope.recipes = [ { id:1, title:"cookies", description: "Delicious, crisp on the outside, chewy" + " on the outside, oozing with chocolatey goodness " + "cookies. The best kind", ingredients: [ { amount: "1", amountUnits: "packet", ingredientName: "Chips Ahoy" } ], instructions: "1. Go buy a packet of Chips Ahoy\ n" + "2. Heat it up in an oven\ n" + "3. Enjoy warm cookies\ n" + "4. Learn how to bake cookies from somewhere else" } ]; }]); /** * View a recipe */ app.controller('ViewCtrl', ['$scope','$location','recipe', function($scope, $location, recipe) { $scope.recipe = recipe; $scope.edit = function() { $location.path('/edit/' + recipe.id); }; }]); /** * Exposes save() and remove() functions to scope. * * save() will save the current recipe and then redirect to the view screen * of the same recipe. * * remove() removes the recipe from the scope and redirects the uses to the * main landing page. */ app.controller('EditCtrl', ['$scope','$location','recipe', function($scope, $location, recipe) { $scope.recipe = recipe; $scope.save = function() { $scope.recipe.$save(function(recipe){ $location.path(baseUrl + '/GutHub/view/' + recipe.id); }); }; $scope.remove = function() { delete $scope.recipe; //Doesn't remove it from the server. Only from $scope. $location.path(baseUrl + '/'); }; }]); /** * Create a new recipe. */ app.controller('NewCtrl',['$scope', '$location', 'Recipe', function($scope, $location, Recipe){ $scope.recipe = new Recipe({ ingredients: [{}] }); $scope.save = function() { $scope.recipe.$save(function(recipe) { $location.path(baseUrl + '/view/' + recipe.id); }); }; }]); /** * Edit the ingredients. It inherits $scope from the parent controller. */ app.controller('IngredientsCtrl', ['$scope', function($scope) { $scope.addIngredient = function() { var ingredients = $scope.recipe.ingredients; ingredients[ingredients.length] = {}; }; $scope.removeIngredient = function(index) { $scope.recipe.ingredients.splice(index, 1); }; }]);
directives.js
var directives = angular.module('guthub.directives',[]); directives.directive('butterbar',['$rootScope', function($rootScope){ return { link: function(scope, element, attrs) { element.addClass('hide'); //Hide the element upfront //Watch the $routeChangeStart and remove the hide css, therefore show $rootScope.$on('$routeChangeStart', function() { element.removeClass('hide'); }); //Watch the $routeChangeSuccess and remove add the hide css, therefore hide $rootScope.$on('$routeChangeSuccess', function() { element.addClass('hide'); }); } }; }]); /** * Calls the focus() method on the current element. * * Call it by adding the focus attribute on any input element. * i.e. <input type="text" focus></input> * Therefore when the page loads, that element immediately gets the focus. */ directives.directive('focus', function() { return { link: function(scope, element, attrs) { element[0].focus(); } }; });
services.js
var services = angular.module('guthub.services',['ngResource']); /** * Recipe service. * * Uses $resource, which is is a RESTful handle that encapsulates lower level $http service. * * Recipe can now be used an argument injected into our controllers. The following methods are built in: * Recipe.get(); * Recipe.save(); * Recipe.delete(); * Recipe.remove(); * Recipe.delete(); * * Useage: if we pass in an object with an id field, then the value of that field will be * added to the end of the URL. * Calling Recipe.get({id:15}) will make a call to /recipes/15 * * For example: * var recipe = new Recipe(existingRecipeObj); * recipe.$save(); - this calls a POST request */ services.factory('Recipe',['$resource', function($resource){ return $resource('/GutHub/recipes/:id',{id: '@id'}); }]); /** * Load all recipes. */ services.factory('MultiRecipeLoader', ['Recipe', '$q', function(Recipe,$q){ return function() { var delay = $q.defer(); Recipe.query(function(recipesData) { delay.resolve(recipesData); }, function() { //delay.reject('Unable to fetch recipes'); delay.resolve([{id:1, title:"cookies"}, {id:2, title:"farts"}]); }); return delay.promise; }; }]); /** * Load a single recipe. */ services.factory('RecipeLoader', ['Recipe','$route', '$q', function(Recipe, $route, $q){ return function() { var delay = $q.defer(); Recipe.get({id: $route.current.params.recipeId}, function(recipeData) { delay.resolve(recipeData); }, function() { //delay.reject('Unable to fetch recipe ' + $route.current.params.recipeId); delay.resolve({ id:1, title:"cookies", description: "Delicious, crisp on the outside, chewy" + " on the outside, oozing with chocolatey goodness " + "cookies. The best kind", ingredients: [ { amount: "1", amountUnits: "packet", ingredientName: "Chips Ahoy" } ], instructions: "1. Go buy a packet of Chips Ahoy\ n" + "2. Heat it up in an oven\ n" + "3. Enjoy warm cookies\ n" + "4. Learn how to bake cookies from somewhere else" }); }); return delay.promise; } }]);
index.html
<!DOCTYPE html>
<html lang="en" ng-app="guthub">
<head>
<title>GutHub - Create and Share</title>
<script src="scripts/vendor/angular.js"></script>
<script src="scripts/vendor/angular-resource.js"></script>
<script src="scripts/directives/directives.js"> </script>
<script src="scripts/services/services.js"> </script>
<script src="scripts/controllers/controllers.js"></script>
<link href="scripts/vendor/bootstrap/css/bootstrap.css" rel="stylesheet">
</head>
<body>
<header>
<h1>GutHub</h1>
</header>
<div butterbar>Loading...</div>
<div class="container-fluid">
<div class="row-fluid">
<div class="span2">
<!--Sidebar-->
<div id="focus">
<a href="#/new">New Recipe</a>
</div>
<div>
<a href="#/">Recipe List</a>
</div>
</div>
<div class="span10">
<div ng-view></div>
</div>
</div>
</div>
</body>
</html>
list.html
<h3>Recipe List</h3>
<ul class="recipes">
<li ng-repeat="recipe in recipes">
<div>
<a href="#/view/{{ recipe.id }}">{{ recipe.title }}</a>
</div>
</li>
</ul>
recipeForm.html
<h2>Edit Recipe</h2> <form name="recipeForm" ng-submit="save()" class="form-horizontal"> <div class="control-group"> <label class="control-label" for="title">Title:</label> <div class="controls"> <input ng-model="recipe.title" class="input-xlarge" id="title" focus> </div> </div> <div class="control-group"> <label class="control-label" for="description">Description:</label> <div class="controls"> <textarea ng-model="recipe.description" class="input-xlarge" id="description"></textarea> </div> </div> <div class="control-group"> <label class="control-label" for="ingredients">Ingredients:</label> <div class="controls"> <ul id="ingredients" class="unstyled" ng-controller="IngredientsCtrl"> <li ng-repeat="ingredient in recipe.ingredients"> <input ng-model="ingredient.amount" class="input-mini"> <input ng-model="ingredient.amountUnits" class="input-small"> <input ng-model="ingredient.ingredientName"> <button type="button" class="btn btn-mini" ng-click="removeIngredient($index)"> <i class="icon-minus-sign"></i> Delete </button> </li> <button type="button" class="btn btn-mini" ng-click="addIngredient()"> <i class="icon-plus-sign"></i> Add </button> </ul> </div> </div> <div class="control-group"> <label class="control-label" for="instructions">Instructions:</label> <div class="controls"> <textarea ng-model="recipe.instructions" class="input-xxxlarge" id="instructions"></textarea> </div> </div> <div class="form-actions"> <button class="btn btn-primary">Save</button> <button type="button" ng-click="remove()" ng-show="!recipe.id" class="btn"> Delete </button> </div> </form>
viewRecipe.html
<h2>{{ recipe.title }}</h2>
<div>{{ recipe.description }}</div>
<h3>Ingredients</h3>
<ul class="unstyled">
<li ng-repeat="ingredient in recipe.ingredients">
<span>{{ ingredient.amount }}</span>
<span>{{ ingredient.amountUnits }}</span>
<span>{{ ingredient.ingredientName }}</span>
</li>
</ul>
<h3>Instructions</h3>
<div>{{ recipe.instructions }}</div>
<form ng-submit="edit()" class="form-horizontal">
<div class="form-actions">
<button class="btn btn-primary">Edit</button>
</div>
</form>
运行效果(由于没有编写后台,不少地方会报错甚至不能成功运行):
附:GitHub上下载的工程源代码和用SeaJS组织的工程源代码