GitHub 您所在的位置:网站首页 豆瓣movie GitHub

GitHub

#GitHub| 来源: 网络整理| 查看: 265

豆瓣电影单页应用(SPA)

展示地址:http://luckybai.top/project/movie/

[TOC]

练手AngularJS小项目

对路由加深下理解:

规则 对应 不同的视图

ng-view -- 占位符

当匹配上规则时,view放上去

此外,提醒一下:

因为常用命令中参数不容易记,可以将每个命令制作成snippet,这样tab出命令时候,自动补全所有的参数。

step-01 构建项目结构 克隆项目骨架

这个项目可以作为angular开发的一个骨架,不用自己手动配置;尤其以后项目中,一般都会有,直接clone后就有了环境。

$ git clone --depth=1 https://github.com/Micua/angular-boilerplate.git moviecat $ cd moviecat

看一下package.json和bower.json包,学习下它们的机构。

$ cnpm run start

安装项目依赖 $ bower install bootstrap --save .editorconfig -- 统一不同开发者的不同开发工具的不同开发配置 在Sublime中使用需要安装一个EditorConfig的插件 为NG做一个项目骨架的目的是为了快速开始一个新的项目 angular-seed

npm 在 package.json中的script节点中可以定义脚本任务,

初始化,当你不懂的时候,坑太多了。。。

终于跑通了...

几个坑

当拿到上面的框架时候,看一下框架

用人家的HTML+CSS,引包

ng-app ng-view

写主模块

'use strict'; // Declare app level module which depends on views, and components angular.module('movieCat', [ 'ngRoute', 'movieCat.in_theaters', 'movieCat.coming_soon', 'movieCat.top250' ]) .config(['$routeProvider', function($routeProvider) { $routeProvider.otherwise( { redirectTo: '/in_theaters' } ); }]);

写子模块

