├── .gitignore ├── Cakefile ├── README ├── public ├── css │ ├── api.css │ └── reset.css ├── images │ ├── button_bg.gif │ ├── logo.png │ └── spinner.gif └── js │ ├── api.js │ ├── jquery-1.3.2.js │ └── underscore-1.0.2.js ├── src ├── api.coffee └── app.coffee └── views └── index.html.ejs /.gitignore: -------------------------------------------------------------------------------- 1 | lib/*.js 2 | config/keys.json -------------------------------------------------------------------------------- /Cakefile: -------------------------------------------------------------------------------- 1 | {print} = require 'sys' 2 | {spawn, exec} = require 'child_process' 3 | 4 | task 'build', 'Build and watch the CoffeeScript source files', -> 5 | backend = spawn 'coffee', ['-cw', '-o', 'lib', 'src/app.coffee'] 6 | backend.stdout.addListener 'data', (data) -> print data.toString() 7 | frontend = spawn 'coffee', ['-cw', '-o', 'public/js', 'src/api.coffee'] 8 | frontend.stdout.addListener 'data', (data) -> print data 9 | 10 | task 'deploy', 'Deploy to Linode', -> 11 | exec "rsync -av --progress --inplace --rsh='ssh -p9977' . jashkenas@ashkenas.com:/home/jashkenas/sites/api_playground", (err, stdout) -> 12 | puts "Deployed..." 13 | exec "ssh -p 9977 jashkenas@ashkenas.com 'pkill -n node; EXPRESS_ENV=production nohup node sites/api_playground/lib/app.js > /var/log/node.log 2>&1 &'", (err, stdout, stderr) -> 14 | puts "Restarted..." 15 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Huge amounts of data are available online via APIs, but often in forms 2 | that aren't viewable by non-technical folks (like many journalists). 3 | This tool helps browse these data sets to make it easier to imagine ways 4 | to turn data into stories. 5 | -------------------------------------------------------------------------------- /public/css/api.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-size: 62.5%; 3 | background: white; 4 | } 5 | 6 | h1, h2, h3, h4, h5, h6 { 7 | font-size: 1.4em; 8 | line-height: 1.5em; 9 | margin: 1.5em 0 0.5em 0; 10 | } 11 | h1 { 12 | font-size: 1.25em; 13 | font-weight: bold; 14 | margin-bottom: 1em; 15 | } 16 | 17 | a { 18 | cursor: pointer; 19 | color: #536eac; 20 | text-decoration: none; 21 | } 22 | a:hover { 23 | color: #000055; 24 | } 25 | 26 | p { 27 | margin: 0 0 1em 0; 28 | width: 650px; 29 | } 30 | 31 | label { 32 | font-weight: bold; 33 | font-size: 0.85em; 34 | } 35 | 36 | .clear { 37 | clear: both; 38 | } 39 | 40 | #container { 41 | position: relative; 42 | font: 1.4em/1.5em "Lucida Grande", "Lucida Sans Unicode", Arial, sans-serif; 43 | margin: 50px 50px 50px 50px; 44 | color: black; 45 | } 46 | #controls { 47 | margin-top: 2em; 48 | margin-bottom: 1em; 49 | } 50 | #box_label { 51 | width: 650px; 52 | } 53 | #picker { 54 | margin: 0 1em 0 1em; 55 | } 56 | #text, #line { 57 | line-height: 1.5em; 58 | padding: 0.5em; 59 | width: 650px; 60 | display: none; 61 | margin: 0.5em 0 1em; 62 | } 63 | body.text_mode #text, body.line_mode #line { 64 | display: inline; 65 | } 66 | #text { 67 | height: 200px; 68 | display: none; 69 | } 70 | #results { 71 | margin: 2em 0 0 0; 72 | } 73 | #map { 74 | height: 400px; 75 | } 76 | #results table { 77 | width: 100%; 78 | font-size: 0.8em; 79 | line-height: 1.4em; 80 | border-left: 1px solid #C1DAD7; 81 | } 82 | #results th { 83 | font-weight: bold; 84 | color: #333; 85 | border: 1px solid #d0d0d0; 86 | border-left: 0; 87 | background: #d7d7d7; 88 | background: -webkit-gradient(linear, left top, left bottom, from(#ffffff), to(#d0d0d0)); 89 | background: -moz-linear-gradient(top, #ffffff, #d0d0d0); 90 | padding: 6px 6px 6px 12px; 91 | } 92 | #results td { 93 | vertical-align: middle; 94 | max-width: 425px; 95 | border-right: 1px solid #C1DAD7; 96 | border-bottom: 1px solid #C1DAD7; 97 | background: #fff; 98 | padding: 6px 6px 6px 12px; 99 | color: #333; 100 | overflow: hidden; 101 | } 102 | #results td p { 103 | width: auto; 104 | } 105 | 106 | .minibutton { 107 | float: left; 108 | cursor: pointer; 109 | color: #fff; 110 | text-shadow: #aaa 0 -1px 0; 111 | font-weight: bold; 112 | font-size: 11px; 113 | line-height: 11px; 114 | padding: 5px 10px 6px; 115 | height: 11px; 116 | text-align: center; 117 | -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; 118 | box-shadow: 0 1px 2px #c4c4c4; -webkit-box-shadow: 0 1px 2px #c4c4c4; -moz-box-shadow: 0 1px 2px #c4c4c4; 119 | border: 1px solid #b2b2b2; border-top-color: #c9c9c9; border-bottom-color: #9a9a9a; 120 | background: url(/public/images/button_bg.gif) repeat-x left top; 121 | } 122 | .minibutton:active { 123 | position: relative; 124 | box-shadow: 0 0 1px #c4c4c4; -webkit-box-shadow: 0 0 1px #c4c4c4; -moz-box-shadow: 0 0 1px #c4c4c4; 125 | top: 1px; 126 | } 127 | .minibutton::selection, .minibutton > span::selection { 128 | background: transparent; 129 | } -------------------------------------------------------------------------------- /public/css/reset.css: -------------------------------------------------------------------------------- 1 | /*------------------------------ RESET + DEFAULT STYLES ---------------------------------*/ 2 | 3 | /* 4 | Eric Meyer's final reset.css 5 | Source: http://meyerweb.com/eric/thoughts/2007/05/01/reset-reloaded/ 6 | */ 7 | html, body, div, span, applet, object, iframe, 8 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 9 | a, abbr, acronym, address, big, cite, code, 10 | del, dfn, em, font, img, ins, kbd, q, s, samp, 11 | small, strike, strong, sub, sup, tt, var, 12 | dl, dt, dd, ol, ul, li, 13 | fieldset, form, label, legend, 14 | table, caption, tbody, tfoot, thead, tr, th, td { 15 | margin: 0; 16 | padding: 0; 17 | border: 0; 18 | outline: 0; 19 | font-weight: inherit; 20 | font-style: inherit; 21 | font-size: 100%; 22 | font-family: inherit; 23 | vertical-align: baseline; 24 | } 25 | 26 | strong { 27 | font-weight: bold; 28 | } 29 | /* remember to define focus styles! */ 30 | :focus { 31 | outline: 0; 32 | } 33 | body { 34 | line-height: 1; 35 | color: black; 36 | background: white; 37 | } 38 | ol, ul { 39 | list-style: none; 40 | } 41 | /* tables still need 'cellspacing="0"' in the markup */ 42 | table { 43 | border-collapse: separate; 44 | border-spacing: 0; 45 | } 46 | caption, th, td { 47 | text-align: left; 48 | font-weight: normal; 49 | } 50 | blockquote:before, blockquote:after, 51 | q:before, q:after { 52 | content: ""; 53 | } 54 | blockquote, q { 55 | quotes: "" ""; 56 | } -------------------------------------------------------------------------------- /public/images/button_bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jashkenas/api-playground/747256bf55eea26d68c78ff0817c010a5879385f/public/images/button_bg.gif -------------------------------------------------------------------------------- /public/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jashkenas/api-playground/747256bf55eea26d68c78ff0817c010a5879385f/public/images/logo.png -------------------------------------------------------------------------------- /public/images/spinner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jashkenas/api-playground/747256bf55eea26d68c78ff0817c010a5879385f/public/images/spinner.gif -------------------------------------------------------------------------------- /public/js/api.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var __hasProp = Object.prototype.hasOwnProperty; 3 | window.API = { 4 | services: { 5 | zemanta: { 6 | box: 'Enter a block of text here — for example, a portion\ 7 | of a newspaper article — and see what Creative Commons\ 8 | images and related articles Zemanta would provide alongside.', 9 | description: 'Zemanta is a service that finds names, locations, photos,\ 10 | links and other material based on a chunk of text.', 11 | mode: 'text' 12 | }, 13 | truveo: { 14 | box: 'Enter video keywords:', 15 | description: 'Truveo is a huge database of online videos.', 16 | mode: 'line' 17 | }, 18 | opencongress: { 19 | box: 'Enter the last name of a Member of Congress:', 20 | description: 'A general resource on Congress, produced by the Participatory\ 21 | Politics Foundation and the Sunlight Foundation.', 22 | mode: 'line' 23 | }, 24 | guardian: { 25 | box: 'Enter anything that might appear in a story, and see what\ 26 | related articles the Guardian API would suggest.', 27 | description: 'This API lets you mine The Guardian\'s article database.', 28 | mode: 'line' 29 | }, 30 | oilreporter: { 31 | box: '', 32 | description: 'Oil Reporter is a new effort to crowdsource oil sightings\ 33 | along the Gulf coast.', 34 | mode: 'none' 35 | }, 36 | twitter: { 37 | box: 'Enter a term to search for on Twitter, and see what the\ 38 | community is saying about it.', 39 | description: 'How might you mine the Twitterverse? With Twitter\'s API.', 40 | mode: 'line' 41 | }, 42 | googlemaps: { 43 | box: 'Enter a location to get a map:', 44 | description: 'The Google Maps API is one of the oldest and most widely\ 45 | used APIs in existence.', 46 | mode: 'gmaps line', 47 | custom: true 48 | }, 49 | freebase: { 50 | box: 'Enter a term to find with Freebase:', 51 | description: 'Freebase is a massive, collaboratively-edited database of\ 52 | cross-linked data.', 53 | mode: 'line' 54 | }, 55 | calais: { 56 | box: 'Enter a block of text here — for example, a portion\ 57 | of a newspaper article — to see what entities are extracted.', 58 | description: 'OpenCalais finds entities (people, places, organizations, terms)\ 59 | within a document, and connects them to the web of linked data.', 60 | mode: 'text' 61 | } 62 | }, 63 | initialize: function() { 64 | $('#go').click(API.go); 65 | $('#picker').bind('change', API.change); 66 | $('#line').keypress(function(e) { 67 | if (e.keyCode === 13) { 68 | return API.go(); 69 | } 70 | }); 71 | return API.change('freebase'); 72 | }, 73 | change: function(service) { 74 | var api; 75 | service = _.isString(service) ? service : $('#picker').val(); 76 | api = API.services[service]; 77 | API.current = api; 78 | document.body.className = ("" + (api.mode) + "_mode"); 79 | $('#box_label').html(api.box); 80 | $('#description').html(api.description); 81 | $('#results').html(''); 82 | return API.getInput().focus(); 83 | }, 84 | go: function() { 85 | var api, text; 86 | api = $('#picker').val(); 87 | text = API.getInput().val(); 88 | return API.services[api].custom ? API[api](text) : API.fetch(api, text); 89 | }, 90 | getInput: function() { 91 | return API.current.mode === 'text' ? $('#text') : $('#line'); 92 | }, 93 | render: function(data) { 94 | return $('#results').html(API.table(data)); 95 | }, 96 | fetch: function(api, value) { 97 | var success; 98 | $('#spinner').show(); 99 | success = function(response) { 100 | $('#spinner').hide(); 101 | if ((typeof console === "undefined" || console === null) ? undefined : console.log) { 102 | console.log(response); 103 | } 104 | return API[("" + (api) + "Complete")](response); 105 | }; 106 | return $.post("/api/" + (api) + ".json", { 107 | text: value 108 | }, success, 'json'); 109 | }, 110 | googlemaps: function(text) { 111 | var geocoder, latlng, map, options; 112 | $('#results').html('
'); 113 | geocoder = new google.maps.Geocoder(); 114 | latlng = new google.maps.LatLng(-34.397, 150.644); 115 | options = { 116 | zoom: 13, 117 | center: latlng, 118 | mapTypeId: google.maps.MapTypeId.SATELLITE 119 | }; 120 | map = new google.maps.Map($('#map')[0], options); 121 | return geocoder.geocode({ 122 | address: text 123 | }, function(results, status) { 124 | var loc; 125 | if (status === google.maps.GeocoderStatus.OK) { 126 | loc = results[0].geometry.location; 127 | map.setCenter(loc); 128 | return new google.maps.Marker({ 129 | map: map, 130 | position: loc 131 | }); 132 | } else { 133 | return alert("Geocode was not successful for the following reason: " + (status)); 134 | } 135 | }); 136 | }, 137 | zemantaComplete: function(response) { 138 | var articles, images, keywords; 139 | images = { 140 | title: "Images", 141 | headers: ["Description", "Image"], 142 | rows: _.map(response.images, function(image) { 143 | return [image.description, ("")]; 144 | }) 145 | }; 146 | articles = { 147 | title: "Articles", 148 | headers: ["Title", "Link", "Publication Date"], 149 | rows: _.map(response.articles, function(article) { 150 | return [ 151 | article.title, { 152 | url: article.url, 153 | text: article.url 154 | }, article.published_datetime 155 | ]; 156 | }) 157 | }; 158 | keywords = { 159 | title: "Keywords", 160 | headers: ["Keyword", "Confidence"], 161 | rows: _.map(response.keywords, function(keyword) { 162 | return [keyword.name, keyword.confidence]; 163 | }) 164 | }; 165 | return API.render({ 166 | tables: [images, articles, keywords] 167 | }); 168 | }, 169 | truveoComplete: function(response) { 170 | var videos; 171 | if (!(response.response.data.results.videoSet.videos)) { 172 | return alert('No related videos were found.'); 173 | } 174 | videos = { 175 | title: "Videos", 176 | headers: ["Title", "Channel", "Description", "Video"], 177 | rows: _.map(response.response.data.results.videoSet.videos, function(video) { 178 | return [video.title, video.channel, video.description, video.videoPlayerEmbedTag]; 179 | }) 180 | }; 181 | return API.render({ 182 | tables: [videos] 183 | }); 184 | }, 185 | opencongressComplete: function(response) { 186 | var articles, news, people; 187 | if (!(response.people)) { 188 | return alert("No member of Congress by that name could be found."); 189 | } 190 | people = { 191 | title: "Members of Congress", 192 | headers: ["Name", "Website", "Phone", "Office", "Religion"], 193 | rows: _.map(response.people, function(person) { 194 | return [ 195 | person.name, { 196 | url: person.website, 197 | text: person.website 198 | }, person.phone, person.congress_office, person.religion 199 | ]; 200 | }) 201 | }; 202 | news = _.flatten(_.map(response.people, function(person) { 203 | return person.recent_news; 204 | })); 205 | articles = { 206 | title: "Articles", 207 | headers: ["Title", "Source", "Excerpt", "Link"], 208 | rows: _.map(news, function(article) { 209 | return [ 210 | article.title, article.source, article.excerpt, { 211 | url: article.url, 212 | text: article.url 213 | } 214 | ]; 215 | }) 216 | }; 217 | return API.render({ 218 | tables: [people, articles] 219 | }); 220 | }, 221 | guardianComplete: function(response) { 222 | var articles; 223 | if (!(response.response.results.length)) { 224 | return alert("No related articles were found."); 225 | } 226 | articles = { 227 | title: "Articles", 228 | headers: ["Title", "Section", "Excerpt", "Link"], 229 | rows: _.map(response.response.results, function(article) { 230 | return [ 231 | article.webTitle, article.sectionName, article.fields.trailText + '...', { 232 | url: article.webUrl, 233 | text: article.webUrl 234 | } 235 | ]; 236 | }) 237 | }; 238 | return API.render({ 239 | tables: [articles] 240 | }); 241 | }, 242 | oilreporterComplete: function(response) { 243 | var reports; 244 | reports = { 245 | title: "Reports", 246 | headers: ["Description", "Wildlife", "Oil", "Lat/Lng", "Date"], 247 | rows: _.map(response, function(report) { 248 | return [report.description, report.wildlife, report.oil, report.latitude + ' / ' + report.longitude, report.created_at]; 249 | }) 250 | }; 251 | return API.render({ 252 | tables: [reports] 253 | }); 254 | }, 255 | twitterComplete: function(response) { 256 | var tweets; 257 | tweets = { 258 | title: "Tweets", 259 | headers: ["User", "Picture", "Tweet"], 260 | rows: _.map(response.results, function(tweet) { 261 | return [tweet.from_user, "", tweet.text]; 262 | }) 263 | }; 264 | return API.render({ 265 | tables: [tweets] 266 | }); 267 | }, 268 | freebaseComplete: function(response) { 269 | var results; 270 | results = { 271 | title: "Results", 272 | headers: ["Name", "Image", "Relevance", "Categories", "Link"], 273 | rows: _.map(response.result, function(item) { 274 | var pic, types, url; 275 | types = _.map(item.type, function(el) { 276 | return el.name; 277 | }); 278 | url = "http://www.freebase.com/view" + item.id; 279 | pic = ""; 280 | return [ 281 | item.name, pic, item['relevance:score'], types.join(', '), { 282 | url: url, 283 | text: url 284 | } 285 | ]; 286 | }) 287 | }; 288 | return API.render({ 289 | tables: [results] 290 | }); 291 | }, 292 | calaisComplete: function(response) { 293 | var _a, _b, _c, _d, hash, rows, sets, tables, title, val; 294 | sets = {}; 295 | _a = response; 296 | for (hash in _a) { 297 | if (!__hasProp.call(_a, hash)) continue; 298 | val = _a[hash]; 299 | if (('Category' === (_b = val._type) || 'Company' === _b || 'Organization' === _b || 'City' === _b || 'Person' === _b || 'IndustryTerm' === _b || 'NaturalFeature' === _b || 'Country' === _b || 'Facility' === _b || 'Region' === _b || 'Product' === _b)) { 300 | sets[val._type] || (sets[val._type] = []); 301 | sets[val._type].push([val.name, val.relevance, val.instances[0].detection]); 302 | } 303 | } 304 | tables = (function() { 305 | _c = []; _d = sets; 306 | for (title in _d) { 307 | if (!__hasProp.call(_d, title)) continue; 308 | rows = _d[title]; 309 | _c.push({ 310 | title: title, 311 | headers: ["Name", "Relevance", "Occurrence"], 312 | rows: rows 313 | }); 314 | } 315 | return _c; 316 | })(); 317 | return API.render({ 318 | tables: tables 319 | }); 320 | }, 321 | table: _.template("<% _.each(tables, function(table) { %>\n

<%= table.title %>

\n \n \n \n <% _.each(table.headers, function(header) { %>\n \n <% }); %>\n \n \n \n <% _.each(table.rows, function(row) { %>\n \n <% _.each(row, function(col) { %>\n \n <% }); %>\n \n <% }); %>\n \n
<%= header %>
\n <% if (col && col.url) { %>\n \" target=\"_blank\"><%= col.text %>\n <% } else { %>\n <%= col %>\n <% } %>\n
\n<% }); %>") 322 | }; 323 | $(API.initialize); 324 | })(); 325 | -------------------------------------------------------------------------------- /public/js/jquery-1.3.2.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery JavaScript Library v1.3.2 3 | * http://jquery.com/ 4 | * 5 | * Copyright (c) 2009 John Resig 6 | * Dual licensed under the MIT and GPL licenses. 7 | * http://docs.jquery.com/License 8 | * 9 | * Date: 2009-02-19 17:34:21 -0500 (Thu, 19 Feb 2009) 10 | * Revision: 6246 11 | */ 12 | (function(){ 13 | 14 | var 15 | // Will speed up references to window, and allows munging its name. 16 | window = this, 17 | // Will speed up references to undefined, and allows munging its name. 18 | undefined, 19 | // Map over jQuery in case of overwrite 20 | _jQuery = window.jQuery, 21 | // Map over the $ in case of overwrite 22 | _$ = window.$, 23 | 24 | jQuery = window.jQuery = window.$ = function( selector, context ) { 25 | // The jQuery object is actually just the init constructor 'enhanced' 26 | return new jQuery.fn.init( selector, context ); 27 | }, 28 | 29 | // A simple way to check for HTML strings or ID strings 30 | // (both of which we optimize for) 31 | quickExpr = /^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/, 32 | // Is it a simple selector 33 | isSimple = /^.[^:#\[\.,]*$/; 34 | 35 | jQuery.fn = jQuery.prototype = { 36 | init: function( selector, context ) { 37 | // Make sure that a selection was provided 38 | selector = selector || document; 39 | 40 | // Handle $(DOMElement) 41 | if ( selector.nodeType ) { 42 | this[0] = selector; 43 | this.length = 1; 44 | this.context = selector; 45 | return this; 46 | } 47 | // Handle HTML strings 48 | if ( typeof selector === "string" ) { 49 | // Are we dealing with HTML string or an ID? 50 | var match = quickExpr.exec( selector ); 51 | 52 | // Verify a match, and that no context was specified for #id 53 | if ( match && (match[1] || !context) ) { 54 | 55 | // HANDLE: $(html) -> $(array) 56 | if ( match[1] ) 57 | selector = jQuery.clean( [ match[1] ], context ); 58 | 59 | // HANDLE: $("#id") 60 | else { 61 | var elem = document.getElementById( match[3] ); 62 | 63 | // Handle the case where IE and Opera return items 64 | // by name instead of ID 65 | if ( elem && elem.id != match[3] ) 66 | return jQuery().find( selector ); 67 | 68 | // Otherwise, we inject the element directly into the jQuery object 69 | var ret = jQuery( elem || [] ); 70 | ret.context = document; 71 | ret.selector = selector; 72 | return ret; 73 | } 74 | 75 | // HANDLE: $(expr, [context]) 76 | // (which is just equivalent to: $(content).find(expr) 77 | } else 78 | return jQuery( context ).find( selector ); 79 | 80 | // HANDLE: $(function) 81 | // Shortcut for document ready 82 | } else if ( jQuery.isFunction( selector ) ) 83 | return jQuery( document ).ready( selector ); 84 | 85 | // Make sure that old selector state is passed along 86 | if ( selector.selector && selector.context ) { 87 | this.selector = selector.selector; 88 | this.context = selector.context; 89 | } 90 | 91 | return this.setArray(jQuery.isArray( selector ) ? 92 | selector : 93 | jQuery.makeArray(selector)); 94 | }, 95 | 96 | // Start with an empty selector 97 | selector: "", 98 | 99 | // The current version of jQuery being used 100 | jquery: "1.3.2", 101 | 102 | // The number of elements contained in the matched element set 103 | size: function() { 104 | return this.length; 105 | }, 106 | 107 | // Get the Nth element in the matched element set OR 108 | // Get the whole matched element set as a clean array 109 | get: function( num ) { 110 | return num === undefined ? 111 | 112 | // Return a 'clean' array 113 | Array.prototype.slice.call( this ) : 114 | 115 | // Return just the object 116 | this[ num ]; 117 | }, 118 | 119 | // Take an array of elements and push it onto the stack 120 | // (returning the new matched element set) 121 | pushStack: function( elems, name, selector ) { 122 | // Build a new jQuery matched element set 123 | var ret = jQuery( elems ); 124 | 125 | // Add the old object onto the stack (as a reference) 126 | ret.prevObject = this; 127 | 128 | ret.context = this.context; 129 | 130 | if ( name === "find" ) 131 | ret.selector = this.selector + (this.selector ? " " : "") + selector; 132 | else if ( name ) 133 | ret.selector = this.selector + "." + name + "(" + selector + ")"; 134 | 135 | // Return the newly-formed element set 136 | return ret; 137 | }, 138 | 139 | // Force the current matched set of elements to become 140 | // the specified array of elements (destroying the stack in the process) 141 | // You should use pushStack() in order to do this, but maintain the stack 142 | setArray: function( elems ) { 143 | // Resetting the length to 0, then using the native Array push 144 | // is a super-fast way to populate an object with array-like properties 145 | this.length = 0; 146 | Array.prototype.push.apply( this, elems ); 147 | 148 | return this; 149 | }, 150 | 151 | // Execute a callback for every element in the matched set. 152 | // (You can seed the arguments with an array of args, but this is 153 | // only used internally.) 154 | each: function( callback, args ) { 155 | return jQuery.each( this, callback, args ); 156 | }, 157 | 158 | // Determine the position of an element within 159 | // the matched set of elements 160 | index: function( elem ) { 161 | // Locate the position of the desired element 162 | return jQuery.inArray( 163 | // If it receives a jQuery object, the first element is used 164 | elem && elem.jquery ? elem[0] : elem 165 | , this ); 166 | }, 167 | 168 | attr: function( name, value, type ) { 169 | var options = name; 170 | 171 | // Look for the case where we're accessing a style value 172 | if ( typeof name === "string" ) 173 | if ( value === undefined ) 174 | return this[0] && jQuery[ type || "attr" ]( this[0], name ); 175 | 176 | else { 177 | options = {}; 178 | options[ name ] = value; 179 | } 180 | 181 | // Check to see if we're setting style values 182 | return this.each(function(i){ 183 | // Set all the styles 184 | for ( name in options ) 185 | jQuery.attr( 186 | type ? 187 | this.style : 188 | this, 189 | name, jQuery.prop( this, options[ name ], type, i, name ) 190 | ); 191 | }); 192 | }, 193 | 194 | css: function( key, value ) { 195 | // ignore negative width and height values 196 | if ( (key == 'width' || key == 'height') && parseFloat(value) < 0 ) 197 | value = undefined; 198 | return this.attr( key, value, "curCSS" ); 199 | }, 200 | 201 | text: function( text ) { 202 | if ( typeof text !== "object" && text != null ) 203 | return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) ); 204 | 205 | var ret = ""; 206 | 207 | jQuery.each( text || this, function(){ 208 | jQuery.each( this.childNodes, function(){ 209 | if ( this.nodeType != 8 ) 210 | ret += this.nodeType != 1 ? 211 | this.nodeValue : 212 | jQuery.fn.text( [ this ] ); 213 | }); 214 | }); 215 | 216 | return ret; 217 | }, 218 | 219 | wrapAll: function( html ) { 220 | if ( this[0] ) { 221 | // The elements to wrap the target around 222 | var wrap = jQuery( html, this[0].ownerDocument ).clone(); 223 | 224 | if ( this[0].parentNode ) 225 | wrap.insertBefore( this[0] ); 226 | 227 | wrap.map(function(){ 228 | var elem = this; 229 | 230 | while ( elem.firstChild ) 231 | elem = elem.firstChild; 232 | 233 | return elem; 234 | }).append(this); 235 | } 236 | 237 | return this; 238 | }, 239 | 240 | wrapInner: function( html ) { 241 | return this.each(function(){ 242 | jQuery( this ).contents().wrapAll( html ); 243 | }); 244 | }, 245 | 246 | wrap: function( html ) { 247 | return this.each(function(){ 248 | jQuery( this ).wrapAll( html ); 249 | }); 250 | }, 251 | 252 | append: function() { 253 | return this.domManip(arguments, true, function(elem){ 254 | if (this.nodeType == 1) 255 | this.appendChild( elem ); 256 | }); 257 | }, 258 | 259 | prepend: function() { 260 | return this.domManip(arguments, true, function(elem){ 261 | if (this.nodeType == 1) 262 | this.insertBefore( elem, this.firstChild ); 263 | }); 264 | }, 265 | 266 | before: function() { 267 | return this.domManip(arguments, false, function(elem){ 268 | this.parentNode.insertBefore( elem, this ); 269 | }); 270 | }, 271 | 272 | after: function() { 273 | return this.domManip(arguments, false, function(elem){ 274 | this.parentNode.insertBefore( elem, this.nextSibling ); 275 | }); 276 | }, 277 | 278 | end: function() { 279 | return this.prevObject || jQuery( [] ); 280 | }, 281 | 282 | // For internal use only. 283 | // Behaves like an Array's method, not like a jQuery method. 284 | push: [].push, 285 | sort: [].sort, 286 | splice: [].splice, 287 | 288 | find: function( selector ) { 289 | if ( this.length === 1 ) { 290 | var ret = this.pushStack( [], "find", selector ); 291 | ret.length = 0; 292 | jQuery.find( selector, this[0], ret ); 293 | return ret; 294 | } else { 295 | return this.pushStack( jQuery.unique(jQuery.map(this, function(elem){ 296 | return jQuery.find( selector, elem ); 297 | })), "find", selector ); 298 | } 299 | }, 300 | 301 | clone: function( events ) { 302 | // Do the clone 303 | var ret = this.map(function(){ 304 | if ( !jQuery.support.noCloneEvent && !jQuery.isXMLDoc(this) ) { 305 | // IE copies events bound via attachEvent when 306 | // using cloneNode. Calling detachEvent on the 307 | // clone will also remove the events from the orignal 308 | // In order to get around this, we use innerHTML. 309 | // Unfortunately, this means some modifications to 310 | // attributes in IE that are actually only stored 311 | // as properties will not be copied (such as the 312 | // the name attribute on an input). 313 | var html = this.outerHTML; 314 | if ( !html ) { 315 | var div = this.ownerDocument.createElement("div"); 316 | div.appendChild( this.cloneNode(true) ); 317 | html = div.innerHTML; 318 | } 319 | 320 | return jQuery.clean([html.replace(/ jQuery\d+="(?:\d+|null)"/g, "").replace(/^\s*/, "")])[0]; 321 | } else 322 | return this.cloneNode(true); 323 | }); 324 | 325 | // Copy the events from the original to the clone 326 | if ( events === true ) { 327 | var orig = this.find("*").andSelf(), i = 0; 328 | 329 | ret.find("*").andSelf().each(function(){ 330 | if ( this.nodeName !== orig[i].nodeName ) 331 | return; 332 | 333 | var events = jQuery.data( orig[i], "events" ); 334 | 335 | for ( var type in events ) { 336 | for ( var handler in events[ type ] ) { 337 | jQuery.event.add( this, type, events[ type ][ handler ], events[ type ][ handler ].data ); 338 | } 339 | } 340 | 341 | i++; 342 | }); 343 | } 344 | 345 | // Return the cloned set 346 | return ret; 347 | }, 348 | 349 | filter: function( selector ) { 350 | return this.pushStack( 351 | jQuery.isFunction( selector ) && 352 | jQuery.grep(this, function(elem, i){ 353 | return selector.call( elem, i ); 354 | }) || 355 | 356 | jQuery.multiFilter( selector, jQuery.grep(this, function(elem){ 357 | return elem.nodeType === 1; 358 | }) ), "filter", selector ); 359 | }, 360 | 361 | closest: function( selector ) { 362 | var pos = jQuery.expr.match.POS.test( selector ) ? jQuery(selector) : null, 363 | closer = 0; 364 | 365 | return this.map(function(){ 366 | var cur = this; 367 | while ( cur && cur.ownerDocument ) { 368 | if ( pos ? pos.index(cur) > -1 : jQuery(cur).is(selector) ) { 369 | jQuery.data(cur, "closest", closer); 370 | return cur; 371 | } 372 | cur = cur.parentNode; 373 | closer++; 374 | } 375 | }); 376 | }, 377 | 378 | not: function( selector ) { 379 | if ( typeof selector === "string" ) 380 | // test special case where just one selector is passed in 381 | if ( isSimple.test( selector ) ) 382 | return this.pushStack( jQuery.multiFilter( selector, this, true ), "not", selector ); 383 | else 384 | selector = jQuery.multiFilter( selector, this ); 385 | 386 | var isArrayLike = selector.length && selector[selector.length - 1] !== undefined && !selector.nodeType; 387 | return this.filter(function() { 388 | return isArrayLike ? jQuery.inArray( this, selector ) < 0 : this != selector; 389 | }); 390 | }, 391 | 392 | add: function( selector ) { 393 | return this.pushStack( jQuery.unique( jQuery.merge( 394 | this.get(), 395 | typeof selector === "string" ? 396 | jQuery( selector ) : 397 | jQuery.makeArray( selector ) 398 | ))); 399 | }, 400 | 401 | is: function( selector ) { 402 | return !!selector && jQuery.multiFilter( selector, this ).length > 0; 403 | }, 404 | 405 | hasClass: function( selector ) { 406 | return !!selector && this.is( "." + selector ); 407 | }, 408 | 409 | val: function( value ) { 410 | if ( value === undefined ) { 411 | var elem = this[0]; 412 | 413 | if ( elem ) { 414 | if( jQuery.nodeName( elem, 'option' ) ) 415 | return (elem.attributes.value || {}).specified ? elem.value : elem.text; 416 | 417 | // We need to handle select boxes special 418 | if ( jQuery.nodeName( elem, "select" ) ) { 419 | var index = elem.selectedIndex, 420 | values = [], 421 | options = elem.options, 422 | one = elem.type == "select-one"; 423 | 424 | // Nothing was selected 425 | if ( index < 0 ) 426 | return null; 427 | 428 | // Loop through all the selected options 429 | for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) { 430 | var option = options[ i ]; 431 | 432 | if ( option.selected ) { 433 | // Get the specifc value for the option 434 | value = jQuery(option).val(); 435 | 436 | // We don't need an array for one selects 437 | if ( one ) 438 | return value; 439 | 440 | // Multi-Selects return an array 441 | values.push( value ); 442 | } 443 | } 444 | 445 | return values; 446 | } 447 | 448 | // Everything else, we just grab the value 449 | return (elem.value || "").replace(/\r/g, ""); 450 | 451 | } 452 | 453 | return undefined; 454 | } 455 | 456 | if ( typeof value === "number" ) 457 | value += ''; 458 | 459 | return this.each(function(){ 460 | if ( this.nodeType != 1 ) 461 | return; 462 | 463 | if ( jQuery.isArray(value) && /radio|checkbox/.test( this.type ) ) 464 | this.checked = (jQuery.inArray(this.value, value) >= 0 || 465 | jQuery.inArray(this.name, value) >= 0); 466 | 467 | else if ( jQuery.nodeName( this, "select" ) ) { 468 | var values = jQuery.makeArray(value); 469 | 470 | jQuery( "option", this ).each(function(){ 471 | this.selected = (jQuery.inArray( this.value, values ) >= 0 || 472 | jQuery.inArray( this.text, values ) >= 0); 473 | }); 474 | 475 | if ( !values.length ) 476 | this.selectedIndex = -1; 477 | 478 | } else 479 | this.value = value; 480 | }); 481 | }, 482 | 483 | html: function( value ) { 484 | return value === undefined ? 485 | (this[0] ? 486 | this[0].innerHTML.replace(/ jQuery\d+="(?:\d+|null)"/g, "") : 487 | null) : 488 | this.empty().append( value ); 489 | }, 490 | 491 | replaceWith: function( value ) { 492 | return this.after( value ).remove(); 493 | }, 494 | 495 | eq: function( i ) { 496 | return this.slice( i, +i + 1 ); 497 | }, 498 | 499 | slice: function() { 500 | return this.pushStack( Array.prototype.slice.apply( this, arguments ), 501 | "slice", Array.prototype.slice.call(arguments).join(",") ); 502 | }, 503 | 504 | map: function( callback ) { 505 | return this.pushStack( jQuery.map(this, function(elem, i){ 506 | return callback.call( elem, i, elem ); 507 | })); 508 | }, 509 | 510 | andSelf: function() { 511 | return this.add( this.prevObject ); 512 | }, 513 | 514 | domManip: function( args, table, callback ) { 515 | if ( this[0] ) { 516 | var fragment = (this[0].ownerDocument || this[0]).createDocumentFragment(), 517 | scripts = jQuery.clean( args, (this[0].ownerDocument || this[0]), fragment ), 518 | first = fragment.firstChild; 519 | 520 | if ( first ) 521 | for ( var i = 0, l = this.length; i < l; i++ ) 522 | callback.call( root(this[i], first), this.length > 1 || i > 0 ? 523 | fragment.cloneNode(true) : fragment ); 524 | 525 | if ( scripts ) 526 | jQuery.each( scripts, evalScript ); 527 | } 528 | 529 | return this; 530 | 531 | function root( elem, cur ) { 532 | return table && jQuery.nodeName(elem, "table") && jQuery.nodeName(cur, "tr") ? 533 | (elem.getElementsByTagName("tbody")[0] || 534 | elem.appendChild(elem.ownerDocument.createElement("tbody"))) : 535 | elem; 536 | } 537 | } 538 | }; 539 | 540 | // Give the init function the jQuery prototype for later instantiation 541 | jQuery.fn.init.prototype = jQuery.fn; 542 | 543 | function evalScript( i, elem ) { 544 | if ( elem.src ) 545 | jQuery.ajax({ 546 | url: elem.src, 547 | async: false, 548 | dataType: "script" 549 | }); 550 | 551 | else 552 | jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" ); 553 | 554 | if ( elem.parentNode ) 555 | elem.parentNode.removeChild( elem ); 556 | } 557 | 558 | function now(){ 559 | return +new Date; 560 | } 561 | 562 | jQuery.extend = jQuery.fn.extend = function() { 563 | // copy reference to target object 564 | var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options; 565 | 566 | // Handle a deep copy situation 567 | if ( typeof target === "boolean" ) { 568 | deep = target; 569 | target = arguments[1] || {}; 570 | // skip the boolean and the target 571 | i = 2; 572 | } 573 | 574 | // Handle case when target is a string or something (possible in deep copy) 575 | if ( typeof target !== "object" && !jQuery.isFunction(target) ) 576 | target = {}; 577 | 578 | // extend jQuery itself if only one argument is passed 579 | if ( length == i ) { 580 | target = this; 581 | --i; 582 | } 583 | 584 | for ( ; i < length; i++ ) 585 | // Only deal with non-null/undefined values 586 | if ( (options = arguments[ i ]) != null ) 587 | // Extend the base object 588 | for ( var name in options ) { 589 | var src = target[ name ], copy = options[ name ]; 590 | 591 | // Prevent never-ending loop 592 | if ( target === copy ) 593 | continue; 594 | 595 | // Recurse if we're merging object values 596 | if ( deep && copy && typeof copy === "object" && !copy.nodeType ) 597 | target[ name ] = jQuery.extend( deep, 598 | // Never move original objects, clone them 599 | src || ( copy.length != null ? [ ] : { } ) 600 | , copy ); 601 | 602 | // Don't bring in undefined values 603 | else if ( copy !== undefined ) 604 | target[ name ] = copy; 605 | 606 | } 607 | 608 | // Return the modified object 609 | return target; 610 | }; 611 | 612 | // exclude the following css properties to add px 613 | var exclude = /z-?index|font-?weight|opacity|zoom|line-?height/i, 614 | // cache defaultView 615 | defaultView = document.defaultView || {}, 616 | toString = Object.prototype.toString; 617 | 618 | jQuery.extend({ 619 | noConflict: function( deep ) { 620 | window.$ = _$; 621 | 622 | if ( deep ) 623 | window.jQuery = _jQuery; 624 | 625 | return jQuery; 626 | }, 627 | 628 | // See test/unit/core.js for details concerning isFunction. 629 | // Since version 1.3, DOM methods and functions like alert 630 | // aren't supported. They return false on IE (#2968). 631 | isFunction: function( obj ) { 632 | return toString.call(obj) === "[object Function]"; 633 | }, 634 | 635 | isArray: function( obj ) { 636 | return toString.call(obj) === "[object Array]"; 637 | }, 638 | 639 | // check if an element is in a (or is an) XML document 640 | isXMLDoc: function( elem ) { 641 | return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" || 642 | !!elem.ownerDocument && jQuery.isXMLDoc( elem.ownerDocument ); 643 | }, 644 | 645 | // Evalulates a script in a global context 646 | globalEval: function( data ) { 647 | if ( data && /\S/.test(data) ) { 648 | // Inspired by code by Andrea Giammarchi 649 | // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html 650 | var head = document.getElementsByTagName("head")[0] || document.documentElement, 651 | script = document.createElement("script"); 652 | 653 | script.type = "text/javascript"; 654 | if ( jQuery.support.scriptEval ) 655 | script.appendChild( document.createTextNode( data ) ); 656 | else 657 | script.text = data; 658 | 659 | // Use insertBefore instead of appendChild to circumvent an IE6 bug. 660 | // This arises when a base node is used (#2709). 661 | head.insertBefore( script, head.firstChild ); 662 | head.removeChild( script ); 663 | } 664 | }, 665 | 666 | nodeName: function( elem, name ) { 667 | return elem.nodeName && elem.nodeName.toUpperCase() == name.toUpperCase(); 668 | }, 669 | 670 | // args is for internal usage only 671 | each: function( object, callback, args ) { 672 | var name, i = 0, length = object.length; 673 | 674 | if ( args ) { 675 | if ( length === undefined ) { 676 | for ( name in object ) 677 | if ( callback.apply( object[ name ], args ) === false ) 678 | break; 679 | } else 680 | for ( ; i < length; ) 681 | if ( callback.apply( object[ i++ ], args ) === false ) 682 | break; 683 | 684 | // A special, fast, case for the most common use of each 685 | } else { 686 | if ( length === undefined ) { 687 | for ( name in object ) 688 | if ( callback.call( object[ name ], name, object[ name ] ) === false ) 689 | break; 690 | } else 691 | for ( var value = object[0]; 692 | i < length && callback.call( value, i, value ) !== false; value = object[++i] ){} 693 | } 694 | 695 | return object; 696 | }, 697 | 698 | prop: function( elem, value, type, i, name ) { 699 | // Handle executable functions 700 | if ( jQuery.isFunction( value ) ) 701 | value = value.call( elem, i ); 702 | 703 | // Handle passing in a number to a CSS property 704 | return typeof value === "number" && type == "curCSS" && !exclude.test( name ) ? 705 | value + "px" : 706 | value; 707 | }, 708 | 709 | className: { 710 | // internal only, use addClass("class") 711 | add: function( elem, classNames ) { 712 | jQuery.each((classNames || "").split(/\s+/), function(i, className){ 713 | if ( elem.nodeType == 1 && !jQuery.className.has( elem.className, className ) ) 714 | elem.className += (elem.className ? " " : "") + className; 715 | }); 716 | }, 717 | 718 | // internal only, use removeClass("class") 719 | remove: function( elem, classNames ) { 720 | if (elem.nodeType == 1) 721 | elem.className = classNames !== undefined ? 722 | jQuery.grep(elem.className.split(/\s+/), function(className){ 723 | return !jQuery.className.has( classNames, className ); 724 | }).join(" ") : 725 | ""; 726 | }, 727 | 728 | // internal only, use hasClass("class") 729 | has: function( elem, className ) { 730 | return elem && jQuery.inArray( className, (elem.className || elem).toString().split(/\s+/) ) > -1; 731 | } 732 | }, 733 | 734 | // A method for quickly swapping in/out CSS properties to get correct calculations 735 | swap: function( elem, options, callback ) { 736 | var old = {}; 737 | // Remember the old values, and insert the new ones 738 | for ( var name in options ) { 739 | old[ name ] = elem.style[ name ]; 740 | elem.style[ name ] = options[ name ]; 741 | } 742 | 743 | callback.call( elem ); 744 | 745 | // Revert the old values 746 | for ( var name in options ) 747 | elem.style[ name ] = old[ name ]; 748 | }, 749 | 750 | css: function( elem, name, force, extra ) { 751 | if ( name == "width" || name == "height" ) { 752 | var val, props = { position: "absolute", visibility: "hidden", display:"block" }, which = name == "width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ]; 753 | 754 | function getWH() { 755 | val = name == "width" ? elem.offsetWidth : elem.offsetHeight; 756 | 757 | if ( extra === "border" ) 758 | return; 759 | 760 | jQuery.each( which, function() { 761 | if ( !extra ) 762 | val -= parseFloat(jQuery.curCSS( elem, "padding" + this, true)) || 0; 763 | if ( extra === "margin" ) 764 | val += parseFloat(jQuery.curCSS( elem, "margin" + this, true)) || 0; 765 | else 766 | val -= parseFloat(jQuery.curCSS( elem, "border" + this + "Width", true)) || 0; 767 | }); 768 | } 769 | 770 | if ( elem.offsetWidth !== 0 ) 771 | getWH(); 772 | else 773 | jQuery.swap( elem, props, getWH ); 774 | 775 | return Math.max(0, Math.round(val)); 776 | } 777 | 778 | return jQuery.curCSS( elem, name, force ); 779 | }, 780 | 781 | curCSS: function( elem, name, force ) { 782 | var ret, style = elem.style; 783 | 784 | // We need to handle opacity special in IE 785 | if ( name == "opacity" && !jQuery.support.opacity ) { 786 | ret = jQuery.attr( style, "opacity" ); 787 | 788 | return ret == "" ? 789 | "1" : 790 | ret; 791 | } 792 | 793 | // Make sure we're using the right name for getting the float value 794 | if ( name.match( /float/i ) ) 795 | name = styleFloat; 796 | 797 | if ( !force && style && style[ name ] ) 798 | ret = style[ name ]; 799 | 800 | else if ( defaultView.getComputedStyle ) { 801 | 802 | // Only "float" is needed here 803 | if ( name.match( /float/i ) ) 804 | name = "float"; 805 | 806 | name = name.replace( /([A-Z])/g, "-$1" ).toLowerCase(); 807 | 808 | var computedStyle = defaultView.getComputedStyle( elem, null ); 809 | 810 | if ( computedStyle ) 811 | ret = computedStyle.getPropertyValue( name ); 812 | 813 | // We should always get a number back from opacity 814 | if ( name == "opacity" && ret == "" ) 815 | ret = "1"; 816 | 817 | } else if ( elem.currentStyle ) { 818 | var camelCase = name.replace(/\-(\w)/g, function(all, letter){ 819 | return letter.toUpperCase(); 820 | }); 821 | 822 | ret = elem.currentStyle[ name ] || elem.currentStyle[ camelCase ]; 823 | 824 | // From the awesome hack by Dean Edwards 825 | // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 826 | 827 | // If we're not dealing with a regular pixel number 828 | // but a number that has a weird ending, we need to convert it to pixels 829 | if ( !/^\d+(px)?$/i.test( ret ) && /^\d/.test( ret ) ) { 830 | // Remember the original values 831 | var left = style.left, rsLeft = elem.runtimeStyle.left; 832 | 833 | // Put in the new values to get a computed value out 834 | elem.runtimeStyle.left = elem.currentStyle.left; 835 | style.left = ret || 0; 836 | ret = style.pixelLeft + "px"; 837 | 838 | // Revert the changed values 839 | style.left = left; 840 | elem.runtimeStyle.left = rsLeft; 841 | } 842 | } 843 | 844 | return ret; 845 | }, 846 | 847 | clean: function( elems, context, fragment ) { 848 | context = context || document; 849 | 850 | // !context.createElement fails in IE with an error but returns typeof 'object' 851 | if ( typeof context.createElement === "undefined" ) 852 | context = context.ownerDocument || context[0] && context[0].ownerDocument || document; 853 | 854 | // If a single string is passed in and it's a single tag 855 | // just do a createElement and skip the rest 856 | if ( !fragment && elems.length === 1 && typeof elems[0] === "string" ) { 857 | var match = /^<(\w+)\s*\/?>$/.exec(elems[0]); 858 | if ( match ) 859 | return [ context.createElement( match[1] ) ]; 860 | } 861 | 862 | var ret = [], scripts = [], div = context.createElement("div"); 863 | 864 | jQuery.each(elems, function(i, elem){ 865 | if ( typeof elem === "number" ) 866 | elem += ''; 867 | 868 | if ( !elem ) 869 | return; 870 | 871 | // Convert html string into DOM nodes 872 | if ( typeof elem === "string" ) { 873 | // Fix "XHTML"-style tags in all browsers 874 | elem = elem.replace(/(<(\w+)[^>]*?)\/>/g, function(all, front, tag){ 875 | return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i) ? 876 | all : 877 | front + ">"; 878 | }); 879 | 880 | // Trim whitespace, otherwise indexOf won't work as expected 881 | var tags = elem.replace(/^\s+/, "").substring(0, 10).toLowerCase(); 882 | 883 | var wrap = 884 | // option or optgroup 885 | !tags.indexOf("", "" ] || 887 | 888 | !tags.indexOf("", "" ] || 890 | 891 | tags.match(/^<(thead|tbody|tfoot|colg|cap)/) && 892 | [ 1, "", "
" ] || 893 | 894 | !tags.indexOf("", "" ] || 896 | 897 | // matched above 898 | (!tags.indexOf("", "" ] || 900 | 901 | !tags.indexOf("", "" ] || 903 | 904 | // IE can't serialize and 71 | 72 | 73 | 74 | 75 | 76 | 77 | --------------------------------------------------------------------------------