入门:《JavaScript DOM编程艺术》第二版
进阶:《JavaScript高级程序设计》第二版、《JavaScript编程精粹》
《JavaScript权威指南》
Node.js是CommonJS的一个部分实现
本书代码下载:
http://github.com/tdd/pragmatic-javascript
https://github.com/tdd/pragmatic-javascript/archive/master.zip
介绍的框架包括:Prototype、jQuery、MooTools、YUI、Dojo、ExtJS
dom/delegation.html
<ul id="items"> <!-- We will insert togglers in each LI using JS --> <li><div><p>Data 1</p><p>Data 2</p></div></li> <li><div><p>Data 1</p><p>Data 2</p></div></li> <li><div><p>Data 1</p><p>Data 2</p></div></li> <!-- Potentially lots more elements here… --> </ul>
dom/delegation.js(Prototype)
document.observe('dom:loaded', function() { $('items').observe('click', function(e) { var trigger = e.findElement('a.toggler'); if (!trigger) return; e.stop(); var content = trigger.up('p').next('div'); if (!content) return; content.toggle(); trigger.update(content.visible() ? 'Close' : 'Open'); trigger.blur(); }); $('items').select('li').each(function(item) { item.insert({ top: '<p><a class="toggler" href="#">Open</a></p>' }); item.down('div').hide(); }); });
var handle = windows.setTimeout(callback, intervalInMs) window.clearTimeout(hanle);
参考https://github.com/madrobby/emile 50行小程序实现精确定时器
(function(emile, container){ var parseEl = document.createElement('div'), props = ('backgroundColor borderBottomColor borderBottomWidth borderLeftColor borderLeftWidth '+ 'borderRightColor borderRightWidth borderSpacing borderTopColor borderTopWidth bottom color fontSize '+ 'fontWeight height left letterSpacing lineHeight marginBottom marginLeft marginRight marginTop maxHeight '+ 'maxWidth minHeight minWidth opacity outlineColor outlineOffset outlineWidth paddingBottom paddingLeft '+ 'paddingRight paddingTop right textIndent top width wordSpacing zIndex').split(' '); function interpolate(source,target,pos){ return (source+(target-source)*pos).toFixed(3); } function s(str, p, c){ return str.substr(p,c||1); } function color(source,target,pos){ var i = 2, j, c, tmp, v = [], r = []; while(j=3,c=arguments[i-1],i--) if(s(c,0)=='r') { c = c.match(/\d+/g); while(j--) v.push(~~c[j]); } else { if(c.length==4) c='#'+s(c,1)+s(c,1)+s(c,2)+s(c,2)+s(c,3)+s(c,3); while(j--) v.push(parseInt(s(c,1+j*2,2), 16)); } while(j--) { tmp = ~~(v[j+3]+(v[j]-v[j+3])*pos); r.push(tmp<0?0:tmp>255?255:tmp); } return 'rgb('+r.join(',')+')'; } function parse(prop){ var p = parseFloat(prop), q = prop.replace(/^[\-\d\.]+/,''); return isNaN(p) ? { v: q, f: color, u: ''} : { v: p, f: interpolate, u: q }; } function normalize(style){ var css, rules = {}, i = props.length, v; parseEl.innerHTML = '<div style="'+style+'"></div>'; css = parseEl.childNodes[0].style; while(i--) if(v = css[props[i]]) rules[props[i]] = parse(v); return rules; } container[emile] = function(el, style, opts, after){ el = typeof el == 'string' ? document.getElementById(el) : el; opts = opts || {}; var target = normalize(style), comp = el.currentStyle ? el.currentStyle : getComputedStyle(el, null), prop, current = {}, start = +new Date, dur = opts.duration||200, finish = start+dur, interval, easing = opts.easing || function(pos){ return (-Math.cos(pos*Math.PI)/2) + 0.5; }; for(prop in target) current[prop] = parse(comp[prop]); interval = setInterval(function(){ var time = +new Date, pos = time>finish ? 1 : (time-start)/dur; for(prop in target) el.style[prop] = target[prop].f(current[prop].v,target[prop].v,easing(pos)) + target[prop].u; if(time>finish) { clearInterval(interval); opts.after && opts.after(); after && setTimeout(after,1); } },10); } })('emile', this);
dom/background.js
var CHUNK_INTERVAL = 25; // ms. var running = false, progress = 0, processTimer; function runChunk() { window.clearTimeout(processTimer); processTimer = null; if (!running) return; // Some work chunk. Let's simulate it: for (var i = 0; i < 10000; i += (Math.random() * 5).round()) ; ++progress; updateUI(); // See source archive -- just updates a progressbar if (progress < 100) { processTimer = window.setTimeout(runChunk, CHUNK_INTERVAL); } else { progress = 0, running = false; } } function toggleProcessing() { running = !running; if (running) { processTimer = window.setTimeout(runChunk, CHUNK_INTERVAL); } }
用CSS属性设置tooltip元素为默认隐藏,并在其内容标签上加:hover选择器来恢复显示。
但这种方式在IE6不起作用,因为IE6只允许<a>元素上有:hover。只能手动编写脚本,响应mouseover和mouseout。
作者推荐Prototype的Prototip2库http://www.nickstakenburg.com/projects/prototip2/
ui/tooltips/index.html
<li tabindex="1"> <span class="name">Capacity: 1.5 TB</span> <div class="tooltip" > <p><strong>1.5 Terabyte = 1,536 Gigabytes</strong></p> <p>Enough for 50,000 songs, 1,000 DivX movies, 100,000 high-definition photos, hundreds of iDVD projects and plenty of backup space left.</p> </div> </li>
ui/tooltips/tooltips.css
#files li { position: relative; } #files li .tooltip { position: absolute; top: 8px; left: 120px; width: 24em; z-index: 1; display: none; /* IE6 doesn't know li:hover, so we need to toggle via JS, therefore avoiding in-rule display: none */ _display: block; border: 1px solid gray; background: #fffdc3 url(bg_tooltip.png) top left repeat-x; } #files li:hover .tooltip, #files li:focus .tooltip { display: block; }
ui/tooltips/tooltips.js
function toggle(reveal, e) { var trigger = e.findElement('li'), tooltip = trigger && trigger.down('.tooltip'); if (!tooltip) return; tooltip[reveal ? 'show' : 'hide'](); } document.observe('dom:loaded', function() { var isIE6 = Prototype.Browser.IE && undefined === document.body.style.maxHeight; if (!isIE6) return; var files = $('files'), tooltips = files && files.select('.tooltip'); if (!files || 0 == tooltips.length) return; tooltips.invoke('hide'); files.observe('mouseover', toggle.curry(true)). observe('mouseout', toggle.curry(false)); });
用<a>链接到要弹出的内容(href=,target=”_blank”),然后在链接上挂上JavaScript代码。这样可以解决禁止弹窗、屏幕阅读器(视觉障碍者使用)、搜索引擎的访问问题。
ui/popups/index.html
<p> The great thing about <a class="popup" target="_blank" href="http://pragprog.com/titles/pg_js">Pocket Guide to JavaScript</a> is that it focuses on a bunch of specific, useful tasks.</p>
ui/popus/popus.js
var POPUP_FEATURES = 'status=yes,resizable=yes,scrollbars=yes,' + 'width=800,height=500,left=100,top=100'; function hookPopupLink(e) { var trigger = e.findElement('a.popup'); if (!trigger) return; e.stop(); trigger.blur(); var wndName = trigger.readAttribute('target') || ('wnd' + trigger.identify()); window.open(trigger.href, wndName, POPUP_FEATURES).focus(); } document.observe('click', hookPopupLink);
利用FancyBox jQuery插件
ui/lightbox/lightbox.js
$('#thumbnails a').fancybox({ zoomSpeedIn: 300, zoomOpacity: true, overlayColor: '#000', overlayOpacity: 0.6 });
ui/infinite/infinite.js
function lowEnough() { var pageHeight = Math.max(document.body.scrollHeight, document.body.offsetHeight); var viewportHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight || 0; var scrollHeight = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0; // Trigger for scrolls within 20 pixels from page bottom return pageHeight - viewportHeight - scrollHeight < 20; } function checkScroll() { if (!lowEnough()) return pollScroll(); $('spinner').show(); new Ajax.Updater('posts', 'more.php', { method: 'get', insertion: 'bottom', onComplete: function() { $('spinner').hide(); }, onSuccess: pollScroll }); } function pollScroll() { setTimeout(checkScroll, 100); } pollScroll();
ui/viewport/index.html
<h2>Comments</h2> <div id="extraComments"> <a id="loadKnownComments" href="?with_known_comments">See previous comments you already know about</a> </div> <h3>Comment 5</h3>
ui/viewport/viewport.js
function loadKnownComments(e) { e.stop(); var zone = $('extraComments'), ref = zone.next('h3'); var upd = new Ajax.Request('known_comments.html', { method: 'get', onSuccess: function(res) { var orig = ref.cumulativeOffset().top - document.viewport.getScrollOffsets().top; zone.insert({ before: res.responseText }); window.scrollTo(0, ref.cumulativeOffset().top - orig); } }); } document.observe('dom:loaded', function() { var loader = $('loadKnownComments'); loader && loader.observe('click', loadKnownComments); });
form/feedback/index.html
<p> <label for="edtDescription">Description</label> <textarea id="edtDescription" name="description" cols="40" rows="5" class="maxLength200"></textarea> </p>
form/feedback/feedback.js
var maxLengths = {}; function bindMaxLengthFeedbacks() { var mlClass, maxLength, feedback; $$('*[class^=maxLength]').each(function(field) { field.up('p').addClassName('lengthFeedback'); mlClass = field.className.match(/\bmaxLength(\d+)\b/)[0]; maxLength = parseInt(mlClass.replace(/\D+/g, ''), 10); feedback = new Element('span', { 'class': 'feedback' }); maxLengths[field.identify()] = [maxLength, feedback]; updateFeedback(field); field.observe('keyup', updateFeedback). observe('keypress', updateFeedback); feedback.clonePosition(field, { setHeight: false, offsetTop: field.offsetHeight + 2 }); field.insert({ after: feedback }); }); } function updateFeedback(e) { var field = e.tagName ? e : e.element(); var current = field.getValue().length, data = maxLengths[field.id], max = data[0], delta = current < max ? max - current : 0; data[1].update('Remaining: ' + delta); if (current > max) { field.setValue(field.getValue().substring(0, max)); } } document.observe('dom:loaded', bindMaxLengthFeedbacks);
form/checklist/index_for_book.html
<table id="mailbox"> <thead> <tr> <th><input type="checkbox" id="toggler" /></th> <th>Subject</th> <th>Date</th> <!-- From, Size, Attachments… --> </tr> </thead> <tbody> <tr> <td><input type="checkbox" name="mail_ids[]" value="1" /></td> <td>Happy new year!</td> <td>Jan 1, 2010 00:03am</td> <!-- … --> </tr> <!-- More rows… --> </tbody> </table>
form/checklist/checklist.js
function toggleAllCheckboxes() { var scope = this.up('table').down('tbody'), boxes = scope && scope.select('tr input[type="checkbox"]:first-of-type'); var refChecked = this.checked; (boxes || []).each(function(box) { box.checked = refChecked; }); } document.observe('dom:loaded', function() { $('toggler').observe('click', toggleAllCheckboxes); });
检验未填的必填项
form/validation101/validation101.js
function checkForm(e) { var firstOffender, value; this.select('.required').each(function(field) { value = field.getValue(); if (value && !value.blank()) { field.up('p').removeClassName('missing'); } else { firstOffender = firstOffender || field; field.up('p').addClassName('missing'); } }); if (firstOffender) { e.stop(); firstOffender.focus(); } } document.observe('dom:loaded', function() { $('registration').observe('submit', checkForm); });
检查特定格式的输入域
form/validation102/validation102.js
var FIELD_PATTERNS = { integer: /^\d+$/, number: /^\d+(?:\.\d+)?$/, email: /^[A-Z0-9._%+-]+@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}$/i }; function checkField(field) { var value = $F(field).toString().strip(); for (var pattern in FIELD_PATTERNS) { if (!field.hasClassName(pattern)) continue; if (!FIELD_PATTERNS[pattern].test(value)) return false; } return true; }
和服务器端通信,监测登录名域
form/validation_ajax/validation_ajax.js
document.observe('dom:loaded', function checkLogin() { var feedback = $('user_login').next('.feedback'), spinner = $('user_login').next('.spinner'); new Field.Observer('user_login', 0.8, function(_, value) { if (value.length < 2) return; feedback.hide(); spinner.show(); new Ajax.Request('check_login.php', { method: 'get', parameters: { login: value }, onComplete: function(res) { if (Ajax.activeRequestCount > 1) return; if (res.request.success() && res.status) { feedback.update('Login available!').removeClassName('ko'); } else { feedback.update('Login taken!').addClassName('ko'); } spinner.hide(); feedback.show(); }, }); });
form/validation_ajax/check_login.php
sleep(rand(5, 10) / 10.0); // Simulate intarwebs delay… $RESERVED = array('bob', 'doudou', 'tdd', 'meshak', 'ook'); $login = isset($_GET['login']) ? $_GET['login'] : ''; $response = in_array($login, $RESERVED) ? '422 Conflict' : '202 Accepted'; header('HTTP/1.1 ' . $response);
form/tooltips/index.html
<p> <label for="user_login"> Login* <span class="tooltip" style="display: none;"> Logins must be unique, at least 3 characters long, and may only use letters, numbers, white space, hyphens, underscores and periods. </span> </label> <input type="text" id="user_login" name="user[login]" class="required text" /> </p>
form/tooltips/tooltips.css
#registration { font-family: sans-serif; } #registration p { margin: 0 0 0.5em;} /* START:main */ #registration label { float: left; width: 6em; position: relative; zoom: 1; } #registration input.text { width: 14em; } #registration .tooltip { display: block; position: absolute; left: 24em; top: 0; padding: 0.35em 0.5em 0.35em 2em; width: 15em; border: 1px solid silver; color: gray; font-size: 80%; background: #ffc url(lightbulb.png) 0.5em 0.3em no-repeat; }
form/tooltips/tooltips.js
document.observe('dom:loaded', function() { var attr = Prototype.Browser.IE ? 'htmlFor' : 'for'; function showTooltip() { var tooltip = $$('label['+attr+'="'+this.id+'"] .tooltip').first(); tooltip && tooltip.show(); } function hideTooltip() { var tooltip = $$('label['+attr+'="'+this.id+'"] .tooltip').first(); tooltip && tooltip.hide(); } $('registration').getInputs().invoke('observe', 'focus', showTooltip). invoke('observe', 'blur', hideTooltip); });
Prototype的Sctipt.aculo.us控件
form/autocompletion/index.html
<div class="p" id="local"> <label for="edtCachedSearch">Local search:</label> <input type="text" id="edtCachedSearch" name="search" type="text" /> <div class="completions"></div> </div> <div class="p"> <label for="edtAjaxSearch">Ajax search:</label> <input type="text" id="edtAjaxSearch" name="search" type="text" /> (capitals of the world) <div class="completions"></div> </div>
form/autocompletion/autocompletion.css
.completions { border: 1px solid silver; background: white; font-size: 80%; z-index: 2; } .completions ul { margin: 0; padding: 0; list-style-type: none; } .completions li { line-height: 1.5em; white-space: nowrap; overflow: hidden; } .completions li.selected { background: #ffa; } .completions strong { color: green; }
form/autocompletion/autocompletion.js
var FREQUENT_SEARCHES = [ 'JavaScript', 'JavaScript frameworks', 'Prototype', 'jQuery', 'Dojo', 'MooTools', 'Ext', 'Ext JS', 'script.aculo.us', 'Scripty2', 'Ajax', 'XHR', '42' ]; function initLocalCompletions() { var field = $('edtCachedSearch'), zone = field.next('.completions'); new Autocompleter.Local(field, zone, FREQUENT_SEARCHES, { fullSearch: true }); } function initAjaxCompletions() { var field = $('edtAjaxSearch'), zone = field.next('.completions'); new Ajax.Autocompleter(field, zone, 'autocomplete.php', { method: 'get', paramName: 'search' }); }
HTML5之前使用Base64编码,每个上传文件膨胀33%
form/uploads/index.html
<form method="post" action="server.php" enctype="multipart/form-data"> <ul id="uploads"></ul> <p><input type="file" name="files[]" id="filSelector" /></p> <p><input type="submit" value="Send these files" /></p> </form>
form/uploads/uploads.js
var ICONS = $H({ word: $w('doc docx'), image: $w('jpg jpeg gif png') }); function getFileClass(fileName) { var ext = (fileName.match(/\.(.+?)$/) || [])[1].toString().toLowerCase(); var icon = ICONS.detect(function(pair) { return pair[1].include(ext); }); return (icon || [])[0]; } function handleQueueRemoval(e) { var trigger = e.findElement('button'); trigger && trigger.up('li').remove(); } function queueFile() { var fileName = $F(this), clone = this.cloneNode(true); var item = new Element('li', { 'class': getFileClass(fileName) }); $(clone).observe('change', queueFile).setValue(''); this.parentNode.appendChild(clone); item.appendChild(this); item.appendChild(document.createTextNode(fileName)); item.insert('<button><img src="remove.png" alt="Remove" /></button>'); $('uploads').appendChild(item); } document.observe('dom:loaded', function() { $('filSelector').observe('change', queueFile); $('uploads').observe('click', handleQueueRemoval); });
JSON-P的传输主要依赖于动态生成的<script>标签,所以传输数据可以不限于同一来源。
jsonp和ajax完全是两个概念,可以说jsonp出现的理由就是弥补ajax无法跨域访问的缺陷而出现的。jsonp返回的数据并不是json,而是javascrip。
参考文章:说说JSON和JSONP 初识jsonp
server/jsonp/jsonp.js
function injectData(data) { var ref = $('sysInfo').down('tbody tr:last-child'), row = new Element('tr'), key; ref.select('td').each(function(cell) { row.appendChild($(cell.cloneNode(true)).update(data[cell.className])); }); ref.insert({ after: row }); } window.injectData = injectData; function loadJSONPBasic(e) { e.stop(); this.blur(); document.documentElement.firstChild.appendChild( new Element('script', { type: 'text/javascript', src: this.href + '&r=' + Math.random() })); } function loadJSONP(e) { e.stop(); this.blur(); var script = new Element('script', { type: 'text/javascript', src: this.href }); script.src += ('&r=' + script.identify()); script.observe('load', Element.remove.curry(script)); document.documentElement.firstChild.appendChild(script); } document.observe('dom:loaded', function() { $('triggerJSONP').observe('click', loadJSONP); });
随机参数是为了避免浏览器缓存而添加的,为了保证代码质量,最好先检查下一URI,以决定用&还是?(这个参数是否是第一个参数)
在浏览器载入之后将其删除,以免DOM变得过大
跨域的方法:
server/crossdomain1/crossdomain1.js
// 同时使用动态生成的表单和<iframe> function loadUsingDF1(e) { e.stop(); this.blur(); var warp = new Element('iframe', { name: '__blackhole' }); warp.setStyle('width: 0; height: 0; border: 0'); document.body.appendChild(warp); warp.observe('load', function() { $('responses').insert('<p>OK, posted.</p>'); }); var form = new Element('form', { method: 'post', action: this.href, target: '__blackhole' }); form.submit(); } // 使用得到204响应的动态生成的表单 function loadUsingDF2(e) { e.stop(); this.blur(); var form = new Element('form', { method: 'post', action: this.href }); form.submit(); Element.insert.defer('responses', '<p>OK, posted.</p>'); } // 使用服务器端协议 function loadUsingSSP(e) { e.stop(); this.blur(); new Ajax.Updater({ success: 'responses' }, 'ssp.php', { method: 'get', parameters: { uri: this.href }, insertion: 'bottom' }); } // 使用CORS兼容的XMLHttpRequest function loadUsingXHR(e) { e.stop(); this.blur(); new Ajax.Updater({ success: 'responses' }, this.href, { method: 'get', insertion: 'bottom' }); }
YQL并不是云数据库的一部分,它是一个严格的查询处理托管服务。另外,这也意味着YQL不受单独的数据资源限制,甚至不限制应用于雅虎的自身产品。YQL可以操作任何第三方数据源,只要对方是一种常见的格式,如RSS, ATOM, JSON, XML,等等。
// 使用之前那种普通的JSON-P function loadUsingJSONP(e) { e.stop(); this.blur(); window.jsonpCallback = function jsonpCallback(data) { $('responses').update(data.payload.escapeHTML()); }; document.documentElement.firstChild.appendChild( new Element('script', { type: 'text/javascript', src: this.href + '?r=' + Math.random() + '&callback=jsonpCallback' })); } // 使用JSON-P-X形式的YQL html表(XML格式的JSON-P) function loadUsingYQLget(e) { e.stop(); this.blur(); window.yqlCallback = function yqlCallback(data) { $('responses').update('<ul>' + data.results.map(function(td) { return '<li>' + td.replace(/<\/?(?:td|p)[^>]*>/g, ''). replace(/href="/g, 'href="http://github.com') + '</li>'; }).join("\n") + '</ul>'); }; var url = this.href, xpath = "//*[@class='title']", yql = 'select * from html where url="' + url + '" and xpath="' + xpath + '"', data = { q: yql, format: 'xml', callback: 'yqlCallback' }; document.documentElement.firstChild.appendChild( new Element('script', { type: 'text/javascript', src: 'http://query.yahooapis.com/v1/public/yql?' + Object.toQueryString(data) + '&r=' + Math.random() })); } // 使用JSON-P形式的YQL htmlpost表 function loadUsingYQLpost(e) { e.stop(); this.blur(); window.yqlCallback = function yqlCallback(data) { $('responses').update(data.query.results.postresult.p.join("<br/>")); }; var post = Object.toQueryString({ foo: 'foo', bar: 'bar' }), url = this.href, xpath = "//p", env = 'store://datatables.org/alltableswithkeys', yql = 'select * from htmlpost where url="' + url + '" and postdata="' + post + '"' + ' and xpath="' + xpath + '"', data = { q: yql, format: 'json', env: env, callback: 'yqlCallback' }; document.documentElement.firstChild.appendChild( new Element('script', { type: 'text/javascript', src: 'http://query.yahooapis.com/v1/public/yql?' + Object.toQueryString(data) + '&r=' + Math.random() })); } // 使用CSSHttpRequest function loadUsingCHR(e) { e.stop(); this.blur(); CSSHttpRequest.get(this.href, function(res) { $('responses').insert('<p>' + res.escapeHTML() + '</p>'); }); }