使用 Ajax
之前看到的例子都是整页地提交,如果提交失败就重新刷新页面,显示错误信息;如果提交正确,就返回完整的新页面或者跳转到其他页面。重新刷新页面和返回新页面意味着整个页面的内容都要重新加载,但事实上其中大部分 HTML 是相同的。这种传统方法浪费了很多带宽,加载得更慢,用户体验不好。另外一个问题是,刷新的页面里应该包含之前未提交成功的数据,不应该让用户重复输入,这也需要大量的逻辑来保证。
大型网站的首页内容都很丰富,比如豆瓣的匿名(未登录状态)首页,除了登录表单,还有顶部导航栏、热点内容和各产品线的推荐内容等。登录时,可不可以在不刷新页面的前提下,只向服务器发送和取回登录必要的数据呢?
Ajax 是 Asynchronous JavaScript and XML 的简称,通过 Ajax 向服务器发送 HTTP 请求,接收服务器返回的 JSON 数据,然后使用 JavaScript 修改网页的局部来实现更新和提交。现在绝大多数 Web 应用都在使用 Ajax。
jQuery 是一个高效、精简并且功能丰富的 JavaScript 工具库,它提供的 API 易于使用且兼容全部主流浏览器,如果不是专业的前端工程师,使用它也可以很方便地操作文档对象、选择文档对象模型(DOM)元素、处理事件、创建动画效果、开发 Ajax 程序等。
首先下载 jQuery:
> wget https://code.jquery.com/jquery-2.2.4.min.js-O static/javascripts/jquery.min.js
jQuery 2.x 与其 1.x 的 API 一样,但是前者只支持 IE 9 及以上的浏览器版本。
登录页面(signin.html)如下:
<!DOCTYPE html> <html> <head> <title>Signin</title> <script src="{{url_for('static', filename='javascripts/jquery.min.js')}}"></ script> <style> #result{ margin-top:20px; color:red; } </style> </head> <body> <div class="container"> <form action="/signin"method="post"role="form"> <h2>Please Sign In</h2> <input type="name"name="username"placeholder="Username"required autofocus> <input type="password"name="password"placeholder="Password"required> <button class="btn"type="button">SignIn</button> </form> <div id="result"></div> </div> <script type='text/javascript'> $(function(){ $('.btn').click(function(){ var$result=$('#result'); var$username=$('input[name="username"]').val(); var$password=$('input[name="password"]').val(); $.ajax({ url:'/signin', data:$('form').serialize(), type:'POST', dataType:'json' }).done(function(data){ if (!data.r){ $result.html(data.rs); }else{ $result.html(data.error); } }); }); }); </script> </body> </html>
这个页面主要包含如下部分。
1.head:head 标签中可以引用 JavaScript 脚本和样式表(CSS)、提供元信息等。jquery.min.js 这种全局的工具库应该放在最上面,在渲染页面时 jQuery 可以更早加载。除此之外,style 标签还给 id 为 result 的 div 元素添加了样式。
2.form:就是登录表单,其中 action 是提交时请求的地址,method 表示提交方法。表单中有两个 input 标签,分别为用户名和密码,还有一个用来提交的按钮。
3.script:最下面的 script 标签中使用 jQuery 监控类名为 btn 的元素的点击事件。单击“提交”按钮则获取表单中的用户名和密码,通过$.ajax 函数发送 POST 请求。其中 done 是执行成功的回调函数,返回的数据叫作 data,如果 data.r 为 0,表示登录成功,给 id 为 result 的 div 添加 data.rs 中的内容,否则添加 data.error 中的内容。由于 script 会获取页面元素,应该在页面元素的之后出现,所以一般都在 body 的最下面。
上述代码中有一些使用 jQuery 的细节。
- “$(function(){...})”将会在浏览器加载完页面的基础内容之后立即执行。
- jQuery 可以使用标签和 CSS 选择器来选取 HTML 元素。举几个例子来看一看:
$("p"):选取<p>元素。 $("p.intro"):选取所有 class="intro"的<p>元素。 $("p#demo"):选取所有 id="demo"的<p>元素。 $('input[name="password"]'):选取所有 name="password"的<input>y 元素。
- 上例使用了“$result.html”设置内容,html() 可以设置或返回所选元素的内容(包括 HTML 标记)。除此之外,还有 text()(设置或返回所选元素的文本内容)和 val()(设置或返回表单字段的值)。使用 html() 更灵活,因为可以在返回的结果中添加标签和样式。
看一看页面视图:
@app.route('/') def index(): return render_template('chapter5/section3/signin.html') @app.route('/signin', methods=['POST']) def signin(): username=request.form['username'] password=request.form['password'] error=None if len(username)<5: error='Password must be at least 5 characters' if len(password)<6: error='Password must be at least 8 characters' elif not any(c.isupper() for c in password): error='Your password needs at least 1 capital' if error is not None: return jsonify({'r':1, 'error':error}) return jsonify({'r':0, 'rs':'Ok'})
视图分为两个:
- 首页接受 GET 请求,渲染 signin.html。
- /signin 页面即上面 Ajax 设置的请求地址,接收用户名和密码,处理之后用 JSON 格式返回结果。需要强调的是,不仅后端需要验证用户名和密码,前端也应该通过 JavaScript 验证用户名和密码的格式是否符合,如果不符合,则直接提示错误而不用产生一次请求,减少后端压力。
使用$.ajax 的时候,提交的数据使用了“$('form').serialize()”,它通过序列化表单值,创建 URL 编码的文本字符串。序列化的值可在生成 Ajax 请求时用在 URL 查询字符串中;否则需要挨个字段拼出来:
data:{'username':$username, 'password':$password}
使用 fetch 实现 Ajax
传统 Ajax 指的是 XMLHttpRequest(XHR),JavaScript 通过它来执行异步请求,jQuery 帮我们把 XMLHttpRequest 封装起来,成为$.ajax、$.get 和$.post 等函数。XHR 在设计上不符合职责分离原则,输入、输出以及状态都放在同一对象中,而且受 XML 影响,其命名也很复杂。
在 ECMAScript 2015(ES6)规范中有一个新的特性:Promise。Promise 对象用于延迟(de-ferred)计算和异步(asynchronous)计算,一个 Promise 对象代表着一个还未完成,但预期将来会完成的操作。Fetch(https://github.com/github/fetch )API 就是基于 Promise 设计的。举个 Ajax 返回 JSON 数据的例子,如果是用原生 XHR 写,会是这样:
var data=new FormData(); data.append('username',$username); data.append('password',$password); var xhr=new XMLHttpRequest(); xhr.open('POST', '/signin'); xhr.onreadystatechange=function(){ if (xhr.readyState==4&&xhr.status==200){ var data=JSON.parse(xhr.responseText); if (!data.r){ $result.html(data.rs); }else{ $result.html(data.error); } }; }; xhr.send(data);
如果使用 fetch 则可以写成这样:
fetch('/signin',{ method:'POST', body:data }).then(function(response){ return response.json(); }).then(function(data){ if (!data.r){ $result.html(data.rs); }else{ $result.html(data.error); } });
从 Firefox 39 以及 Chrome 42 开始,它们已经支持了 Fetch API。为了保证其他浏览器或者低版本的 Firefox 以及 Chrome 也能使用,需要使用 GitHub 开源的 polyfill 脚本:
> wget https://github.com/github/fetch/raw/master/fetch.js-O static/javascripts/fetch.js
在模板中加载这个脚本:
<script src="{{url_for('static', filename='javascripts/fetch.js')}}"></script>
如果再使用 ES 7 提供的 async/awaitAPI,就可以像写同步代码一样写异步代码了:
try{ let response=await fetch('/signin',{ method:'POST', body:data }) let data=await response.json(); if (!data.r){ $result.html(data.rs); }else{ $result.html(data.error); } }catch(e){ console.log("Oops, error", e); }
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论