(function(angular) { 'use strict'; // 创建正在热映模块 var module = angular.module('movieCat.in_theaters', ['ngRoute']); // 配置模块的路由 module.config(['$routeProvider', function($routeProvider) { $routeProvider.when('/in_theaters', { templateUrl: 'in_theaters/view.html', controller: 'InTheatersController' }); }]); // 控制器 module.controller('InTheatersController', ['$scope', function($scope) { }]) })(angular); 写子模块控制器

控制器 分两步: 1.设计暴露数据; 2.设计暴露行为 去分析!!!!

就是列表,对吗: 图片,名称,导演,类型

数据从哪儿来?

API

Application Programxxx Interface

应用程序编程接口

有哪些常见的API

WebAPI 通过WEB方式提供结构叫做 WEBAPI

Math.random() -- api?

所有有输入有输出的事物都可以是API

都是函数

测试WebAPI的工具: POSTMAN

step-02 抽象数据成员,以假数据的方式设计控制器和视图 正在热映模块 -- 控制器暴露数据$scope

豆瓣API地址:https://developers.douban.com/wiki/?title=guide

https://developers.douban.com/wiki/?title=api_v2

在Chrome插件打开POSTMAN:

chrome://apps/

输入:

https://api.douban.com//v2/movie/in_theaters?count=3

获得假数据

var data = {获取的数据} module.controller('InTheatersController', ['$scope', function($scope) { // 控制器 分两步: 1.设计暴露数据; 2.设计暴露行为 去分析!!!! // 就是列表,对吗: 图片,名称,导演,类型 $scope.data = data.subjects; }]) view绑定数据 热映电影北京站 {{item.rating.average}} ... {{item.title}}

类型:{{item.genres.join('、')}} ;;;; 上映年份:{{item.year}}

导演: {{director.name}} 、

主演: {{cast.name}} 、

step-03 图片链接数据绑定BUG -- src vs ng-src

这里会报一个错:

其实就是在src加载图片时候,浏览器并不认识{{}}...

...

step-04 加入$http服务对象完成AJAX异步请求数据

关于异步请求,几种方式?

两种,angular告诉你了:

前者不支持跨域;后者支持。

传统方式

跨域方式 -- jsonp

那么,支持跨域请求的方式有?

为什么最终选择script

script

上面返回如下:`callback({id:1, name: '张三', age: 18})

然后,返回的数据 -- 函数,会调用本地函数:

function callback (data) { console.log(data); }

缺点:需要全局环境,如果有多个跨域请求如何解?

angular解决方案:只污染一个window.angular,所有的的回调函数挂靠其上。

定义一个全局对象:

window.angular = {};

全局对象定义一个对象:

angular.callbacks = {};

给上面这个对象进行挂靠:

angular.callbacks._0 = function() {...};

但是,有些网站不支持angular的玩儿法!!!!

比如豆瓣API:

这样是支持的:

注意:

为什么有个; -- 防止上面情况

但是,这样就不可以了:

同时可以看出,angular在用jsonp做跨域请求的时候,必须给地址加上一个?callback=JSON_CALLBACK

jQuery解决方案:既然污染到全局环境,那名字起得古怪一点

function jquery_jsonp123234234234 (arguments) {...};

img

这个之前用于 统计链接;

支持跨域,但是,将其作为图片**,无法实现获取服务器返回的数据**。

被淘汰

iframe -- 在页面内占个位

可以接收服务端数据,但是太复杂,淘汰

link

会在CSS处理阶段报错,无法使用,淘汰

a

但是,点击才会去请求,这个更不用考虑。

这里再给自己提个醒,一定要去看官方文档!!!!

同源异步请求

实现效果:

本地文件夹下面,建立存放postman获取到的数据文件:data.json文件

module.controller('InTheatersController', ['$scope', "$http", function($scope, $http) { // 控制器 分两步: 1.设计暴露数据; 2.设计暴露行为 去分析!!!! // 就是列表,对吗: 图片,名称,导演,类型 // 需要先暴露一下,因为$http异步请求,如果不先声明,view端可能不认识 $scope.data = []; $scope.errorMsg = ''; $http({ method: 'GET', // 写项目的绝对路径 url: '/moviecat/app/data.json' }).then(function successCallback(response) { // this callback will be called asynchronously // when the response is available console.log(response); if (response.status == 200) { $scope.data = response.data.subjects; } // else { // $scope.errorMsg = '发生错误' + response.status; // } }, function errorCallback(response) { // called asynchronously if an error occurs // or server returns response with an error status. console.log(response); $scope.errorMsg = '发生错误' + response.status; }); }])

写项目的绝对路径 url: '/moviecat/app/data.json'

跨域异步请求 -- 封装好的jsonp方式

angular中将所有的jsonp的callback都挂在angular.callbacks对象上

当然,如果这样的请求方式,肯定是不允许跨域请求的:

$http({ method: 'GET', url: 'https://api.douban.com//v2/movie/in_theaters?' }).then()

肯定,也知道了,这样也是不行的

$http({ method: 'JSONP', url: 'https://api.douban.com//v2/movie/in_theaters?callback=hello' }).then()

这样,还是不可以的

$http({ method: 'JSONP', url: 'https://api.douban.com//v2/movie/in_theaters?callback=JSON_CALLBACK' }).then

结论:上面的都不行。

angular和豆瓣妥协? -- 还是自己写吧

单纯先手写jsonp一遍,详细可以看这里:问题总结文件夹

http.js

// angular提供的异步请求对象不支持自定义回调函数名 // angular随机分配的回调函数名称不被豆瓣支持 // 自己写一个跨域请求的模块 (function(angular) { // 创建模块 var http = angular.module('movieCat.services.http', []); // 模块 设置服务 http.service("httpService", [ '$document', "$window", function ($document, $window) { // console.log($document); // console.log($window); this.jsonp = function(url, data, callback) { var jsonpCb = 'luckyJsonpCb' + Math.random().toString().replace('.', '').substr(1, 6); $window[jsonpCb] = callback; var search = url.indexOf('?') === -1 ? "?" : ''; for (var key in data) { search += key + '=' + data[key] + '&'; } search += 'callback=' + jsonpCb; var scriptEle = $document[0].createElement('script'); scriptEle.src = url + search; $document[0].body.appendChild(scriptEle); } } ]); })(angular);

控制器:

module.controller('InTheatersController', ['$scope', "httpService", function($scope, httpService) { // 控制器 分两步: 1.设计暴露数据; 2.设计暴露行为 去分析!!!! // 就是列表,对吗: 图片,名称,导演,类型 // 需要先暴露一下,因为$http异步请求,如果不先声明,view端可能不认识 $scope.subjects = []; $scope.errorMsg = ''; var url = 'https://api.douban.com//v2/movie/in_theaters'; var data = { // count: 3, // start: 0, }; httpService.jsonp(url, data, function(obj) { // console.log(data); $scope.data = obj.subjects; $scope.$apply(); }); }]);

注意需要apply:

可以了:

step-05 加载提示,Loading状态设计

http://tobiasahlin.com/spinkit/

直接把HTML、CSS拿过来了,其实知道如何设计了,自己用C3实现不难。

step-06 实现分页功能 豆瓣电影API支持

start=0&count=2

start=2&count=2

window.loacation.hash之后需要更改

为了让分页之后,打开一个链接显示不同的内容,需要一个页码page:

http://127.0.0.1:8020/moviecat/app/index.html#/in_theaters/1

综上,start = (page-1) * 2

如何实现

在路由设置里面加上分页参数

$routeProvider.when('/in_theaters/:page', {

在控制器中获取到分页参数

module.controller('InTheatersController', [ '$scope', '$routeParams' "httpService", function($scope, $routeParams, httpService) {

所有3个module都一样,只是将module和controller名字改一下,所有,之后就可以抽象。

(function(angular) { 'use strict'; // 假数据 // var data = {}; // 创建正在热映模块 var module = angular.module('movieCat.in_theaters', [ 'ngRoute', 'movieCat.services.http' ]); // 配置模块的路由 module.config(['$routeProvider', function($routeProvider) { $routeProvider.when('/in_theaters/:page', { // view templateUrl: 'in_theaters/view.html', // controller controller: 'InTheatersController' }); }]); // 控制器 module.controller('InTheatersController', [ '$scope', '$route', '$routeParams', "httpService", function($scope, $route, $routeParams, httpService) { // 控制器 分两步: 1.设计暴露数据; 2.设计暴露行为 去分析!!!! // 就是列表,对吗: 图片,名称,导演,类型 var count = 4; var page = parseInt($routeParams.page); var start = (page - 1) * count; // console.log(page) // 需要先暴露一下,因为$http异步请求,如果不先声明,view端可能不认识 $scope.loading = true; $scope.data = []; $scope.title = ''; $scope.total = 0; $scope.totalPages = 0; $scope.currentPage = page; var url = 'https://api.douban.com/v2/movie/in_theaters'; var data = { count: count, start: start }; httpService.jsonp(url, data, function(obj) { // console.log(obj); $scope.data = obj.subjects; $scope.title = obj.title; $scope.total = obj.total; $scope.loading = false; $scope.totalPages = Math.ceil($scope.total / count); // 重新apply一下数据 $scope.$apply(); }); // 暴露行为 $scope.goPage = function(page) { if (page < 1 || page > $scope.totalPages) { return ; } $route.updateParams({page: page}); } }]); })(angular); /* $http({ method: 'GET', url: '/moviecat/app/data.json' }).then(function successCallback(response) { // this callback will be called asynchronously // when the response is available console.log(response); if (response.status == 200) { $scope.subjects = response.data.subjects; } // else { // $scope.errorMsg = '发生错误' + response.status; // } }, function errorCallback(response) { // called asynchronously if an error occurs // or server returns response with an error status. console.log(response); $scope.errorMsg = '发生错误' + response.status; }); */ step-07 抽象公共的列表页

提取公共列表,单独建一个文件夹,存放controller.js和view.html;

修改controller.js中的模块名字+controller名字+URL地址为动态(路由:category)+index.html引入js文件+主app.js引入模块

step-08 左侧class="active"单击自动焦点问题

解决方案1 -- 使用公共controller暴露给view数据type

因为index.html中并没有使用controller,可以为公共区域(除显示电影部分)设置一个公共controller

在公共controller里面给view暴露一个type(显示当前location里面是哪个模块)$scope.type = 'in_theaters';

由于location随单击变化,所以要时刻watch$scope.type 值得变化: $scope.$watch('$location.$$path', function(now) {

以下:

.controller('NavController', [ '$scope', '$location', function($scope, $location) { console.log($location.$$path) // console.log($location.path()) // /top250/1 $scope.$location = $location; // 这里watch的是scope下面的对象$location.$$path -- $scope.$location.$$path $scope.$watch('$location.$$path', function(now) { if (now.startsWith('/in_theaters')) { $scope.type = 'in_theaters'; } else if (now.startsWith('/coming_soon')) { $scope.type = 'coming_soon'; } else { $scope.type = 'top250'; } console.log(now); }) } ])

之后在view端设置controller区域和ng-class;

正在热映 即将上映 TOP250 解决方案2 -- 使用指令方式实现 auto-focus

小模块中指令directive名字驼峰命名,但是在view端使用时候需要用-连接。

另外,所有的DOM操作,都放在指令的link中

创建一个文件:auto_focus.js

(function(angular) { angular.module('movieCat.directives.auto_focus', []) .directive('autoFocus', [ '$location', function($location) { var path = $location.path(); return { restrict : 'A', // 与添加auto-focus的DOM对象建立link的时候 link : function($scope, element, attrs) { var currentAHref = element.children().attr('href') console.log(currentAHref) // #/in_theaters/1 var type = currentAHref.replace(/#(.+?)\/\d+/, '$1') console.log(type) // 根据当前URL设置class if (path.startsWith(type)) { element.addClass('active') } element.on('click', function() { // console.log(this) angular.element(this).parent().children().removeClass('active'); angular.element(this).addClass('active'); }) } } } ] ) })(angular);

在app.js主模块中引用movieCat.directives.auto_focus这个模块即可;

angular.module('movieCat', [ 'ngRoute', 'movieCat.movie_list', 'movieCat.directives.auto_focus' ])

在index.html中设置auto-focusautoFocus这个属性即可:

正在热映 指令解决方式有个初始化问题

当输入http://127.0.0.1:8020/moviecat/app/index.html时,

自动跳转至http://127.0.0.1:8020/moviecat/app/index.html#/in_theaters/1

但是刚开始的时候,没有点击任何左侧的选项或者URL只是index.html时,所以并不能给正在热映加上class='active'

因为:路由跳转是在公共模块之后,所以刚开始,输入index.html,URL并没有路由到index.html#/in_theaters/1

解决就是见识location.hash -- $location.path() -- $location.$$path:

(function(angular) { angular.module('movieCat.directives.auto_focus', []) .directive('autoFocus', [ '$location', function($location) { // var path = $location.path(); return { restrict : 'A', // 与添加auto-focus的DOM对象建立link的时候 link : function($scope, element, attrs) { // $watch只能watch$scope的元素 $scope.$location = $location; // 实时监测url的path -- (location.hash) $scope.$watch('$location.path()', function(now) { var currentAHref = element.children().attr('href') // console.log(currentAHref) // #/in_theaters/1 var type = currentAHref.replace(/#(.+?)\/\d+/, '$1') // console.log(type) if (now.startsWith(type)) { element.parent().children().removeClass('active'); element.addClass('active'); } }) // 下面就不需要了,因为单击时候会出发URL改变 // element.on('click', function() { //// console.log(this) // angular.element(this).parent().children().removeClass('active'); // angular.element(this).addClass('active'); // // }) // } } } } ] ) })(angular); 继续解决 -- 下面加了搜索以后

当进行电影搜索的时候,左侧背景色应该是没有的,比较简单:

$scope.$location = $location;已经写死,

当$watch auto-focus的li时,用之前$location.path()和当前now值比较即可,相同就加active;不同,删除active

if (now.startsWith(type)) { //element.parent().children().removeClass('active'); element.addClass('active'); } else { element.removeClass('active'); } step-09 load.js/sea.js等等异步加载js库和jsonp手写问题 load.js异步加载 functino $script (loads, callback) { var scriptEle = document.createElement('script'); scriptEle.src = loads; document.head.appendChild[scriptEle]; // 脚本加载完成之后才能执行callback scriptElement.addEventListener('load', callback); } jsonp手写问题

每次下一页,都会加载一个script,会产生很多很多

所以,需要remove操作 -- 什么时候?

只要callback执行完毕之后,也就是取回数据并执行完成后callback(data),就可以删除这个script了 -- 动态效果很棒

this.jsonp = function(url, data, callback) { var jsonpCb = 'luckyJsonpCb' + Math.random().toString().replace('.', '').substr(1, 6); // $window[jsonpCb] = callback; var search = url.indexOf('?') === -1 ? "?" : ''; for (var key in data) { search += key + '=' + data[key] + '&'; } search += 'callback=' + jsonpCb; var scriptEle = $document[0].createElement('script'); scriptEle.src = url + search; // 回调函数必须在append之前 $window[jsonpCb] = function(data) { callback(data); $document[0].body.removeChild(scriptEle); }; $document[0].body.appendChild(scriptEle); }; step-10 搜索功能模块

搜索模块

$routeParams数据来源:

路由匹配 :category -- $routeParams.category ?参数 -- #/***/1?q='周润发' -- $routeParams.q 可以单独设置一个controller,然后绑定数据、暴露行为;

因为是在公共区域,所以直接在app.js模块加上controller即可

.controller('SearchController', [ '$scope', '$route', // 更新URL参数 function($scope, $route) { $scope.search_name = ''; // 取文本框中数据 $scope.search = function() { console.log($scope.search_name) $route.updateParams({category: 'search', page: 1, q: $scope.search_name}) } } ])


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有