diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..7faa7966 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +# This file is for unifying the coding style for different editors and IDEs +# editorconfig.org + +root = true + +[*] +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = tab + +[*.{json,yml}] +indent_style = space +indent_size = 2 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..b7ca95b5 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +# Auto detect text files and perform LF normalization +* text=auto + +# JS files must always use LF for tools to work +*.js eol=lf diff --git a/.gitignore b/.gitignore index c8e4ed6f..633f1bde 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,3 @@ -vendor -tmp/* -tmp -config.json -node_modules/ -dist/ -.DS_Store +/dist/ +/node_modules/ +config.js* diff --git a/.mailmap b/.mailmap new file mode 100644 index 00000000..ffb1b476 --- /dev/null +++ b/.mailmap @@ -0,0 +1,16 @@ +Addy Osmani +Clément Partiot +Corey Frang +Douglas Calhoun +Eddie Monge +Eran Weissenstern +Eugene Nikolaev +Garrett Johnson <=> +Herwin Weststrate +John K. Paul +Luís Soares +Radu Oprita +Rebecca Murphey +Scott Murphy +Steven Hauser +Youssef Boulkaid diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5013c8bb..97125d06 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,169 +1,84 @@ ---- -title: Contributing -customFields: - - - key: "is_chapter" - value: 0 ---- - -Depending on your level of experience with some of the workflows common to many -open source projects, e.g., git/Github, the command line, and setting up a -local development environment, contributing to this site may be a breeze or -come with a bit of a learning curve. If you fit into the former group, great! -Jump ahead to learn how to get started. - -But if you think you're part of the second group, and have had trouble -participating in open source because of a lack of comfort with the tools, -**you're still welcome**! Beyond providing a resource for learning jQuery, a -major goal of this site is to provide an encouraging environment for you to -develop these skills, while still making a contribution that matters. Many -people think that the only way to get involved with a programming project like -jQuery is to solve intricate bugs that require a nuanced understanding of the -codebase, or to propose enhancements that may or may not be in scope with the -development team's plans. The fact is that there's way more: improving -documentation, working on web properties, and supporting other users are -crucial aspects where more help is always needed. If you're willing to share -your time and expertise to help other developers, we're willing to [help you -get up to speed with the tools](#getting-help) you'll need. +Depending on your level of experience with some of the workflows common to many open source projects, e.g. git/GitHub, the command line, and setting up a local development environment, contributing to this site may be a breeze or come with a bit of a learning curve. If you fit into the former group, great! Jump ahead to learn how to get started. +But if you think you're part of the second group, and have had trouble participating in open source because of a lack of comfort with the tools, **you're still welcome**! Beyond providing a resource for learning jQuery, a major goal of this site is to provide an encouraging environment for you to develop these skills, while still making a contribution that matters. Many people think that the only way to get involved with a programming project like jQuery is to solve intricate bugs that require a nuanced understanding of the codebase, or to propose enhancements that may or may not be in scope with the development team's plans. The fact is that there's way more: improving documentation, working on web properties, and supporting other users are crucial aspects where more help is always needed. If you're willing to share your time and expertise to help other developers, we're willing to [help you get up to speed with the tools](#getting-help) you'll need. ## Why Contribute? -If you've ever looked for help with jQuery -- or with web development in -general -- you know the hunt can sometimes be challenging. It's can be a -process of wading through a number of different posts until you find that -article that's the right combination of trustworthy, timely, and helpful for -your particular problem. And if you're one of those authors -- thanks! -- then -you are probably familiar with the frustrating feeling of putting a useful tip -out there, and then wondering if it's actually making its way to the people who -need it, and what to do with that old post years and versions down the road. -You're invited to share that energy to help us bring that ecosystem together -and grow it further! - -If you've ever helped anyone, colleague or stranger, with a particular problem, -then you know the value of having a reference you can quickly link to that says -"here's how you do it." This site is intended to be that compendium, but -there's always more to refine and add, and we need your help too! - -Of course, we'll also give you credit for your work! The **Contributors** section -for each article is generated from the git commit logs on the file, so you'll -be publicly acknowledged for your help. +If you've ever looked for help with jQuery – or with web development in general – you know the hunt can sometimes be challenging. It can be a process of wading through a number of different posts until you find that article that's the right combination of trustworthy, timely, and helpful for your particular problem. And if you're one of those authors – thanks! – then you are probably familiar with the frustrating feeling of putting a useful tip out there, and then wondering if it's actually making its way to the people who need it, and what to do with that old post years and versions down the road. You're invited to share that energy to help us bring that ecosystem together and grow it further! + +If you've ever helped anyone, colleague or stranger, with a particular problem, then you know the value of having a reference you can quickly link to that says "here's how you do it." This site is intended to be that compendium, but there's always more to refine and add, and we need your help too! + +Of course, we'll also give you credit for your work! The **Contributors** section for each article is generated from the git commit logs on the file, so you'll be publicly acknowledged for your help. ## How Does It Work? ### Content -The content in this site is maintained in -[this GitHub repository](http://github.com/jquery/learn.jquery.com) as a collection of -[Markdown](http://daringfireball.net/projects/markdown/) files in the `page` -directory. The order in which chapters and articles are presented is controlled -by the [order.yml](https://github.com/jquery/learn.jquery.com/blob/master/order.yml) -file. +The content in this site is maintained in [this GitHub repository](https://github.com/jquery/learn.jquery.com) as a collection of [Markdown](http://daringfireball.net/projects/markdown/) files in the `page` directory. The order in which chapters and articles are presented is controlled by the [order.json](https://github.com/jquery/learn.jquery.com/blob/master/order.json) file. ### Design -The site's layout and design is controlled by our -[`jquery-wp-content`](http://github.com/jquery/jquery-wp-content), a custom -[WordPress](http://wordpress.org) configuration that runs (or will run in the -near future) all of the sites run by the jQuery Foundation. The [master -theme](https://github.com/jquery/jquery-wp-content/tree/master/themes/jquery) -controls most of the layout for all of our sites, and there is a [child -theme](https://github.com/jquery/jquery-wp-content/tree/master/themes/learn.jquery.com) -that controls the templates and styles specific to the learn site. +The site's layout and design is controlled by our [`jquery-wp-content`](https://github.com/jquery/jquery-wp-content), a custom [WordPress](http://wordpress.org) configuration that runs (or will run in the near future) all of the sites run by the jQuery Foundation. The [master theme](https://github.com/jquery/jquery-wp-content/tree/master/themes/jquery) controls most of the layout for all of our sites, and there is a [child theme](https://github.com/jquery/jquery-wp-content/tree/master/themes/learn.jquery.com) that controls the templates and styles specific to the learn site. -[`jquery-wp-content`](http://github.com/jquery/jquery-wp-content) powers our sites in -production and staging environments, and can set up for local development relatively easily. +[`jquery-wp-content`](https://github.com/jquery/jquery-wp-content) powers our sites in production and staging environments, and can be set up for local development relatively easily. ### Build -The static content in the `page` directory is deployed to a -[`jquery-wp-content`](http://github.com/jquery/jquery-wp-content) instance -using [grunt](http://gruntjs.com), specifically with two grunt plugins we've created: - -* [grunt-jquery-content](http://github.com/jquery/grunt-jquery-content) - pre-processes content in a variety of formats (HTML, Markdown, XML) into HTML, applying syntax highlighting and some simple partial support, preparing it for processing by -* [grunt-wordpress](http://github.com/scottgonzalez/grunt-wordpress) - syncs static content to WordPress using [XML-RPC](http://codex.wordpress.org/XML-RPC_Support) +The static content in the `page` directory is deployed to a [`jquery-wp-content`](https://github.com/jquery/jquery-wp-content) instance using [grunt](http://gruntjs.com), specifically with two grunt plugins we've created: +* [grunt-jquery-content](https://github.com/jquery/grunt-jquery-content) – pre-processes content in a variety of formats (HTML, Markdown, XML) into HTML, applying syntax highlighting and some simple partial support, preparing it for processing by: +* [grunt-wordpress](https://github.com/scottgonzalez/grunt-wordpress) – syncs static content to WordPress using [XML-RPC](http://codex.wordpress.org/XML-RPC_Support) ## How Can I Help? -The simplest and least complicated way to help is to [file -issues](http://github.com/jquery/learn.jquery.com/issues) if you notice -mistakes that should be fixed, improvements that can be made, or if you have -ideas for new articles. We'll use the issues to continue discussion and track -progress on anything you point out. - -If you'd like to go a step further and contribute new articles, make edits to -existing ones, or work on the site itself, the first thing you'll need is a -[fork](https://help.github.com/articles/fork-a-repo). When you have changes -you'd like to have reviewed for integration into the site, submit a [pull -request](http://help.github.com/send-pull-requests/). - -*(If you're unfamiliar with Git, you can still contribute by using features in -GitHub's web UI. You can edit files directly via [GitHub's in-browser -editor](https://github.com/blog/905-edit-like-an-ace). You can [create and -delete branches directly from your -fork](https://github.com/blog/1377-create-and-delete-branches), so you can also -submit new articles as well. Either way, we still encourage you to [learn how -to use Git and GitHub](http://help.github.com/) as soon as you can.)* +The simplest and least complicated way to help is to [file issues](https://github.com/jquery/learn.jquery.com/issues) if you notice mistakes that should be fixed, improvements that can be made, or if you have ideas for new articles. We'll use the issues to continue discussion and track progress on anything you point out. + +If you'd like to go a step further and contribute new articles, make edits to existing ones, or work on the site itself, the first thing you'll need is a [fork](https://help.github.com/articles/fork-a-repo). When you have changes you'd like to have reviewed for integration into the site, submit a [pull request](https://help.github.com/articles/using-pull-requests). + +*(If you're unfamiliar with Git, you can still contribute by using features in GitHub's web interface. You can edit files directly via [GitHub's in-browser editor](https://github.com/blog/905-edit-like-an-ace). You can [create and delete branches directly from your fork](https://github.com/blog/1377-create-and-delete-branches), so you can also submit new articles as well. Either way, we still encourage you to [learn how to use Git and GitHub](https://help.github.com/) as soon as you can.)* ## Local Development -In order to preview your changes locally, work on design/layout issues, or work -on other jQuery sites' content, and generally contribute most effectively, we -recommend that you set up a local development environment. You can learn how to -get set up from our [documentation on contributing to jQuery Foundation web -sites](http://contribute.jquery.org/web-sites/#local-development). +In order to preview your changes locally, work on design/layout issues, or work on other jQuery sites' content, and generally contribute most effectively, we recommend that you set up a local development environment. You can learn how to get set up from our [documentation on contributing to jQuery Foundation web sites](http://contribute.jquery.org/web-sites/#local-development). -* **Windows note:** Line endings need to be Unix-style (line-feed only). Make - sure your text editor creates new files with Unix-style line endings. In - addition, the following setting to your git config will keep the Unix-style - line endings when pulling from the repository.* +* **Windows note:** Line endings need to be Unix-style (line-feed only). Make sure your text editor creates new files with Unix-style line endings. In addition, the following setting to your git config will keep the Unix-style line endings when pulling from the repository: ``` $ git config --global core.autocrlf true ``` -### Working With Content +### Working with Content Once you've gotten your environment working, here are the general steps you should follow to make your changes: -1. Create a new "feature" branch based on `master` -- `git branch ` -2. Move onto that branch -- `git checkout ` -3. Work on your awesome contribution. -4. As you work and want to preview your changes, use `grunt` to deploy them to the your site. You can also use `grunt watch` to have the site monitor the `page` directory for any changes and automatically have the changes deployed every time you save. -5. When you're done, stage the new/modified preparation for commit -- `git add page/faq/how-do-i-add-a-new-article-to-the-learn-site.md` -6. Commit the files to your local repo -- `git commit -m "add a relevant message describing the change"` -7. Push the files to your GitHub remote -- `git push origin ` +1. Create a new "feature" branch based on `master`: `git branch ` +2. Move onto that branch: `git checkout ` +3. Work on your awesome contribution. +4. As you work and want to preview your changes, use `grunt` to deploy them to your site. You can also use `grunt watch` to have the site monitor the `page` directory for any changes and automatically have the changes deployed every time you save. +5. When you're done, stage the new/modified preparation for commit: `git add page/faq/how-do-i-add-a-new-article-to-the-learn-site.md` +6. Commit the files to your local repo: `git commit -m "add a relevant message describing the change"` +7. Push the files to your GitHub remote: `git push origin ` 8. Go to your fork on GitHub and submit a new [pull request](https://help.github.com/articles/using-pull-requests). -For more advice on managing your fork and submitting pull requests to jQuery -Foundation, read our [Commits and Pull -Requests](http://contribute.jquery.org/commits-and-pull-requests/) guide. +For more advice on managing your fork and submitting pull requests to the jQuery Foundation, read our [Commits and Pull Requests](http://contribute.jquery.org/commits-and-pull-requests/) guide. -### Adding A New Article +### Adding a New Article -1. Add the file to the right folder in the page folder. -2. Add the slug name (the filename without the extension) to the desired location `order.yml` +1. Add the file to the right folder in the `page` folder. +2. Add the slug name (the filename without the extension) to the desired location in `order.json` 3. Run `grunt` -4. You should now be able to navigate to the file. +4. You should now be able to navigate to the file. ### Formatting Articles -Yes! Take a look at our [style guide](http://learn.jquery.com/style-guide) for -more information on authoring and formatting conventions. - -## How Will My Contribution Be Acknowledged? - -We will build the attribution of an article based on the git commit logs and present this information in the site. +Yes! Take a look at our [style guide](http://learn.jquery.com/style-guide/) for more information on authoring and formatting conventions. -## Getting Help +

Getting Help

If you're struggling to get any part of the site working properly, or have any questions, we're here to help. -The best place to get help is on [IRC](http://en.wikipedia.org/wiki/Internet_Relay_Chat), in the #jquery-content -channel on [Freenode](http://freenode.net). If you're unfamiliar with IRC, you can use the [webchat gateway](http://webchat.freenode.net/). +The best place to get help is on [IRC](http://en.wikipedia.org/wiki/Internet_Relay_Chat), in the `#jquery-content` channel on [Freenode](http://freenode.net). If you're unfamiliar with IRC, you can use the [webchat gateway](http://webchat.freenode.net/). -In addition, the jQuery Content Team holds a [public, weekly -meetings](http://jquery.org/meeting) on Freenode, at 1PM Eastern time in the #jquery-meeting channel. +In addition, the jQuery Content Team holds a [public, biweekly meeting](http://jquery.org/meeting) on Wednesday, at 1PM Eastern time in the `#jquery-meeting` channel on Freenode. -If IRC is not your thing, but you still want or need to get in touch, please use the site's GitHub repo or send us an e-mail to `content at jquery dot org`. +If IRC is not your thing, but you still want or need to get in touch, please use the site's [GitHub repo](https://github.com/jquery/learn.jquery.com) or send us an e-mail to `content at jquery dot org`. diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 00000000..bc7c615b --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,61 @@ +var jqueryContent = require( "grunt-jquery-content" ); + +module.exports = function( grunt ) { + +grunt.loadNpmTasks( "grunt-jquery-content" ); + +grunt.initConfig({ + "build-posts": { + page: "page/**" + }, + "build-resources": { + all: "resources/**" + }, + wordpress: (function() { + var config = require( "./config" ); + config.dir = "dist/wordpress"; + return config; + })() +}); + +function getOrderMap() { + var map = {}, + index = 0; + + function walk( items, prefix ) { + items.forEach(function( item ) { + if ( typeof item === "object" ) { + var page = Object.keys( item )[ 0 ]; + map[ prefix + page ] = ++index; + walk( item[ page ], prefix + page + "/" ); + } else { + map[ prefix + item ] = ++index; + } + }); + } + + walk( require( "./order" ), "" ); + + return map; +} + +jqueryContent.postPreprocessors.page = (function() { + var orderMap = getOrderMap(); + + return function( post, postPath, callback ) { + var slug = postPath.replace( /^.+?\/(.+)\.\w+$/, "$1" ), + menuOrder = orderMap[ slug ]; + + if ( menuOrder ) { + post.menuOrder = menuOrder; + } + + callback( null, post ); + }; +})(); + +grunt.registerTask( "build", [ "build-posts", "build-resources" ] ); + +grunt.registerTask( "deploy", [ "wordpress-deploy", "deploy-redirects" ] ); + +}; diff --git a/MIT-LICENSE.txt b/LICENSE.txt similarity index 56% rename from MIT-LICENSE.txt rename to LICENSE.txt index 957f26d3..5bbeec51 100644 --- a/MIT-LICENSE.txt +++ b/LICENSE.txt @@ -1,5 +1,13 @@ -Copyright 2013 jQuery Foundation and other contributors -http://jquery.com/ +Copyright jQuery Foundation and other contributors, https://jquery.org/ + +This software consists of voluntary contributions made by many +individuals. For exact contribution history, see the revision history +available at https://github.com/jquery/learn.jquery.com + +The following license applies to all parts of this software except as +documented below: + +==== Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -19,3 +27,18 @@ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +==== + +Copyright and related rights for sample code are waived via CC0. Sample +code is defined as all source code displayed within the prose of the +documentation. + +CC0: http://creativecommons.org/publicdomain/zero/1.0/ + +==== + +All files located in the node_modules directory are externally +maintained libraries used by this software which have their own +licenses; we recommend you read them, as their terms may differ from the +terms above. diff --git a/README.md b/README.md old mode 100755 new mode 100644 index 1c4351af..cbfec1e1 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # The jQuery Learning Site -* Primary Domain: [http://learn.jquery.com](http://learn.jquery.com) -* Staging Domain: [http://stage.learn.jquery.com](http://stage.learn.jquery.com) +This site aims to be an authoritative educational resource for those wishing to learn how to use jQuery. From beginners, to more advanced users. It's being driven by the jQuery community. ## About @@ -10,47 +9,38 @@ The goal of this site is twofold: 1. To serve as a central, trustworthy, narrative compendium of information about how to use jQuery and JavaScript. 2. To remain a timely, vibrant, and community-driven reference with a relatively low barrier to contribution. -Much of the initial content - and spirit - comes from [jQuery Fundamentals](https://github.com/rmurphey/jqfundamentals), an open-source book about jQuery, originally written by [Rebecca Murphey](http://www.rmurphey.com/) and released in 2010. In 2011, Rebecca [bequeathed the book](http://rmurphey.com/blog/2011/03/17/the-future-of-jquery-fundamentals-and-a-confession/) unto the jQuery Foundation to serve as the basis for this site. +Much of the initial content - and spirit - comes from [jQuery Fundamentals](http://jqfundamentals.com/legacy), an open-source book about jQuery, originally written by [Rebecca Murphey](http://www.rmurphey.com/) and released in 2010. In 2011, Rebecca [bequeathed the book](http://rmurphey.com/blog/2011/03/17/the-future-of-jquery-fundamentals-and-a-confession/) unto the jQuery Foundation to serve as the basis for this site. The book was split up into individual sections, whch we iterated on and improved over several months. Early on we wanted to ensure we covered as many relevant topics as possible and reached out to writers in the community to give us permission to integrate some of their blog posts as well. ## How This Site Works -This site's core content consists of [Markdown](http://daringfireball.net/projects/markdown/) files. The template that controls the site's appearance is a [child theme](https://github.com/jquery/jquery-wp-content/tree/master/themes/learn.jquery.com) of the jQuery [web base template](https://github.com/jquery/jquery-wp-content), and any issues with the presentation should be directed to [that repository](https://github.com/jquery/jquery-wp-content). +This site's core content consists of [Markdown](http://daringfireball.net/projects/markdown/) files. The template that controls the site's appearance is a [child theme](https://github.com/jquery/jquery-wp-content/tree/master/themes/learn.jquery.com) of [jquery-wp-content](https://github.com/jquery/jquery-wp-content), and any issues with the presentation should be directed to [that repository](https://github.com/jquery/jquery-wp-content). + ### Site Organization All of the content lives inside of the subdirectories of the `page` directory. Each of these subdirectories is considered a **chapter**, and contains one or more **articles**, and there is also a top level file that corresponds to each chapter, which contains the chapter's human-readable title and an overview, which will appear on the chapter's landing page. -The [`order.yml`](https://github.com/jquery/learn.jquery.com/blob/master/order.yml) file controls the order that chapters and articles appear in the site. - - -### YAML Conventions +The [`order.json`](https://github.com/jquery/learn.jquery.com/blob/master/order.json) file controls the order that chapters and articles appear in the site. -Each of the articles on the site has some [YAML "Front Matter"](https://github.com/mojombo/jekyll/wiki/YAML-Front-Matter) that contains metadata. All articles should include the following: -* title - the title of the article as it will appear in the site. if it contains special characters, put the string in quotes +### Front Matter -`title: "jQuery Event Extensions"` +Each of the articles on the site has some JSON "Front Matter" that contains metadata. All articles should include the following: -* level - the approximate level of jQuery experience required to find the article useful. Options: `beginner`, `intermediate`, or `advanced` +* `title` - The title of the article as it will appear in the site. -`level: advanced` +`"title": "jQuery Event Extensions"` -## Building && Working Locally +* `level` - The approximate level of jQuery experience required to find the article useful. Options: `beginner`, `intermediate`, or `advanced`. -As this site is part of the jQuery network of sites, its presentation is controlled by our [web base template](https://github.com/jquery/jquery-wp-content). To preview the site locally, first follow the [instructions there](https://github.com/jquery/jquery-wp-content) to set up a local version of the jQuery WordPress network. Then, clone this repo and run the following steps (node.js required). +`"level": "advanced"` -1. `npm install` -2. `cp config-sample.json config.json` -3. Edit config.json to use the username and password for your local WordPress network -4. `grunt` +## Building and Deploying -*Windows note: Line endings need to be Unix-style (line-feed only). Make sure your text editor creates new files with Unix-style line endings. In addition, the following setting to your git config will keep the Unix-style line endings when pulling from the repository.* +To build and deploy your changes for previewing in a [`jquery-wp-content`](https://github.com/jquery/jquery-wp-content) instance, follow the [workflow instructions](http://contribute.jquery.org/web-sites/#workflow) from our documentation on [contributing to jQuery Foundation web sites](http://contribute.jquery.org/web-sites/). -``` -$ git config --global core.autocrlf true -``` ## How Can I Help? diff --git a/config-sample.json b/config-sample.json deleted file mode 100644 index 5a51f916..00000000 --- a/config-sample.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "url": "local.learn.jquery.com", - "username": "admin", - "password": "sample" -} diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 00000000..dcf8f70f --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,18 @@ +const jqueryConfig = require( "eslint-config-jquery" ); +const globals = require( "globals" ); + +module.exports = [ + { + ignores: [ "dist" ] + }, + + { + files: [ "eslint.config.js", "Gruntfile.js" ], + languageOptions: { + globals: { + ...globals.node + } + }, + rules: jqueryConfig.rules + } +]; diff --git a/grunt.js b/grunt.js deleted file mode 100644 index 31eda56c..00000000 --- a/grunt.js +++ /dev/null @@ -1,199 +0,0 @@ -var yaml = require( "js-yaml" ), - config = require("./config.json"); - -module.exports = function( grunt ) { - -"use strict"; - -grunt.loadNpmTasks( "grunt-clean" ); -grunt.loadNpmTasks( "grunt-html" ); -grunt.loadNpmTasks( "grunt-wordpress" ); -grunt.loadNpmTasks( "grunt-jquery-content" ); -grunt.loadNpmTasks( "grunt-check-modules" ); - -grunt.initConfig({ - clean: { - wordpress: "dist/" - }, - htmllint: { - resources: "resources/*.html" - }, - jshint: { - options: { - undef: true, - node: true - } - }, - lint: { - grunt: "grunt.js" - }, - watch: { - pages: { - files: "page/**", - tasks: "deploy" - } - }, - "build-pages": { - all: grunt.file.expandFiles( "page/**" ) - }, - "build-resources": { - all: grunt.file.expandFiles( "resources/**/*" ) - }, - wordpress: grunt.utils._.extend({ - dir: "dist/wordpress", - order: "order.yml" - }, grunt.file.readJSON( "config.json" ) ) -}); - - - -// Process a YAML order file and return an object of page slugs and their ordinal indices -grunt.registerHelper( "read-order", function( orderFile ) { - var order, - map = {}, - index = 0; - - try { - order = yaml.load( grunt.file.read( orderFile ) ); - } catch( error ) { - grunt.warn( "Invalid order file: " + orderFile ); - return null; - } - - - function flatten( item, folder ) { - var title, - path = folder ? [ folder ] : []; - - if ( grunt.utils._.isObject( item ) ) { - title = Object.keys( item )[ 0 ]; - path.push( title ); - path = path.join( "/" ); - map[ path ] = ++index; - - item[ title ].forEach(function( item ) { - flatten( item, path ); - }); - } else { - path.push( item ); - map[ path.join( "/" ) ] = ++index; - } - } - order.forEach(function( item ) { - flatten( item ); - }); - return map; -}); - -grunt.registerHelper( "contributor-attribution", function( post, fileName, fn ) { - var contribs = [], - _ = grunt.utils._, - parseRE = /^(.*)<(.*)>$/; // could certainly be better. - - // Read contributors from git file information - grunt.utils.spawn({ - cmd: "git", - args: [ "log", "--format=%aN <%aE>", fileName ] - }, function( err, result ) { - if ( err ) { - grunt.verbose.error(); - grunt.log.error( err ); - return; - } - // make unique. - contribs = _.uniq( result.stdout.split( /\r?\n/g ) ); - - // make object { name: 'name', email: 'email@address.com' } - contribs.forEach(function(str, idx) { - var m = parseRE.exec(str); - if ( m ) { - contribs[idx] = { name: m[1].trim(), email: m[2] }; - } - else { - contribs[idx] = { name: str }; - } - }); - - // Alphabetize by 'last name' (relatively crude) - contribs = _.sortBy( contribs, function(a) { - return a.name.split(' ').pop().toLowerCase(); - }); - - // Handle "legacy" content - content authored outside of the learn site - // and attributed with metadata in the file, - // push those contributors to the front of the list - if ( post.attribution ) { - post.attribution.forEach(function(str, idx) { - var contrib, m; - - // Handling specifically for articles originally from jQuery Fundamentals - if (str == "jQuery Fundamentals") { - contribs.unshift({ - name: str, - // Use the jQuery Gravatar - email: "github@jquery.com", - source: post.source - }); - } else { - m = parseRE.exec(str); - if ( m ) { - contrib = { name: m[1].trim(), email: m[2] }; - } - else { - contrib = { name: str }; - } - if ( post.source ) { - contrib.source = post.source; - } - contribs.unshift( contrib ); - } - }); - } - - if ( post.customFields ) { - post.customFields.push({ - key: "contributors", - value: JSON.stringify( contribs ) - }); - - } else { - post.customFields = [{ - key: "contributors", - value: JSON.stringify( contribs ) - }]; - } - - fn(); - }); - -}); - -grunt.registerHelper( "build-pages-preprocess", (function() { - var orderMap = grunt.helper( "read-order", "order.yml" ); - - return function( post, fileName, done ) { - grunt.utils.async.series([ - function applyOrder( fn ) { - var slug = fileName.replace( /^.+?\/(.+)\.\w+$/, "$1" ), - menuOrder = orderMap[ slug ]; - if ( menuOrder ) { - post.menuOrder = menuOrder; - } - fn(); - }, - - function applyContribs( fn ) { - grunt.helper( "contributor-attribution", post, fileName, fn ); - } - ], done ); - }; -})()); - -grunt.registerTask( "default", "wordpress-deploy" ); -grunt.registerTask( "build-wordpress", "check-modules clean lint build-pages build-resources"); -grunt.registerTask( "deploy", "wordpress-deploy" ); - -}; - - - diff --git a/order.json b/order.json new file mode 100644 index 00000000..2b38e2c8 --- /dev/null +++ b/order.json @@ -0,0 +1,137 @@ +[ + "index", + "about", + "contributing", + { + "about-jquery": [ + "how-jquery-works", + "additional-support" + ] + }, + { + "using-jquery-core": [ + "dollar-object-vs-function", + "document-ready", + "avoid-conflicts-other-libraries", + "attributes", + "selecting-elements", + "working-with-selections", + "manipulating-elements", + "jquery-object", + "traversing", + "css-styling-dimensions", + "data-methods", + "utility-methods", + "iterating", + "understanding-index", + { + "faq": [ + "how-do-i-select-an-item-using-class-or-id", + "how-do-i-select-elements-when-i-already-have-a-dom-element", + "how-do-i-test-whether-an-element-has-a-particular-class", + "how-do-i-test-whether-an-element-exists", + "how-do-i-determine-the-state-of-a-toggled-element", + "how-do-i-select-an-element-by-an-id-that-has-characters-used-in-css-notation", + "how-do-i-disable-enable-a-form-element", + "how-do-i-check-uncheck-a-checkbox-input-or-radio-button", + "how-do-i-get-the-text-value-of-a-selected-option", + "how-do-i-replace-text-from-the-3rd-element-of-a-list-of-10-items", + "how-do-i-pull-a-native-dom-element-from-a-jquery-object" + ] + } + ] + }, + { + "events": [ + "event-basics", + "event-helpers", + "introduction-to-events", + "handling-events", + "inside-event-handling-function", + "event-delegation", + "triggering-event-handlers", + "history-of-events", + "introduction-to-custom-events", + "event-extensions" + ] + }, + { + "effects": [ + "intro-to-effects", + "custom-effects", + "queue-and-dequeue-explained" + ] + }, + { + "ajax": [ + "key-concepts", + "jquery-ajax-methods", + "ajax-and-forms", + "working-with-jsonp", + "ajax-events" + ] + }, + { + "plugins": [ + "finding-evaluating-plugins", + "basic-plugin-creation", + "publishing-plugins", + "advanced-plugin-concepts" + ] + }, + { + "performance": [ + "append-outside-loop", + "cache-loop-length", + "detach-elements-before-work-with-them", + "dont-act-on-absent-elements", + "optimize-selectors", + "use-stylesheets-for-changing-css", + "read-the-source" + ] + }, + { + "code-organization": [ + "concepts", + "beware-anonymous-functions", + "dont-repeat-yourself", + "feature-browser-detection", + { + "deferreds": [ + "about-deferreds", + "jquery-deferreds", + "examples" + ] + } + ] + }, + { + "jquery-ui": [ + "getting-started", + "how-jquery-ui-works", + "theming", + "themeroller", + { + "widget-factory": [ + "why-use-the-widget-factory", + "how-to-use-the-widget-factory", + "widget-method-invocation", + "extending-widgets", + "classes-option" + ] + }, + { + "environments": [ + "amd", + "bower" + ] + } + ] + }, + { + "jquery-mobile": [ + "getting-started", + "theme-roller" + ] + } +] diff --git a/order.yml b/order.yml deleted file mode 100644 index f75492df..00000000 --- a/order.yml +++ /dev/null @@ -1,105 +0,0 @@ -- index -- about -- contributing -- about-jquery: - - how-jquery-works - - additional-support -- javascript-101: - - getting-started - - running-code - - syntax-basics - - types - - operators - - conditional-code - - loops - - reserved-words - - arrays - - objects - - functions - - testing-type - - this-keyword - - scope - - closures -- using-jquery-core: - - dollar-object-vs-function - - document-ready - - avoid-conflicts-other-libraries - - attributes - - selecting-elements - - working-with-selections - - manipulating-elements - - jquery-object - - traversing - - css-styling-dimensions - - data-methods - - utility-methods - - iterating - - understanding-index - - faq: - - how-do-i-select-an-item-using-class-or-id - - how-do-i-select-elements-when-i-already-have-a-dom-element - - how-do-i-test-whether-an-element-has-a-particular-class - - how-do-i-test-whether-an-element-exists - - how-do-i-determine-the-state-of-a-toggled-element - - how-do-i-select-an-element-by-an-id-that-has-characters-used-in-css-notation - - how-do-i-disable-enable-a-form-element - - how-do-i-check-uncheck-a-checkbox-input-or-radio-button - - how-do-i-get-the-text-value-of-a-selected-option - - how-do-i-replace-text-from-the-3rd-element-of-a-list-of-10-items - - how-do-i-pull-a-native-dom-element-from-a-jquery-object -- events: - - introduction-to-events - - handling-events - - inside-event-handling-function - - event-delegation - - triggering-event-handlers - - history-of-events - - introduction-to-custom-events - - event-extensions -- effects: - - intro-to-effects - - custom-effects - - queue-and-dequeue-explained - - uses-of-queue-and-dequeue -- ajax: - - key-concepts - - jquery-ajax-methods - - ajax-and-forms - - working-with-jsonp - - ajax-events -- plugins: - - finding-evaluating-plugins - - basic-plugin-creation - - advanced-plugin-concepts - - stateful-plugins-with-widget-factory -- performance: - - append-outside-loop - - cache-loop-length - - detach-elements-before-work-with-them - - dont-act-on-absent-elements - - optimize-selectors - - use-stylesheets-for-changing-css - - variable-definition - - read-the-source -- code-organization: - - concepts - - beware-anonymous-functions - - dont-repeat-yourself - - feature-browser-detection - - deferreds: - - about-deferreds - - jquery-deferreds - - examples -- jquery-ui: - - getting-started - - how-jquery-ui-works - - theming: - - themeroller - - api - - write-a-theme - - widget-factory: - - why-use-the-widget-factory - - how-to-use-the-widget-factory -- jquery-mobile: - - getting-started - - theme-roller diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..8871b1ed --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2249 @@ +{ + "name": "learn.jquery.com", + "version": "0.6.31", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "learn.jquery.com", + "version": "0.6.31", + "dependencies": { + "grunt": "1.6.1", + "grunt-jquery-content": "3.3.1" + }, + "devDependencies": { + "eslint": "^9.0.0", + "eslint-config-jquery": "^3.0.2", + "globals": "^15.0.0" + } + }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.0.2.tgz", + "integrity": "sha512-wV19ZEGEMAC1eHgrS7UQPqsdEiCIbTKTasEfcXAigzoXICcqZSjBZEHlZwNVvKg6UBCjSlos84XiLqsRJnIcIg==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.0.0.tgz", + "integrity": "sha512-RThY/MnKrhubF6+s1JflwUjPEsnCEmYCWwqa/aRISKWNXGZ9epUwft4bUMM35SdKF9xvBrLydAM1RDHd1Z//ZQ==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.12.3.tgz", + "integrity": "sha512-jsNnTBlMWuTpDkeE3on7+dWJi0D6fdDfeANj/w7MpS8ztROCoLvIO2nG0CcFj+E4k8j4QrSTh4Oryi3i2G669g==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "dev": true + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, + "node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/array-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", + "integrity": "sha512-zHjL5SZa68hkKHBFBK6DJCTtr9sfTCPCaph/L7tMSLcTFgy+zX7E+6q5UArbtOtMBCtxdICpfTCspRse+ywyXA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-slice": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", + "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/async": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", + "integrity": "sha512-l6ToIJIotphWahxxHyzK9bnLR6kM4jJIIgLShZeqLY7iboHoGkdgFl7W2/Ivi4SkMJYGKqW8vSuk0uKUj6qsSw==" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cheerio": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", + "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==", + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "htmlparser2": "^8.0.1", + "parse5": "^7.0.0", + "parse5-htmlparser2-tree-adapter": "^7.0.0" + }, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "dependencies": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/colors": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", + "integrity": "sha512-ENwblkFQpqqia6b++zLD/KUWafYlVY/UNnAp7oz7LY7E924wmpye416wBOmvv/HMWzl8gL1kJlfvId/1Dg176w==", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-spawn/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/dateformat": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", + "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", + "engines": { + "node": "*" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/detect-file": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", + "integrity": "sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.0.0.tgz", + "integrity": "sha512-IMryZ5SudxzQvuod6rUdIUz29qFItWx281VhtFVc2Psy/ZhlCeD/5DT6lBIJ4H3G+iamGJoTln1v+QSuPw0p7Q==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^3.0.2", + "@eslint/js": "9.0.0", + "@humanwhocodes/config-array": "^0.12.3", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.0.1", + "eslint-visitor-keys": "^4.0.0", + "espree": "^10.0.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-jquery": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/eslint-config-jquery/-/eslint-config-jquery-3.0.2.tgz", + "integrity": "sha512-1CdP7AY5ZuhDGUXz+/b7FwhRnDoK0A1swz+2nZ+zpEYJ3EyV085AOAfpFJL2s+ioHDspNQEsGSsl9uUEm9/f/g==", + "dev": true + }, + "node_modules/eslint-scope": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.1.tgz", + "integrity": "sha512-pL8XjgP4ZOmmwfFE8mEhSxA7ZY4C+LWyqjQ3o4yWkkmD0qcMT9kkW3zWHOczhWcjTSgqycYAgwSlXvZltv65og==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", + "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.0.1.tgz", + "integrity": "sha512-MWkrWZbJsL2UwnjxTX3gG8FneachS/Mwg7tdGXce011sJd5b0JG54vat5KHnfSBODZ3Wvzd2WnjxyzsRoVv+ww==", + "dev": true, + "dependencies": { + "acorn": "^8.11.3", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eventemitter2": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz", + "integrity": "sha512-K7J4xq5xAD5jHsGM5ReWXRTFa3JRGofHiMcVgQ8PRwgWxzjHpMWCIzsmyf60+mh8KLsqYPcjUMa0AC4hd6lPyQ==" + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==", + "dependencies": { + "homedir-polyfill": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/findup-sync": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-5.0.0.tgz", + "integrity": "sha512-MzwXju70AuyflbgeOhzvQWAvvQdo1XL0A9bVvlXsYcFEBM87WR4OakL4OfZq+QRmr+duJubio+UtNQCPsVESzQ==", + "dependencies": { + "detect-file": "^1.0.0", + "is-glob": "^4.0.3", + "micromatch": "^4.0.4", + "resolve-dir": "^1.0.1" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/fined": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fined/-/fined-1.2.0.tgz", + "integrity": "sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng==", + "dependencies": { + "expand-tilde": "^2.0.2", + "is-plain-object": "^2.0.3", + "object.defaults": "^1.1.0", + "object.pick": "^1.2.0", + "parse-filepath": "^1.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/flagged-respawn": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz", + "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true + }, + "node_modules/for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/for-own": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", + "integrity": "sha512-0OABksIGrxKK8K4kynWkQ7y1zounQxP+CWnyclVwj81KW3vlLlGUx57DKGcP/LH216GzqnstnPocF16Nxs0Ycg==", + "dependencies": { + "for-in": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/getobject": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/getobject/-/getobject-1.0.2.tgz", + "integrity": "sha512-2zblDBaFcb3rB4rF77XVnuINOE2h2k/OnqXAiy0IrTxUfV1iFp3la33oAQVY9pCpWU268WFYVt2t71hlMuLsOg==", + "engines": { + "node": ">=10" + } + }, + "node_modules/gilded-wordpress": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/gilded-wordpress/-/gilded-wordpress-1.0.7.tgz", + "integrity": "sha512-w8g4jfs1TWywX2hZ4+LlzQoz2z/JRX/8S6OgelD3IUsNnGHxXQ1FgExoIqomwZVPAmxYs0vEu2BeA1Y4KciZlw==", + "dependencies": { + "async": "^0.9.0", + "wordpress": "^1.4.2" + } + }, + "node_modules/glob": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/global-modules": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "dependencies": { + "global-prefix": "^1.0.1", + "is-windows": "^1.0.1", + "resolve-dir": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/global-prefix": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", + "integrity": "sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==", + "dependencies": { + "expand-tilde": "^2.0.2", + "homedir-polyfill": "^1.0.1", + "ini": "^1.3.4", + "is-windows": "^1.0.1", + "which": "^1.2.14" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/global-prefix/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "node_modules/global-prefix/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/globals": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.0.0.tgz", + "integrity": "sha512-m/C/yR4mjO6pXDTm9/R/SpYTAIyaUB4EOzcaaMEl7mds7Mshct9GfejiJNQGjHHbdMPey13Kpu4TMbYi9ex1pw==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/grunt": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/grunt/-/grunt-1.6.1.tgz", + "integrity": "sha512-/ABUy3gYWu5iBmrUSRBP97JLpQUm0GgVveDCp6t3yRNIoltIYw7rEj3g5y1o2PGPR2vfTRGa7WC/LZHLTXnEzA==", + "dependencies": { + "dateformat": "~4.6.2", + "eventemitter2": "~0.4.13", + "exit": "~0.1.2", + "findup-sync": "~5.0.0", + "glob": "~7.1.6", + "grunt-cli": "~1.4.3", + "grunt-known-options": "~2.0.0", + "grunt-legacy-log": "~3.0.0", + "grunt-legacy-util": "~2.0.1", + "iconv-lite": "~0.6.3", + "js-yaml": "~3.14.0", + "minimatch": "~3.0.4", + "nopt": "~3.0.6" + }, + "bin": { + "grunt": "bin/grunt" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/grunt-check-modules": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/grunt-check-modules/-/grunt-check-modules-1.1.0.tgz", + "integrity": "sha512-B0dWhmnqEZztfDbqEb5UWAI/9o0mmlRPEyJYdpitgr/W7i1893cq4hmN+Vw+xJEC/+A5vzmaaHfDZU3fO8JheQ==", + "engines": { + "node": ">=0.6.0" + }, + "peerDependencies": { + "grunt": ">=0.4.0" + } + }, + "node_modules/grunt-cli": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/grunt-cli/-/grunt-cli-1.4.3.tgz", + "integrity": "sha512-9Dtx/AhVeB4LYzsViCjUQkd0Kw0McN2gYpdmGYKtE2a5Yt7v1Q+HYZVWhqXc/kGnxlMtqKDxSwotiGeFmkrCoQ==", + "dependencies": { + "grunt-known-options": "~2.0.0", + "interpret": "~1.1.0", + "liftup": "~3.0.1", + "nopt": "~4.0.1", + "v8flags": "~3.2.0" + }, + "bin": { + "grunt": "bin/grunt" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/grunt-cli/node_modules/nopt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", + "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", + "dependencies": { + "abbrev": "1", + "osenv": "^0.1.4" + }, + "bin": { + "nopt": "bin/nopt.js" + } + }, + "node_modules/grunt-jquery-content": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/grunt-jquery-content/-/grunt-jquery-content-3.3.1.tgz", + "integrity": "sha512-aK4DdMYkM+qjSyXkEswvlSTHqcWDfvKKjWveeDLK/dIM1Ok7iXGd1SwFuFp2ShO+rx9XEHToX3KC5+Gn2BwAug==", + "dependencies": { + "cheerio": "^1.0.0-rc.12", + "gilded-wordpress": "1.0.7", + "grunt-check-modules": "^1.1.0", + "he": "^1.2.0", + "highlight.js": "^10.7.2", + "marked": "^4.0.0", + "which": "^4.0.0", + "wordpress": "^1.4.1" + } + }, + "node_modules/grunt-known-options": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/grunt-known-options/-/grunt-known-options-2.0.0.tgz", + "integrity": "sha512-GD7cTz0I4SAede1/+pAbmJRG44zFLPipVtdL9o3vqx9IEyb7b4/Y3s7r6ofI3CchR5GvYJ+8buCSioDv5dQLiA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/grunt-legacy-log": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-3.0.0.tgz", + "integrity": "sha512-GHZQzZmhyq0u3hr7aHW4qUH0xDzwp2YXldLPZTCjlOeGscAOWWPftZG3XioW8MasGp+OBRIu39LFx14SLjXRcA==", + "dependencies": { + "colors": "~1.1.2", + "grunt-legacy-log-utils": "~2.1.0", + "hooker": "~0.2.3", + "lodash": "~4.17.19" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/grunt-legacy-log-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-2.1.0.tgz", + "integrity": "sha512-lwquaPXJtKQk0rUM1IQAop5noEpwFqOXasVoedLeNzaibf/OPWjKYvvdqnEHNmU+0T0CaReAXIbGo747ZD+Aaw==", + "dependencies": { + "chalk": "~4.1.0", + "lodash": "~4.17.19" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/grunt-legacy-util": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-2.0.1.tgz", + "integrity": "sha512-2bQiD4fzXqX8rhNdXkAywCadeqiPiay0oQny77wA2F3WF4grPJXCvAcyoWUJV+po/b15glGkxuSiQCK299UC2w==", + "dependencies": { + "async": "~3.2.0", + "exit": "~0.1.2", + "getobject": "~1.0.0", + "hooker": "~0.2.3", + "lodash": "~4.17.21", + "underscore.string": "~3.3.5", + "which": "~2.0.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/grunt-legacy-util/node_modules/async": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==" + }, + "node_modules/grunt-legacy-util/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "node_modules/grunt-legacy-util/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "bin": { + "he": "bin/he" + } + }, + "node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "engines": { + "node": "*" + } + }, + "node_modules/homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "dependencies": { + "parse-passwd": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/hooker": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz", + "integrity": "sha512-t+UerCsQviSymAInD01Pw+Dn/usmz1sRO+3Zk1+lx8eg+WKpD2ulcwWqHHL0+aseRBr+3+vIhiG1K1JTwaIcTA==", + "engines": { + "node": "*" + } + }, + "node_modules/htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + }, + "node_modules/interpret": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz", + "integrity": "sha512-CLM8SNMDu7C5psFCn6Wg/tgpj/bKAg7hc2gWqcuR9OD5Ft9PhBpIu8PLicPeis+xDd6YX2ncI8MCA64I9tftIA==" + }, + "node_modules/is-absolute": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", + "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", + "dependencies": { + "is-relative": "^1.0.0", + "is-windows": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-relative": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", + "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", + "dependencies": { + "is-unc-path": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-unc-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", + "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", + "dependencies": { + "unc-path-regex": "^0.1.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "engines": { + "node": ">=16" + } + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/liftup": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/liftup/-/liftup-3.0.1.tgz", + "integrity": "sha512-yRHaiQDizWSzoXk3APcA71eOI/UuhEkNN9DiW2Tt44mhYzX4joFoCZlxsSOF7RyeLlfqzFLQI1ngFq3ggMPhOw==", + "dependencies": { + "extend": "^3.0.2", + "findup-sync": "^4.0.0", + "fined": "^1.2.0", + "flagged-respawn": "^1.0.1", + "is-plain-object": "^2.0.4", + "object.map": "^1.0.1", + "rechoir": "^0.7.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/liftup/node_modules/findup-sync": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-4.0.0.tgz", + "integrity": "sha512-6jvvn/12IC4quLBL1KNokxC7wWTvYncaVUYSoxWw7YykPLuRrnv4qdHcSOywOI5RpkOVGeQRtWM8/q+G6W6qfQ==", + "dependencies": { + "detect-file": "^1.0.0", + "is-glob": "^4.0.0", + "micromatch": "^4.0.2", + "resolve-dir": "^1.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/make-iterator": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", + "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/marked": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", + "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/nopt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha512-4GUt3kSEYmk4ITxzB/b9vaIDfUVWN/Ml1Fwl11IlnIG2iaJ9O6WXZ9SrYM9NLI8OCBieN2Y8SWC2oJV0RQ7qYg==", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/object.defaults": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", + "integrity": "sha512-c/K0mw/F11k4dEUBMW8naXUuBuhxRCfG7W+yFy8EcijU/rSmazOUd1XAEEe6bC0OuXY4HUKjTJv7xbxIMqdxrA==", + "dependencies": { + "array-each": "^1.0.1", + "array-slice": "^1.0.0", + "for-own": "^1.0.0", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz", + "integrity": "sha512-3+mAJu2PLfnSVGHwIWubpOFLscJANBKuB/6A4CxBstc4aqwQY0FWcsppuy4jU5GSB95yES5JHSI+33AWuS4k6w==", + "dependencies": { + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "dependencies": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-filepath": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", + "integrity": "sha512-FwdRXKCohSVeXqwtYonZTXtbGJKrn+HNyWDYVcp5yuJlesTwNH4rsmRZ+GrKAPJ5bLpRxESMeS+Rl0VCHRvB2Q==", + "dependencies": { + "is-absolute": "^1.0.0", + "map-cache": "^0.2.0", + "path-root": "^0.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz", + "integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==", + "dependencies": { + "domhandler": "^5.0.2", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "node_modules/path-root": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", + "integrity": "sha512-QLcPegTHF11axjfojBIoDygmS2E3Lf+8+jI6wOVmNVenrKSo3mFdSGiIgdSHenczw3wPtlVMQaFVwGmM7BJdtg==", + "dependencies": { + "path-root-regex": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-root-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", + "integrity": "sha512-4GlJ6rZDhQZFE0DPVKh0e9jmZ5egZfxTkp7bcRDuPlJXbAwhxcl2dINPUAsjLdejqaLsCeg8axcLjIbvBjN4pQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/rechoir": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz", + "integrity": "sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg==", + "dependencies": { + "resolve": "^1.9.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-dir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", + "integrity": "sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==", + "dependencies": { + "expand-tilde": "^2.0.0", + "global-modules": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/unc-path-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", + "integrity": "sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/underscore.string": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.3.6.tgz", + "integrity": "sha512-VoC83HWXmCrF6rgkyxS9GHv8W9Q5nhMKho+OadDJGzL2oDYbYEppBaCMH6pFlwLeqj2QS+hhkw2kpXkSdD1JxQ==", + "dependencies": { + "sprintf-js": "^1.1.1", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/underscore.string/node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==" + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/v8flags": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz", + "integrity": "sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==", + "dependencies": { + "homedir-polyfill": "^1.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/wordpress": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/wordpress/-/wordpress-1.4.2.tgz", + "integrity": "sha512-T+o+Af6pK7mhTz/rJEZk4PpSIyRMVhx6vZm6UsmrnlL8pVudhu1gWzn1n3wZXlcEZQz7I0AOkEvPQ5hu++L+qg==", + "dependencies": { + "xmlrpc": "1.3.2" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/xmlbuilder": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-8.2.2.tgz", + "integrity": "sha512-eKRAFz04jghooy8muekqzo8uCSVNeyRedbuJrp0fovbLIi7wlsYtdUn3vBAAPq2Y3/0xMz2WMEUQ8yhVVO9Stw==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/xmlrpc": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/xmlrpc/-/xmlrpc-1.3.2.tgz", + "integrity": "sha512-jQf5gbrP6wvzN71fgkcPPkF4bF/Wyovd7Xdff8d6/ihxYmgETQYSuTc+Hl+tsh/jmgPLro/Aro48LMFlIyEKKQ==", + "dependencies": { + "sax": "1.2.x", + "xmlbuilder": "8.2.x" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.0.0" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json index 4ae852de..8ca5b6e7 100644 --- a/package.json +++ b/package.json @@ -1,32 +1,14 @@ { "name": "learn.jquery.com", - "title": "jQuery Learning Site", - "description": "jQuery Foundation site for learning jQuery and JavaScript", - "version": "0.2.4", - "homepage": "http://learn.jquery.com", - "author": { - "name": "jQuery Foundation (http://jquery.org/)" - }, - "repository": { - "type": "git", - "url": "git://github.com/jquery/learn.jquery.com.git" - }, - "bugs": { - "url": "https://github.com/jquery/learn.jquery.com/issues" - }, - "licenses": [ - { - "type": "MIT", - "url": "http://www.opensource.org/licenses/MIT" - } - ], + "private": true, + "version": "0.6.31", "dependencies": { - "grunt": "0.3.x", - "grunt-clean": "0.3.0", - "grunt-html": "0.1.1", - "grunt-wordpress": "1.0.5", - "grunt-jquery-content": "0.8.1", - "grunt-check-modules": "0.1.0", - "js-yaml": "2.0.1" + "grunt": "1.6.1", + "grunt-jquery-content": "3.3.1" + }, + "devDependencies": { + "eslint": "^9.0.0", + "eslint-config-jquery": "^3.0.2", + "globals": "^15.0.0" } } diff --git a/page/about-jquery.md b/page/about-jquery.md index c4892746..1830bd4e 100644 --- a/page/about-jquery.md +++ b/page/about-jquery.md @@ -1,11 +1,14 @@ ---- -title: About jQuery -level: beginner -customFields: - - - key: "icon" - value: "certificate" ---- + -Getting started with jQuery can be easy or challenging, depending on -your experience with JavaScript, HTML, CSS, and programming concepts in general. +Getting started with jQuery can be easy or challenging, depending on your experience with JavaScript, HTML, CSS, and programming concepts in general. In addition to these articles, you can read about the [history of jQuery](https://jquery.org/history/) and the [licensing terms](https://jquery.org/license/) that apply to jQuery projects. You can also [make a donation](https://jquery.org/donate/) to help the [jQuery team](https://jquery.org/team/) continue to improve jQuery. + +One important thing to know is that jQuery is just a __JavaScript library__. All the power of jQuery is accessed via JavaScript, so having a strong grasp of JavaScript is essential for understanding, structuring, and debugging your code. While working with jQuery regularly can, over time, improve your proficiency with JavaScript, it can be hard to get started writing jQuery without a working knowledge of JavaScript's built-in constructs and syntax. Therefore, if you're new to JavaScript, we recommend checking out the [JavaScript basics tutorial](https://developer.mozilla.org/en-US/Learn/Getting_started_with_the_web/JavaScript_basics) on the Mozilla Developer Network (MDN). diff --git a/page/about-jquery/additional-support.md b/page/about-jquery/additional-support.md index d99a477b..9378b57f 100644 --- a/page/about-jquery/additional-support.md +++ b/page/about-jquery/additional-support.md @@ -1,6 +1,6 @@ ---- -title : Additional jQuery Support ---- + While we hope to cover most jQuery-related topics on this site, you may need additional or more immediate support. The following resources can prove useful. @@ -11,29 +11,29 @@ http://forum.jquery.com/ There are many subforums where you can discuss jQuery, ask questions, talk about JavaScript, or announce your plugins. * [Getting Started](http://forum.jquery.com/getting-started) - * This is the best place to post if you are brand new to jQuery and JavaScript. + * This is the best place to post if you are brand new to jQuery and JavaScript. * [Using jQuery](http://forum.jquery.com/using-jquery) - * This is the best place to post if you have general questions or concerns. - * If you've built a site that uses jQuery, or would like to announce a new plugin, this is the place to do it. + * This is the best place to post if you have general questions or concerns. + * If you've built a site that uses jQuery, or would like to announce a new plugin, this is the place to do it. * [Using jQuery Plugins](http://forum.jquery.com/using-jquery-plugins) - * If you are a plugin author or user and you wish to discuss specific plugins, plugin bugs, new features, or new plugins. + * If you are a plugin author or user and you wish to discuss specific plugins, plugin bugs, new features, or new plugins. * [Using jQuery UI](http://forum.jquery.com/using-jquery-ui) - * This is the place to discuss use of [jQuery UI](http://jqueryui.com/) Interactions, Widgets, and Effects + * This is the place to discuss use of [jQuery UI](http://jqueryui.com/) Interactions, Widgets, and Effects * [jQuery Mobile](http://forum.jquery.com/jquery-mobile) - * This is the place to discuss jQuery Mobile. + * This is the place to discuss jQuery Mobile. * [Developing jQuery Core](http://forum.jquery.com/developing-jquery-core) - * This forum centers around development of the jQuery library itself. - * Post here if you have questions about certain bugs, development with jQuery, features, or anything in the bug tracker or Git. + * This forum centers around development of the jQuery library itself. + * Post here if you have questions about certain bugs, development with jQuery, features, or anything in the bug tracker or Git. * [Developing jQuery Plugins](http://forum.jquery.com/developing-jquery-plugins) - * This forum covers development of jQuery plugins. + * This forum covers development of jQuery plugins. * [Developing jQuery UI](http://forum.jquery.com/developing-jquery-ui) - * This is the place to discuss development of [jQuery UI](http://jqueryui.com/) itself - including bugs, new plugins, and how you can help. - * All jQuery UI svn commits are posted to this list to facilitate feedback, discussion, and review. - * Also note that a lot of the development and planning of jQuery UI takes place on the [jQuery UI Development and Planning Wiki](http://wiki.jqueryui.com/). + * This is the place to discuss development of [jQuery UI](http://jqueryui.com/) itself – including bugs, new plugins, and how you can help. + * All jQuery UI svn commits are posted to this list to facilitate feedback, discussion, and review. + * Also note that a lot of the development and planning of jQuery UI takes place on the [jQuery UI Development and Planning Wiki](http://wiki.jqueryui.com/). * [Developing jQuery Mobile](http://forum.jquery.com/developing-jquery-mobile) - * This forum covers issues related to the development of jQuery Mobile. + * This forum covers issues related to the development of jQuery Mobile. * [QUnit and Testing](http://forum.jquery.com/qunit-and-testing) - * This is the place to discuss JavaScript testing in general and QUnit in particular + * This is the place to discuss JavaScript testing in general and QUnit in particular At the bottom of each of the forums is an RSS feed you can subscribe to. @@ -41,53 +41,53 @@ To ensure that you'll get a useful answer in no time, please consider the follow * Ensure your markup is valid. * Use Firebug/Developer Tools to see if you have an exception. -* Use Firebug/Developer Tools to inspect the html classes, css. etc. -* Try expected resulting html and css without javascript/jQuery and see if the problem could be isolated to those two. +* Use Firebug/Developer Tools to inspect the HTML classes, CSS, etc. +* Try expected resulting HTML and CSS without JavaScript/jQuery and see if the problem could be isolated to those two. * Reduce to a minimal test case (keep removing things until the problem goes away, etc.) -* Provide that test case as part of your mail. Either upload it somewhere or post it on jsbin.com. +* Provide that test case as part of your mail. Either upload it somewhere or post it on [jsbin.com](http://jsbin.com/). -In general, keep your question short and focused and provide only essential details - others can be added when required. +In general, keep your question short and focused and provide only essential details – others can be added when required. ### Mailing List Archives -The mailing list existed before the forums were created. The mailing lists were closed in early 2010. +The mailing lists existed before the forums were created, and were closed in early 2010. There are two different ways of browsing the mailing list archives. 1. The official mailing list archives can be found here: - * [jQuery General Discussion Archives](http://groups.google.com/group/jquery-en) - * [jQuery Dev List Archives](http://groups.google.com/group/jquery-dev) - * [jQuery UI General Discussion Archives](http://groups.google.com/group/jquery-ui) - * [jQuery UI Dev List Archives](http://groups.google.com/group/jquery-ui-dev) - * [jQuery Plugins List Archives](http://groups.google.com/group/jquery-plugins) + * [jQuery General Discussion Archives](http://groups.google.com/group/jquery-en) + * [jQuery Dev List Archives](http://groups.google.com/group/jquery-dev) + * [jQuery UI General Discussion Archives](http://groups.google.com/group/jquery-ui) + * [jQuery UI Dev List Archives](http://groups.google.com/group/jquery-ui-dev) + * [jQuery Plugins List Archives](http://groups.google.com/group/jquery-plugins) -Also, an interactive, browsable, version of the General Discussion mailing list can be found on [Nabble](http://www.nabble.com/JQuery-f15494.html) (a forum-like mailing list mirror). +2. Also, an interactive, browsable version of the General Discussion mailing list can be found on [Nabble](http://jquery.10927.n7.nabble.com/jQuery-General-Discussion-f3.html) (a forum-like mailing list mirror). ### Chat / IRC Channel -jQuery also has a very active IRC channel, **#jquery**, hosted by [freenode](http://freenode.net/). +jQuery also has a very active IRC channel, `#jquery`, hosted by [freenode](http://freenode.net/). The IRC Channel is best if you need quick help with any of the following: * JavaScript * jQuery syntax -* problem solving -* strange bugs. +* Problem solving +* Strange bugs If your problem is more in-depth, we may ask you to post to the mailing list, or the bug tracker, so that we can help you in a more-suitable environment. -####Connect info: +#### Connect info: **Server:** irc.freenode.net -**Room:** #jquery +**Room:** `#jquery` You can also connect at http://webchat.freenode.net/?channels=#jquery. -Additionally we have **#jquery-es** and **#jquery-de** if you want to speak your native language. +If you wish to post code snippets to the channel, you should use a paste site, like [jsfiddle.net](http://jsfiddle.net/) or [jsbin.com](http://jsbin.com/). -If you wish to post code snippets to the channel, you should use a paste site, like http://jsfiddle.net/ or http://jsbin.com/ +Additional info regarding jQuery's use of IRC can be found on [irc.jquery.org](http://irc.jquery.org). ### StackOverflow -There is an active and well-informed support community at [StackOverflow](http://stackoverflow.com/questions/tagged/jquery). You can likely find an answer for whatever issue you're experiencing. If your question isn't addressed, you can ask a new question and often receive a quick response. \ No newline at end of file +There is an active and well-informed support community at [StackOverflow](http://stackoverflow.com/questions/tagged/jquery). You can likely find an answer for whatever issue you're experiencing. If your question isn't addressed, you can ask a new question and often receive a quick response. diff --git a/page/about-jquery/how-jquery-works.md b/page/about-jquery/how-jquery-works.md index a175d7c4..5314273e 100644 --- a/page/about-jquery/how-jquery-works.md +++ b/page/about-jquery/how-jquery-works.md @@ -1,161 +1,183 @@ ---- -title : How jQuery Works -level: beginner ---- + + ### jQuery: The Basics -This is a basic tutorial, designed to help you get started using jQuery. If you -don't have a test page setup yet, start by creating the following HTML page: +This is a basic tutorial, designed to help you get started using jQuery. If you don't have a test page setup yet, start by creating the following HTML page: ``` - - - Demo - - - jQuery - - - + + + Demo + + + jQuery + + + ``` -The `src` attribute in the ` - - + + + Demo + + + jQuery + + + ``` ### Adding and Removing an HTML Class -**Important:** *You must place the remaining jQuery examples inside the `ready` event so that your code executes when the document is ready to be worked on.* +
+ +**Important:** You must place the remaining jQuery examples inside the `ready` event so that your code executes when the document is ready to be worked on. -Another common task is adding or removing a `class`. +
+ +Another common task is adding or removing a class. First, add some style information into the `` of the document, like this: ``` ``` -Next, add the [addClass()](http://api.jquery.com/addClass) call to the script: +Next, add the [.addClass()](http://api.jquery.com/addClass/) call to the script: ``` -$("a").addClass("test"); +$( "a" ).addClass( "test" ); ``` -All `a` elements are now bold. +All `` elements are now bold. -To remove an existing `class`, use [removeClass()](http://api.jquery.com/removeClass): +To remove an existing class, use [.removeClass()](http://api.jquery.com/removeClass/): ``` -$("a").removeClass("test"); +$( "a" ).removeClass( "test" ); ``` ### Special Effects -jQuery also provides some handy [effects](http://api.jquery.com/category/effects/) -to help you make your web sites stand out. -For example, if you create a click handler of: +jQuery also provides some handy [effects](http://api.jquery.com/category/effects/) to help you make your web sites stand out. For example, if you create a click handler of: ``` -$("a").click(function( event ){ - event.preventDefault(); - $( this ).hide("slow"); +$( "a" ).click(function( event ) { + + event.preventDefault(); + + $( this ).hide( "slow" ); + }); ``` -then the link slowly disappears when clicked. +Then the link slowly disappears when clicked. ## Callbacks and Functions -Unlike many other programming languages, JavaScript enables you to freely pass functions around to be executed at a later time. -A *callback* is a function that is passed as an argument to another function and -is executed after its parent function has completed. Callbacks are special because -they patiently wait to execute until their parent finishes. -Meanwhile, the browser can be executing other functions or doing all sorts of other work. +Unlike many other programming languages, JavaScript enables you to freely pass functions around to be executed at a later time. A *callback* is a function that is passed as an argument to another function and is executed after its parent function has completed. Callbacks are special because they patiently wait to execute until their parent finishes. Meanwhile, the browser can be executing other functions or doing all sorts of other work. To use callbacks, it is important to know how to pass them into their parent function. @@ -167,36 +189,34 @@ If a callback has no arguments, you can pass it in like this: $.get( "myhtmlpage.html", myCallBack ); ``` -When [$.get](http://api.jquery.com/jQuery.get/) finishes getting the page `myhtmlpage.html`, it executes the `myCallBack` function. -**Note** that the second parameter here is simply the function name (but *not* as a string and without parentheses). +When [$.get()](http://api.jquery.com/jQuery.get/) finishes getting the page `myhtmlpage.html`, it executes the `myCallBack()` function. + +* **Note:** The second parameter here is simply the function name (but *not* as a string, and without parentheses). ### Callback *with* Arguments -Executing callbacks with arguments can be tricky. +Executing callbacks with arguments can be tricky. #### Wrong + This code example will ***not*** work: ``` -$.get( "myhtmlpage.html", myCallBack(param1, param2) ); +$.get( "myhtmlpage.html", myCallBack( param1, param2 ) ); ``` -The reason this fails is that the code executes `myCallBack( param1, param2 )` immediately -and then passes the myCallBack's *return value* as the second parameter to `$.get`. -We actually want to pass in `myCallBack` the function, not `myCallBack`'s return value -(which might or might not be a function). So, how to pass in `myCallBack` *and* include its arguments? +The reason this fails is that the code executes `myCallBack( param1, param2 )` immediately and then passes `myCallBack()`'s *return value* as the second parameter to `$.get()`. We actually want to pass the function `myCallBack()`, not `myCallBack( param1, param2 )`'s return value (which might or might not be a function). So, how to pass in `myCallBack()` *and* include its arguments? #### Right -To defer executing `myCallBack` with its parameters, you can use an anonymous function as a wrapper. -Note the use of `function() {`. The anonymous function does exactly one thing: calls -`myCallBack`, with the values of `param1` and `param2`. +To defer executing `myCallBack()` with its parameters, you can use an anonymous function as a wrapper. Note the use of `function() {`. The anonymous function does exactly one thing: calls `myCallBack()`, with the values of `param1` and `param2`. ``` - $.get( "myhtmlpage.html", function() { - myCallBack( param1, param2 ); - }); +$.get( "myhtmlpage.html", function() { + + myCallBack( param1, param2 ); + +}); ``` -When `$.get` finishes getting the page `myhtmlpage.html`, it executes the anonymous function, -which executes `myCallBack( param1, param2 )`. +When `$.get()` finishes getting the page `myhtmlpage.html`, it executes the anonymous function, which executes `myCallBack( param1, param2 )`. diff --git a/page/about.md b/page/about.md index 517a5ad0..454bfe9f 100644 --- a/page/about.md +++ b/page/about.md @@ -1,70 +1,33 @@ ---- -title: About This Site -customFields: - - - key: "is_chapter" - value: 0 ---- + -Learning how and when to use jQuery is a different process for each and every -web developer, depending largely on experience with the primary tools for -front-end development (HTML, CSS, and JavaScript) and knowledge of general -programming principles. Over the years developers of all stripes have come to -rely on our [API documentation](http://api.jquery.com) for help figuring out -how to do exactly what they need to do. +Learning how and when to use jQuery is a different process for each and every web developer, depending largely on experience with the primary tools for front-end development (HTML, CSS, and JavaScript) and knowledge of general programming principles. Over the years developers of all stripes have come to rely on our [API documentation](http://api.jquery.com) for help figuring out how to do exactly what they need to do. -However, API documentation alone cannot serve as a guide to solving problems -and fostering a true understanding of web development. Over the years, an -ecosystem of blog posts, books, support forums and channels has grown to help -cover the **hows** and **whys** of developing with jQuery, as well as explaining -best practices, techniques, and workarounds for common problems. This type of -documentation has been invaluable resource for millions of people, but the -experience of navigating these waters can be frustrating as often as it is -fulfilling, as developers struggle to identify trustworthy resources, determine -whether what they're reading is actually up to date, and figure out those -magical search keywords that are _just right_! +However, API documentation alone cannot serve as a guide to solving problems and fostering a true understanding of web development. Over the years, an ecosystem of blog posts, books, support forums, and channels has grown to help cover the **hows** and **whys** of developing with jQuery, as well as explaining best practices, techniques, and workarounds for common problems. This type of documentation has been invaluable resource for millions of people, but the experience of navigating these waters can be frustrating as often as it is fulfilling, as developers struggle to identify trustworthy resources, determine whether what they're reading is actually up to date, and figure out those magical search keywords that are _just right_! -This site represents the jQuery Foundation's ongoing effort to consolidate and -curate this information in order to provide this crucial "narrative -documentation" to our community and serve the following goals: +This site represents the jQuery Foundation's ongoing effort to consolidate and curate this information in order to provide this crucial "narrative documentation" to our community and serve the following goals: 1. Provide our **users** with a digestible reference on all aspects of using jQuery, from the basics of getting started and performing common tasks to more advanced topics like approaches to structuring code and where jQuery fits into modern web application development. -2. Provide our **contributors** a central, open place to collaborate and provide a dependable, highly sharable resource that will improve our users' support experiences -3. Foster an environment by which users are encouraged to become contributors and build the skills to help them work on jQuery -- or any other open source project! +2. Provide our **contributors** a central, open place to collaborate and provide a dependable, highly sharable resource that will improve our users' support experiences. +3. Foster an environment by which users are encouraged to become contributors and build the skills to help them work on jQuery – or any other open source project! -In order to achieve these goals, all of [this site's content is maintained -publicly on GitHub](http://github.com/jquery/learn.jquery.com) and is licensed -under the [MIT -License](https://github.com/jquery/learn.jquery.com/blob/master/MIT-LICENSE.txt). -To learn more about how the site works, take a look at our [contributing -guide](/contributing). +In order to achieve these goals, all of [this site's content is maintained publicly on GitHub](https://github.com/jquery/learn.jquery.com) and is licensed under the [MIT License](https://github.com/jquery/learn.jquery.com/blob/master/LICENSE.txt). To learn more about how the site works, take a look at our [contributing guide](/contributing/). ## History The jQuery Learning site has its roots in two primary places. -The first is Rebecca Murphey's [jQuery Fundamentals](http://jqfundamentals.com/legacy), -a free, open source book on jQuery basics she originally released in 2010. -Seeking a better home where the information could be both maintained going -forward, and consumed in a more piecemeal fashion, Rebecca donated the content -to the jQuery Foundation to form the basis of what was then an abstract idea -for some sort of "learning center." +The first is Rebecca Murphey's _jQuery Fundamentals_ ([archive](https://web.archive.org/web/20151123192026/http://jqfundamentals.com/legacy/), [source](https://github.com/rmurphey/jqfundamentals)), a free, open source book on jQuery basics originally released in 2010. Seeking a better home where the information could be both maintained going forward, and consumed in a more piecemeal fashion, Rebecca donated the content to the jQuery Foundation to form the basis of what was then an abstract idea for some sort of "learning center." -The second is [docs.jquery.com](http://docs.jquery.com), that erstwhile -chestnut still living out its final days before it will be shut down in early -2013. Since we've moved the API documentation for jQuery Core off that domain, -we needed a place that could serve a similar need -- documentation (that anyone -can contribute to) that gets into the "how-to" and FAQs -- without clumsy -barriers to entry like finding the right person to set you up with a special -wiki account and forcing all authoring into a `textarea`. +The second is docs.jquery.com ([archive](https://web.archive.org/web/20121027123801/http://docs.jquery.com/Main%5FPage), [source](https://github.com/jquery/docs.jquery.com)), that erstwhile chestnut was home to all official documentation for jQuery Core, jQuery UI, and QUnit, as well as various community plugins, tutorials, and contribution guides. It was powered by MediaWiki and ran from 2006 to 2013. We moved official API documentation for jQuery to their own sites (that [anyone can contribute to](https://contribute.jquery.org/web-sites/#site-amp-repository-guide) via Git), but still needed a place for the the "how-to" guides and FAQs that was similarly open to contribution. Many of these pages live on in the jQuery Learning Center. -

About the Beta

+## Contributing -Though this resource will never truly be "done," the current version of this -site should still be considered something of a preview. We still have a number -of improvements we want to make to the content, user experience, and site build -before we're ready to call it a "final release." At the same time, however, -it's important for us to open the doors now so we can begin providing better -docs to people who need the right away and spread the word about this effort. -If you're interested in helping us reach the finish line, we invite you to -please read more about how [you can get involved with contributing](/contributing)! +This project wouldn't have been possible without all our [awesome contributors](https://github.com/jquery/learn.jquery.com/graphs/contributors?type=a). If you feel like something on the site is open for improvements, you can contribute [on GitHub](https://github.com/jquery/learn.jquery.com). Feel free to check out [the contributing guide](contributing/) for more in-depth information. diff --git a/page/ajax.md b/page/ajax.md index ced39912..95315bb7 100644 --- a/page/ajax.md +++ b/page/ajax.md @@ -1,20 +1,22 @@ ---- -title: Ajax -level: beginner -customFields: - - - key: "icon" - value: "refresh" ---- + -Traditionally webpages required reloading to update their content. For web-based email this meant that users had to manually reload their inbox to check and see if they had new mail. This had huge drawbacks: it was slow and it required user input. When the user reloaded their inbox, the server had to reconstruct the entire web page and resend all of the HTML, CSS, JavaScript, as well as the user's email. This was hugely inefficient. Ideally, the server should only have to send the user's new messages, not the entire page. By 2003 all the major browsers, solved this issue by adopting the XMLHttpRequest (XHR) object, allowing browsers to communicate with the server without requiring a page reload. +Traditionally webpages required reloading to update their content. For web-based email this meant that users had to manually reload their inbox to check and see if they had new mail. This had huge drawbacks: it was slow and it required user input. When the user reloaded their inbox, the server had to reconstruct the entire web page and resend all of the HTML, CSS, JavaScript, as well as the user's email. This was hugely inefficient. Ideally, the server should only have to send the user's new messages, not the entire page. By 2003, all the major browsers solved this issue by adopting the XMLHttpRequest (XHR) object, allowing browsers to communicate with the server without requiring a page reload. -The XMLHttpRequest object is part of a technology called Ajax (Asynchronous JavaScript and XML). Using Ajax, data could then be passed between the browser and the server, using the XMLHttpRequest API, without having to reload the web page. With the widespread adoption of the XMLHttpRequest object it quickly became possible to build web applications like Google Maps, and GMail that used XMLHttpRequest to get new map tiles, or new email without having to reload the entire page. +The XMLHttpRequest object is part of a technology called Ajax (Asynchronous JavaScript and XML). Using Ajax, data could then be passed between the browser and the server, using the XMLHttpRequest API, without having to reload the web page. With the widespread adoption of the XMLHttpRequest object it quickly became possible to build web applications like Google Maps, and Gmail that used XMLHttpRequest to get new map tiles, or new email without having to reload the entire page. -Ajax requests are triggered by JavaScript code; your code sends a request to a URL, and when it receives a response, a callback function can be triggered to handle the response. Because the request is asynchronous, the rest of your code continues to execute while the request is being processed, so it’s imperative that a callback be used to handle the response. +Ajax requests are triggered by JavaScript code; your code sends a request to a URL, and when it receives a response, a callback function can be triggered to handle the response. Because the request is asynchronous, the rest of your code continues to execute while the request is being processed, so it's imperative that a callback be used to handle the response. Unfortunately, different browsers implement the Ajax API differently. Typically this meant that developers would have to account for all the different browsers to ensure that Ajax would work universally. Fortunately, jQuery provides Ajax support that abstracts away painful browser differences. It offers both a full-featured `$.ajax()` method, and simple convenience methods such as `$.get()`, `$.getScript()`, `$.getJSON()`, `$.post()`, and `$().load()`. -Most jQuery applications don’t in fact use XML, despite the name “Ajax”; instead, they transport data as plain HTML or JSON (JavaScript Object Notation). +Most jQuery applications don't in fact use XML, despite the name "Ajax"; instead, they transport data as plain HTML or JSON (JavaScript Object Notation). -In general, Ajax does not work across domains. For instance, a webpage loaded from example1.com is unable to make an Ajax request to example2.com as it would violate the same origin policy. As a work around, JSONP (JSON with Padding) uses ` -jQuery’s ajax capabilities can be especially useful when dealing with forms. There are several advantages, which can range from serialization, to simple client-side validation (e.g. "Sorry, that username is taken"), to [prefilters](http://api.jquery.com/extending-ajax/#Prefilters) (explained below), and even more! +jQuery's ajax capabilities can be especially useful when dealing with forms. There are several advantages, which can range from serialization, to simple client-side validation (e.g. "Sorry, that username is taken"), to [prefilters](http://api.jquery.com/jQuery.ajaxPrefilter/) (explained below), and even more! ### Serialization -Serializing form inputs in jQuery is extremely easy. Two methods come supported natively - `$.fn.serialize` and `$.fn.serializeArray`. While the names are fairly self-explanatory, there are many advantages to using them. -The `serialize` method serializes a form's data into a query string. For the element's value to be serialized, it **must** have a `name` attribute. Please note that values from inputs with a type of `checkbox` or `radio` are included only if they are checked. +Serializing form inputs in jQuery is extremely easy. Two methods come supported natively: `.serialize()` and `.serializeArray()`. While the names are fairly self-explanatory, there are many advantages to using them. + +The `.serialize()` method serializes a form's data into a query string. For the element's value to be serialized, it **must** have a `name` attribute. Please note that values from inputs with a type of `checkbox` or `radio` are included only if they are checked. ``` // Turning form data into a query string -$("#myForm").serialize(); +$( "#myForm" ).serialize(); -// creates a query string like this: +// Creates a query string like this: // field_1=something&field2=somethingElse ``` -While plain old serialization is great, sometimes your application would work better if you sent over an array of objects, instead of just the query string. For that, jQuery has the `serializeArray` method. It's very similar to the `serialize` method listed above, except it produces an array of objects, instead of a string. +While plain old serialization is great, sometimes your application would work better if you sent over an array of objects, instead of just the query string. For that, jQuery has the `.serializeArray()` method. It's very similar to the `.serialize()` method listed above, except it produces an array of objects, instead of a string. ``` // Creating an array of objects containing form data -$("#myForm").serializeArray(); +$( "#myForm" ).serializeArray(); -// creates a structure like this: +// Creates a structure like this: // [ // { -// // name : "field_1", // value : "something" -// // }, // { -// // name : "field_2", // value : "somethingElse" -// // } // ] ``` ### Client-side validation -Client-side validation is, much like many other things, extremely easy using jQuery. While there are several cases developers can test for, some of the most common ones are: presence of a required input, valid usernames/emails/phone numbers/etc..., or checking an "I agree..." box. + +Client-side validation is, much like many other things, extremely easy using jQuery. While there are several cases developers can test for, some of the most common ones are: presence of a required input, valid usernames/emails/phone numbers/etc…, or checking an "I agree…" box. Please note that it is advisable that you also perform server-side validation for your inputs. However, it typically makes for a better user experience to be able to validate some things without submitting the form. @@ -53,80 +50,69 @@ With that being said, let's jump on in to some examples! First, we'll see how ea ``` // Using validation to check for the presence of an input -$("#form").submit(function( event ) { - - // if .required's value's length is zero - if ( $(".required").val().length === 0 ) { - - // usually show some kind of error message here +$( "#form" ).submit(function( event ) { - // this prevents the form from submitting - return false; + // If .required's value's length is zero + if ( $( ".required" ).val().length === 0 ) { - } else { + // Usually show some kind of error message here - // run $.ajax here - - } + // Prevent the form from submitting + event.preventDefault(); + } else { + // Run $.ajax() here + } }); ``` -Let's see how easy it is to check for invalid characters in a username: +Let's see how easy it is to check for invalid characters in a phone number: ``` // Validate a phone number field -$("#form").submit(function( event ) { - - var inputtedPhoneNumber = $("#phone").val(); - // match only numbers - var phoneNumberRegex = ^\d*$/; - - // if the phone number doesn't match the regex - if ( !phoneNumberRegex.test( inputtedPhoneNumber ) ) { - - // usually show some kind of error message here +$( "#form" ).submit(function( event ) { + var inputtedPhoneNumber = $( "#phone" ).val(); - // prevent the form from submitting - return false; + // Match only numbers + var phoneNumberRegex = /^\d*$/; - } else { + // If the phone number doesn't match the regex + if ( !phoneNumberRegex.test( inputtedPhoneNumber ) ) { - // run $.ajax here + // Usually show some kind of error message here - } + // Prevent the form from submitting + event.preventDefault(); + } else { + // Run $.ajax() here + } }); ``` ### Prefiltering + A prefilter is a way to modify the ajax options before each request is sent (hence, the name `prefilter`). -For example, say we would like to modify all crossDomain requests through a proxy. To do so with a prefilter is quite simple: +For example, say we would like to modify all cross-domain requests through a proxy. To do so with a prefilter is quite simple: ``` // Using a proxy with a prefilter $.ajaxPrefilter(function( options, originalOptions, jqXHR ) { - - if ( options.crossDomain ) { - - options.url = "http://mydomain.net/proxy/" + encodeURIComponent( options.url ); - - options.crossDomain = false; - - } - + if ( options.crossDomain ) { + options.url = "http://mydomain.net/proxy/" + encodeURIComponent( options.url ); + options.crossDomain = false; + } }); ``` You can pass in an optional argument before the callback function that specifies which `dataTypes` you'd like the prefilter to be applied to. For example, if we want our prefilter to only apply to `JSON` and `script` requests, we'd do: ``` -// Using the optional dataTypes argument"> +// Using the optional dataTypes argument $.ajaxPrefilter( "json script", function( options, originalOptions, jqXHR ) { - // do all of the prefiltering here, but only for - // requests that indicate a dataType of "JSON" or "script" - + // Do all of the prefiltering here, but only for + // requests that indicate a dataType of "JSON" or "script" }); ``` diff --git a/page/ajax/ajax-events.md b/page/ajax/ajax-events.md index 0c29a561..5cc70bfb 100644 --- a/page/ajax/ajax-events.md +++ b/page/ajax/ajax-events.md @@ -1,26 +1,19 @@ ---- -title : Ajax Events -level: beginner -source: http://jqfundamentals.com/legacy -attribution: - - jQuery Fundamentals ---- -Often, you’ll want to perform an operation whenever an Ajax requests starts or -stops, such as showing or hiding a loading indicator. Rather than defining -this behavior inside every Ajax request, you can bind Ajax events to elements -just like you'd bind other events. For a complete list of Ajax events, visit -[ Ajax Events documentation on docs.jquery.com ]( http://docs.jquery.com/Ajax_Events ). + + +Often, you'll want to perform an operation whenever an Ajax request starts or stops, such as showing or hiding a loading indicator. Rather than defining this behavior inside every Ajax request, you can bind Ajax events to elements just like you'd bind other events. For a complete list of Ajax events, visit [Ajax Events documentation on docs.jquery.com](http://docs.jquery.com/Ajax_Events). ``` // Setting up a loading indicator using Ajax Events -$("#loading_indicator").ajaxStart(function() { - - $( this ).show(); - - }).ajaxStop(function() { - - $( this ).hide(); - -}); - +$( "#loading_indicator" ) + .ajaxStart(function() { + $( this ).show(); + }) + .ajaxStop(function() { + $( this ).hide(); + }); ``` diff --git a/page/ajax/jquery-ajax-methods.md b/page/ajax/jquery-ajax-methods.md index 41a1c6ff..54a62e1e 100644 --- a/page/ajax/jquery-ajax-methods.md +++ b/page/ajax/jquery-ajax-methods.md @@ -1,140 +1,99 @@ ---- -title : jQuery's Ajax-Related Methods -level: beginner -source: http://jqfundamentals.com/legacy -attribution: - - jQuery Fundamentals ---- -While jQuery does offer many Ajax-related convenience methods, the core -`$.ajax` method is at the heart of all of them, and understanding it is -imperative. We'll review it first, and then touch briefly on the convenience -methods. - -I generally use the `$.ajax` method and do not use convenience methods. As -you'll see, it offers features that the convenience methods do not, and its -syntax is more easily understandable, in my opinion. - -### `$.ajax` - -jQuery’s core `$.ajax` method is a powerful and straightforward way of creating -Ajax requests. It takes a configuration object that contains all the -instructions jQuery requires to complete the request. The `$.ajax` method is -particularly valuable because it offers the ability to specify both success and -failure callbacks. Also, its ability to take a configuration object that can -be defined separately makes it easier to write reusable code. For complete -documentation of the configuration options, visit -[http://api.jquery.com/jQuery.ajax/](http://api.jquery.com/jQuery.ajax/ "$.ajax -documentation on api.jquery.com"). + -``` -// Using the core $.ajax method -$.ajax({ - - // the URL for the request - url : "post.php", +While jQuery does offer many Ajax-related convenience methods, the core `$.ajax()` method is at the heart of all of them, and understanding it is imperative. We'll review it first, and then touch briefly on the convenience methods. - // the data to send - // (will be converted to a query string) - data : { - id : 123 - }, +It's often considered good practice to use the `$.ajax()` method over the jQuery provided [convenience methods](#convenience-methods). As you'll see, it offers features that the convenience methods do not, and its syntax allows for the ease of readability. - // whether this is a POST or GET request - type : "GET", +### `$.ajax()` - // the type of data we expect back - dataType : "json", +jQuery’s core `$.ajax()` method is a powerful and straightforward way of creating Ajax requests. It takes a configuration object that contains all the instructions jQuery requires to complete the request. The `$.ajax()` method is particularly valuable because it offers the ability to specify both success and failure callbacks. Also, its ability to take a configuration object that can be defined separately makes it easier to write reusable code. For complete documentation of the configuration options, visit [http://api.jquery.com/jQuery.ajax/](http://api.jquery.com/jQuery.ajax/). - // code to run if the request succeeds; - // the response is passed to the function - success : function( json ) { - $("

").text( json.title ).appendTo("body"); - $("
").html( json.html ).appendTo("body"); - }, - - // code to run if the request fails; - // the raw request and status codes are - // passed to the function - error : function( xhr, status ) { - alert("Sorry, there was a problem!"); - }, +``` +// Using the core $.ajax() method +$.ajax({ - // code to run regardless of success or failure - complete : function( xhr, status ) { - alert("The request is complete!"); - } + // The URL for the request + url: "post.php", + + // The data to send (will be converted to a query string) + data: { + id: 123 + }, + + // Whether this is a POST or GET request + type: "GET", + + // The type of data we expect back + dataType : "json", +}) + // Code to run if the request succeeds (is done); + // The response is passed to the function + .done(function( json ) { + $( "

" ).text( json.title ).appendTo( "body" ); + $( "
").html( json.html ).appendTo( "body" ); + }) + // Code to run if the request fails; the raw request and + // status codes are passed to the function + .fail(function( xhr, status, errorThrown ) { + alert( "Sorry, there was a problem!" ); + console.log( "Error: " + errorThrown ); + console.log( "Status: " + status ); + console.dir( xhr ); + }) + // Code to run regardless of success or failure; + .always(function( xhr, status ) { + alert( "The request is complete!" ); + }); -}); ``` -
-### Note - -A note about the dataType setting: if the server sends back data that is in a -different format than you specify, your code may fail, and the reason will not -always be clear, because the HTTP response code will not show an error. When -working with Ajax requests, make sure your server is sending back the data type -you're asking for, and verify that the Content-type header is accurate for the -data type. For example, for JSON data, the Content-type header should be -`application/json`.
+**Note:** Regarding the `dataType` setting, if the server sends back data that is in a different format than you specify, your code may fail, and the reason will not always be clear, because the HTTP response code will not show an error. When working with Ajax requests, make sure your server is sending back the data type you're asking for, and verify that the `Content-type` header is accurate for the data type. For example, for JSON data, the `Content-type` header should be `application/json`. -### `$.ajax` Options +### `$.ajax()` Options -There are many, many options for the `$.ajax` method, which is part of its -power. For a complete list of options, visit -[http://api.jquery.com/jQuery.ajax/](http://api.jquery.com/jQuery.ajax/ "$.ajax -documentation on api.jquery.com"); here are several that you will use -frequently: +There are many, many options for the `$.ajax()` method, which is part of its power. For a complete list of options, visit [http://api.jquery.com/jQuery.ajax/](http://api.jquery.com/jQuery.ajax/); here are several that you will use frequently: #### async -Set to `false` if the request should be sent synchronously. Defaults to `true`. -Note that if you set this option to `false`, your request will block execution -of other code until the response is received. +Set to `false` if the request should be sent synchronously. Defaults to `true`. Note that if you set this option to `false`, your request will block execution of other code until the response is received. #### cache -Whether to use a cached response if available. Defaults to true for all -dataTypes except "script" and "jsonp". When set to false, the URL will simply -have a cachebusting parameter appended to it. +Whether to use a cached response if available. Defaults to `true` for all `dataType`s except "script" and "jsonp". When set to `false`, the URL will simply have a cachebusting parameter appended to it. + +#### done + +A callback function to run if the request succeeds. The function receives the response data (converted to a JavaScript object if the `dataType` was JSON), as well as the text status of the request and the raw request object. + +#### fail -#### complete +A callback function to run if the request results in an error. The function receives the raw request object and the text status of the request. -A callback function to run when the request is complete, regardless of success -or failure. The function receives the raw request object and the text status of -the request. +#### always + +A callback function to run when the request is complete, regardless of success or failure. The function receives the raw request object and the text status of the request. #### context -The scope in which the callback function(s) should run (i.e. what `this` will -mean inside the callback function(s)). By default, `this` inside the callback -function(s) refers to the object originally passed to `$.ajax`. +The scope in which the callback function(s) should run (i.e. what `this` will mean inside the callback function(s)). By default, `this` inside the callback function(s) refers to the object originally passed to `$.ajax()`. #### data -The data to be sent to the server. This can either be an object or a query -string, such as `foo=bar&baz=bim`. +The data to be sent to the server. This can either be an object or a query string, such as `foo=bar&baz=bim`. #### dataType -The type of data you expect back from the server. By default, jQuery will look -at the MIME type of the response if no dataType is specified. - -#### error - -A callback function to run if the request results in an error. The function -receives the raw request object and the text status of the request. +The type of data you expect back from the server. By default, jQuery will look at the MIME type of the response if no `dataType` is specified. #### jsonp -The callback name to send in a query string when making a JSONP request. -Defaults to "callback". +The callback name to send in a query string when making a JSONP request. Defaults to "callback". -#### success - -A callback function to run if the request succeeds. The function receives the -response data (converted to a JavaScript object if the dataType was JSON), as -well as the text status of the request and the raw request object. #### timeout @@ -142,32 +101,21 @@ The time in milliseconds to wait before considering the request a failure. #### traditional -Set to true to use the param serialization style in use prior to jQuery 1.4. -For details, see -[http://api.jquery.com/jQuery.param/](http://api.jquery.com/jQuery.param/ -"$.param documentation on api.jquery.com"). +Set to `true` to use the param serialization style in use prior to jQuery 1.4. For details, see [http://api.jquery.com/jQuery.param/](http://api.jquery.com/jQuery.param/). #### type -The type of the request, "POST" or "GET". Defaults to "GET". Other request -types, such as "PUT" and "DELETE" can be used, but they may not be supported by -all browsers. +The type of the request, "POST" or "GET". Defaults to "GET". Other request types, such as "PUT" and "DELETE" can be used, but they may not be supported by all browsers. #### url The URL for the request. -The `url` option is the only required property of the `$.ajax` configuration -object; all other properties are optional. This can also be passed as the first -argument to $.ajax, and the options object as the second argument. +The `url` option is the only required property of the `$.ajax()` configuration object; all other properties are optional. This can also be passed as the first argument to `$.ajax()`, and the options object as the second argument. ### Convenience Methods -If you don't need the extensive configurability of `$.ajax`, and you don't care -about handling errors, the Ajax convenience functions provided by jQuery can be -useful, terse ways to accomplish Ajax requests. These methods are just -"wrappers" around the core `$.ajax` method, and simply pre-set some of the -options on the `$.ajax` method. +If you don't need the extensive configurability of `$.ajax()`, and you don't care about handling errors, the Ajax convenience functions provided by jQuery can be useful, terse ways to accomplish Ajax requests. These methods are just "wrappers" around the core `$.ajax()` method, and simply pre-set some of the options on the `$.ajax()` method. The convenience methods provided by jQuery are: @@ -195,75 +143,57 @@ The URL for the request. Required. #### data -The data to be sent to the server. Optional. This can either be an object or a -query string, such as `foo=bar&baz=bim`. - -
-### Note +The data to be sent to the server. Optional. This can either be an object or a query string, such as `foo=bar&baz=bim`. -This option is not valid for `$.getScript`. -
+**Note:** This option is not valid for `$.getScript`. #### success callback -A callback function to run if the request succeeds. Optional. The function -receives the response data (converted to a JavaScript object if the data type -was JSON), as well as the text status of the request and the raw request -object. +A callback function to run if the request succeeds. Optional. The function receives the response data (converted to a JavaScript object if the data type was JSON), as well as the text status of the request and the raw request object. #### data type The type of data you expect back from the server. Optional. -
-### Note - -This option is only applicable for methods that don't already specify the data -type in their name.
+**Note:** This option is only applicable for methods that don't already specify the data type in their name. ``` // Using jQuery's Ajax convenience methods -// get plain text or html + +// Get plain text or HTML $.get( "/users.php", { - userId : 1234 + userId: 1234 }, function( resp ) { - console.log( resp ); // server response + console.log( resp ); // server response }); -// add a script to the page, then run a function defined in it +// Add a script to the page, then run a function defined in it $.getScript( "/static/js/myScript.js", function() { - - functionFromMyScript(); - + functionFromMyScript(); }); -// get JSON-formatted data from the server +// Get JSON-formatted data from the server $.getJSON( "/details.php", function( resp ) { - // log each key in the response data - $.each( resp, function( key, value ) { - console.log( key + " : " + value ); - }); - + // Log each key in the response data + $.each( resp, function( key, value ) { + console.log( key + " : " + value ); + }); }); ``` ### `$.fn.load` -The `$.fn.load` method is unique among jQuery’s Ajax methods in that it is -called on a selection. The `$.fn.load` method fetches HTML from a URL, and -uses the returned HTML to populate the selected element(s). In addition to -providing a URL to the method, you can optionally provide a selector; jQuery -will fetch only the matching content from the returned HTML. +The `.load()` method is unique among jQuery’s Ajax methods in that it is called on a selection. The `.load()` method fetches HTML from a URL, and uses the returned HTML to populate the selected element(s). In addition to providing a URL to the method, you can optionally provide a selector; jQuery will fetch only the matching content from the returned HTML. ``` -// Using $.fn.load to populate an element -$("#newContent").load("/foo.html"); +// Using .load() to populate an element +$( "#newContent" ).load( "/foo.html" ); ``` ``` -// Using $.fn.load to populate an element based on a selector -$("#newContent").load( "/foo.html #myDiv h1:first:", function( html ) { - alert("Content updated!""); +// Using .load() to populate an element based on a selector +$( "#newContent" ).load( "/foo.html #myDiv h1:first", function( html ) { + alert( "Content updated!" ); }); ``` diff --git a/page/ajax/key-concepts.md b/page/ajax/key-concepts.md index b353512d..68f14596 100644 --- a/page/ajax/key-concepts.md +++ b/page/ajax/key-concepts.md @@ -1,122 +1,82 @@ ---- -title : Key Concepts -level: beginner -source: http://jqfundamentals.com/legacy -attribution: - - jQuery Fundamentals ---- -Proper use of Ajax-related jQuery methods requires understanding some key -concepts first. - -### GET vs. Post - -The two most common “methods” for sending a request to a server are GET and -POST. It’s important to understand the proper application of each. - -The GET method should be used for non-destructive operations — that is, -operations where you are only “getting” data from the server, not changing data -on the server. For example, a query to a search service might be a GET -request. GET requests may be cached by the browser, which can lead to -unpredictable behavior if you are not expecting it. GET requests generally -send all of their data in a query string. - -The POST method should be used for destructive operations — that is, operations -where you are changing data on the server. For example, a user saving a blog -post should be a POST request. POST requests are generally not cached by the -browser; a query string can be part of the URL, but the data tends to be sent -separately as post data. + + +Proper use of Ajax-related jQuery methods requires understanding some key concepts first. + +### GET vs. POST + +The two most common "methods" for sending a request to a server are GET and POST. It's important to understand the proper application of each. + +The GET method should be used for non-destructive operations — that is, operations where you are only "getting" data from the server, not changing data on the server. For example, a query to a search service might be a GET request. GET requests may be cached by the browser, which can lead to unpredictable behavior if you are not expecting it. GET requests generally send all of their data in a query string. + +The POST method should be used for destructive operations — that is, operations where you are changing data on the server. For example, a user saving a blog post should be a POST request. POST requests are generally not cached by the browser; a query string can be part of the URL, but the data tends to be sent separately as post data. ### Data Types -jQuery generally requires some instruction as to the type of data you expect to -get back from an Ajax request; in some cases the data type is specified by the -method name, and in other cases it is provided as part of a configuration -object. There are several options: +jQuery generally requires some instruction as to the type of data you expect to get back from an Ajax request; in some cases the data type is specified by the method name, and in other cases it is provided as part of a configuration object. There are several options: #### text -For transporting simple strings +For transporting simple strings. #### html -For transporting blocks of HTML to be placed on the page +For transporting blocks of HTML to be placed on the page. #### script -For adding a new script to the page +For adding a new script to the page. #### json -For transporting JSON-formatted data, which can include strings, arrays, and objects +For transporting JSON-formatted data, which can include strings, arrays, and objects. -
-### Note - -As of jQuery 1.4, if the JSON data sent by your server isn't properly -formatted, the request may fail silently. See -[http://json.org](http://json.org) for details on properly formatting JSON, but -as a general rule, use built-in language methods for generating JSON on the -server to avoid syntax issues. +**Note:** As of jQuery 1.4, if the JSON data sent by your server isn't properly formatted, the request may fail silently. See [http://json.org](http://json.org) for details on properly formatting JSON, but as a general rule, use built-in language methods for generating JSON on the server to avoid syntax issues. #### jsonp -For transporting JSON data from another domain +For transporting JSON data from another domain. #### xml -For transporting data in a custom XML schema +For transporting data in a custom XML schema. -I am a strong proponent of using the JSON format in most cases, as it provides -the most flexibility. It is especially useful for sending both HTML and data at -the same time. +Consider using the JSON format in most cases, as it provides the most flexibility. It is especially useful for sending both HTML and data at the same time. ### A is for Asynchronous -The asynchronicity of Ajax catches many new jQuery users off guard. Because -Ajax calls are asynchronous by default, the response is not immediately -available. Responses can only be handled using a callback. So, for example, -the following code will not work: +The asynchronicity of Ajax catches many new jQuery users off guard. Because Ajax calls are asynchronous by default, the response is not immediately available. Responses can only be handled using a callback. So, for example, the following code will not work: ``` var response; $.get( "foo.php", function( r ) { - - response = r; - + response = r; }); console.log( response ); // undefined ``` -Instead, we need to pass a callback function to our request; this callback will -run when the request succeeds, at which point we can access the data that it -returned, if any. +Instead, we need to pass a callback function to our request; this callback will run when the request succeeds, at which point we can access the data that it returned, if any. ``` $.get( "foo.php", function( response ) { - console.log( response ); // server response + console.log( response ); // server response }); ``` ### Same-Origin Policy and JSONP -In general, Ajax requests are limited to the same protocol (http or https), the -same port, and the same domain as the page making the request. This limitation -does not apply to scripts that are loaded via jQuery's Ajax methods. +In general, Ajax requests are limited to the same protocol (http or https), the same port, and the same domain as the page making the request. This limitation does not apply to scripts that are loaded via jQuery's Ajax methods. + +Note: Versions of Internet Explorer less than 10 do not support cross-domain AJAX requests. -The other exception is requests targeted at a JSONP service on another domain. -In the case of JSONP, the provider of the service has agreed to respond to your -request with a script that can be loaded into the page using a ` -``` -// Using YQL and JSONP -$.ajax({ - - url : "http://query.yahooapis.com/v1/public/yql", - - // the name of the callback parameter, - // as specified by the YQL service - jsonp : "callback", - - // tell jQuery we're expecting JSONP - dataType : "jsonp", +The advent of JSONP — essentially a consensual cross-site scripting hack — has opened the door to powerful mashups of content. Many prominent sites provide JSONP services, allowing you access to their content via a predefined API. A particularly great source of JSONP-formatted data is the [Wikipedia API](https://en.wikipedia.org/w/api.php?action=help&modules=query), which we'll use in the following example to fetch a list of articles about [JavaScript libraries](https://en.wikipedia.org/wiki/Category:Free_software_programmed_in_JavaScript). - // tell YQL what we want and that we want JSON - data : { - q : "select title,abstract,url from search.news where query=\"cat\"", - format : "json" - }, - - // work with the response - success : function( response ) { - console.log( response ); // server response - } - -}); +``` +$.ajax( { + // Tell MediaWiki what we want and that we want JSON + url: "https://en.wikipedia.org/w/api.php", + data: { + format: "json", + action: "query", + list: "categorymembers", + cmtitle: "Category:Free_software_programmed_in_JavaScript", + cmprop: "title", + cmlimit: 500 + }, + + // Name of the query parameter that jQuery will add to set the callback function + jsonp: "callback", + + // Tell jQuery we're expecting JSONP + dataType: "jsonp", + +} ).then( function( response ) { + console.log( response.query.categorymembers ); +} ); ``` -jQuery handles all the complex aspects of JSONP behind-the-scenes — all we have -to do is tell jQuery the name of the JSONP callback parameter specified by YQL -("callback" in this case), and otherwise the whole process looks and feels like -a normal Ajax request. - +jQuery handles all the complex aspects of JSONP behind-the-scenes. All we have to do is set `dataType` to `"jsonp"` and tell jQuery the name of the JSONP parameter ("callback" in this case). Beyond that one option, the whole process looks and feels like a normal Ajax request for fetching JSON. diff --git a/page/code-organization.md b/page/code-organization.md index 8b46c316..696a78b6 100644 --- a/page/code-organization.md +++ b/page/code-organization.md @@ -1,9 +1,12 @@ ---- -title: Code Organization -level: intermediate -customFields: - - - key: "icon" - value: "sitemap" ---- + + Understanding the basic mechanics is one thing, but the essence of building applications is understanding how to organize code so that it is navigable and well-encapsulated instead of a whole slew of global functions. diff --git a/page/code-organization/beware-anonymous-functions.md b/page/code-organization/beware-anonymous-functions.md index 8ce056b9..31bd9e60 100644 --- a/page/code-organization/beware-anonymous-functions.md +++ b/page/code-organization/beware-anonymous-functions.md @@ -1,56 +1,43 @@ ---- -title: Beware Anonymous Functions -level: beginner -source: http://jqfundamentals.com/legacy -attribution: - - jQuery Fundamentals ---- - -Anonymous functions bound everywhere are a pain. They're difficult to debug, -maintain, test, or reuse. Instead, use an object literal to organize and name -your handlers and callbacks. + + +Anonymous functions bound everywhere are a pain. They're difficult to debug, maintain, test, or reuse. Instead, use an object literal to organize and name your handlers and callbacks. + ``` // BAD $( document ).ready(function() { - $("#magic").click(function( event ) { - - $("#yayeffects").slideUp(function() { - - // ... - - }); - - }); - - $("#happiness").load( url + " #unicorns", function() { + $( "#magic" ).click(function( event ) { + $( "#yayeffects" ).slideUp(function() { + // ... + }); + }); - // ... - - }); + $( "#happiness" ).load( url + " #unicorns", function() { + // ... + }); }); // BETTER var PI = { - onReady : function() { - - $("#magic").click( PI.candyMtn ); - - $("#happiness").load( PI.url + " #unicorns", PI.unicornCb ); - - }, - - candyMtn : function( event ) { - - $("#yayeffects").slideUp( PI.slideCb ); + onReady: function() { + $( "#magic" ).click( PI.candyMtn ); + $( "#happiness" ).load( PI.url + " #unicorns", PI.unicornCb ); + }, - }, + candyMtn: function( event ) { + $( "#yayeffects" ).slideUp( PI.slideCb ); + }, - slideCb : function() { ... }, + slideCb: function() { ... }, - unicornCb : function() { ... } + unicornCb: function() { ... } }; diff --git a/page/code-organization/concepts.md b/page/code-organization/concepts.md index 612a479b..509de7bd 100644 --- a/page/code-organization/concepts.md +++ b/page/code-organization/concepts.md @@ -1,66 +1,47 @@ ---- -title: Code Organization Concepts -level: beginner -source: http://jqfundamentals.com/legacy -attribution: - - jQuery Fundamentals ---- - -When you move beyond adding simple enhancements to your website with jQuery and -start developing full-blown client-side applications, you need to consider how -to organize your code. In this chapter, we'll take a look at various code -organization patterns you can use in your jQuery application and explore the -RequireJS dependency management and build system. + + +When you move beyond adding simple enhancements to your website with jQuery and start developing full-blown client-side applications, you need to consider how to organize your code. In this chapter, we'll take a look at various code organization patterns you can use in your jQuery application and explore the [RequireJS](http://requirejs.org/) dependency management and build system. ## Key Concepts -Before we jump into code organization patterns, it's important to understand -some concepts that are common to all good code organization patterns. - -- Your code should be divided into units of functionality — modules, services, - etc. Avoid the temptation to have all of your code in one huge - `$( document ).ready()` block. This concept, loosely, is known as - encapsulation. -- Don't repeat yourself. Identify similarities among pieces of functionality, - and use inheritance techniques to avoid repetitive code. -- Despite jQuery's DOM-centric nature, JavaScript applications are not all - about the DOM. Remember that not all pieces of functionality need to — or - should — have a DOM representation. -- Units of functionality should be [loosely coupled](http://en.wikipedia.org/wiki/Loose_coupling), that is, - a unit of functionality should be able to exist on its own, and communication between - units should be handled via a messaging system such as custom events or - pub/sub. Stay away from direct communication between units of functionality - whenever possible. - -The concept of loose coupling can be especially troublesome to developers -making their first foray into complex applications, so be mindful of this as -you're getting started. +Before we jump into code organization patterns, it's important to understand some concepts that are common to all good code organization patterns. + +* Your code should be divided into units of functionality — modules, services, etc. Avoid the temptation to have all of your code in one huge `$( document ).ready()` block. This concept, loosely, is known as encapsulation. +* Don't repeat yourself. Identify similarities among pieces of functionality, and use inheritance techniques to avoid repetitive code. +* Despite jQuery's DOM-centric nature, JavaScript applications are not all about the DOM. Remember that not all pieces of functionality need to — or should — have a DOM representation. +* Units of functionality should be [loosely coupled](http://en.wikipedia.org/wiki/Loose_coupling), that is, a unit of functionality should be able to exist on its own, and communication between units should be handled via a messaging system such as custom events or pub/sub. Stay away from direct communication between units of functionality whenever possible. + +The concept of loose coupling can be especially troublesome to developers making their first foray into complex applications, so be mindful of this as you're getting started. ## Encapsulation -The first step to code organization is separating pieces of your application -into distinct pieces; sometimes, even just this effort is sufficient to lend +The first step to code organization is separating pieces of your application into distinct pieces; sometimes, even just this effort is sufficient to improve the structure of your code and its maintainability. ### The Object Literal -An object literal is perhaps the simplest way to encapsulate related code. It -doesn't offer any privacy for properties or methods, but it's useful for -eliminating anonymous functions from your code, centralizing configuration -options, and easing the path to reuse and refactoring. +An object literal is perhaps the simplest way to encapsulate related code. It doesn't offer any privacy for properties or methods, but it's useful for eliminating anonymous functions from your code, centralizing configuration options, and easing the path to reuse and refactoring. ``` // An object literal var myFeature = { - myProperty: "hello", - myMethod: function() { - console.log( myFeature.myProperty ); - }, - init: function( settings ) { - myFeature.settings = settings; - }, - readSettings : function() { - console.log( myFeature.settings ); - } + myProperty: "hello", + + myMethod: function() { + console.log( myFeature.myProperty ); + }, + + init: function( settings ) { + myFeature.settings = settings; + }, + + readSettings: function() { + console.log( myFeature.settings ); + } }; myFeature.myProperty === "hello"; // true @@ -68,255 +49,191 @@ myFeature.myProperty === "hello"; // true myFeature.myMethod(); // "hello" myFeature.init({ - foo: "bar" + foo: "bar" }); myFeature.readSettings(); // { foo: "bar" } ``` -The object literal above is simply an object assigned to a variable. The object -has one property and several methods. All of the properties and methods are -public, so any part of your application can see the properties and call methods -on the object. While there is an init method, there's nothing requiring that it -be called before the object is functional. +The object literal above is simply an object assigned to a variable. The object has one property and several methods. All of the properties and methods are public, so any part of your application can see the properties and call methods on the object. While there is an init method, there's nothing requiring that it be called before the object is functional. -How would we apply this pattern to jQuery code? Let's say that we had this code -written in the traditional jQuery style: +How would we apply this pattern to jQuery code? Let's say that we had this code written in the traditional jQuery style: ``` -// clicking on a list item loads some content -// using the list item's ID and hides content -// in sibling list items +// Clicking on a list item loads some content using the +// list item's ID, and hides content in sibling list items $( document ).ready(function() { - - $("#myFeature li").append("
").click(function() { - - var $this = $( this ); - - var $div = $this.find("div"); - - $div.load( "foo.php?item=" + $this.attr("id"), function() { - - $div.show(); - - $this.siblings().find("div").hide(); - - } - - ); - - }); - + $( "#myFeature li" ).append( "
" ).click(function() { + var item = $( this ); + var div = item.find( "div" ); + div.load( "foo.php?item=" + item.attr( "id" ), function() { + div.show(); + item.siblings().find( "div" ).hide(); + }); + }); }); ``` -If this were the extent of our application, leaving it as-is would be fine. On -the other hand, if this was a piece of a larger application, we'd do well to -keep this functionality separate from unrelated functionality. We might also -want to move the URL out of the code and into a configuration area. Finally, we -might want to break up the chain to make it easier to modify pieces of the -functionality later. +If this were the extent of our application, leaving it as-is would be fine. On the other hand, if this was a piece of a larger application, we'd do well to keep this functionality separate from unrelated functionality. We might also want to move the URL out of the code and into a configuration area. Finally, we might want to break up the chain to make it easier to modify pieces of the functionality later. ``` // Using an object literal for a jQuery feature var myFeature = { - - init : function( settings ) { - - myFeature.config = { - $items : $("#myFeature li"), - $container : $("
"), - urlBase : "/foo.php?item=" - }; - - // allow overriding the default config - $.extend( myFeature.config, settings ); - - myFeature.setup(); - - }, - - setup : function() { - - myFeature.config.$items.each( myFeature.createContainer ).click( myFeature.showItem ); - - }, - - createContainer : function() { - - var $i = $( this ); - - var $c = myFeature.config.$container.clone().appendTo( $i ); - - $i.data( "container", $c ); - - }, - - buildUrl : function() { - - return myFeature.config.urlBase + myFeature.$currentItem.attr("id"); - - }, - - showItem : function() { - - var myFeature.$currentItem = $( this ); - - myFeature.getContent( myFeature.showContent ); - - }, - - getContent : function( callback ) { - - var url = myFeature.buildUrl(); - - myFeature.$currentItem.data("container").load( url, callback ); - - }, - - showContent : function() { - - myFeature.$currentItem.data("container").show(); - - myFeature.hideContent(); - - }, - - hideContent : function() { - - myFeature.$currentItem.siblings().each(function() { - - $( this ).data("container").hide(); - - }); - - } - + init: function( settings ) { + myFeature.config = { + items: $( "#myFeature li" ), + container: $( "
" ), + urlBase: "/foo.php?item=" + }; + + // Allow overriding the default config + $.extend( myFeature.config, settings ); + + myFeature.setup(); + }, + + setup: function() { + myFeature.config.items + .each( myFeature.createContainer ) + .click( myFeature.showItem ); + }, + + createContainer: function() { + var item = $( this ); + var container = myFeature.config.container + .clone() + .appendTo( item ); + item.data( "container", container ); + }, + + buildUrl: function() { + return myFeature.config.urlBase + myFeature.currentItem.attr( "id" ); + }, + + showItem: function() { + myFeature.currentItem = $( this ); + myFeature.getContent( myFeature.showContent ); + }, + + getContent: function( callback ) { + var url = myFeature.buildUrl(); + myFeature.currentItem.data( "container" ).load( url, callback ); + }, + + showContent: function() { + myFeature.currentItem.data( "container" ).show(); + myFeature.hideContent(); + }, + + hideContent: function() { + myFeature.currentItem.siblings().each(function() { + $( this ).data( "container" ).hide(); + }); + } }; $( document ).ready( myFeature.init ); ``` -The first thing you'll notice is that this approach is obviously far longer -than the original -- again, if this were the extent of our application, using an -object literal would likely be overkill. Assuming it's not the extent of our -application, though, we've gained several things: - -- We've broken our feature up into tiny methods. In the future, if we want to - change how content is shown, it's clear where to change it. In the original - code, this step is much harder to locate. -- We've eliminated the use of anonymous functions. -- We've moved configuration options out of the body of the code and put them in - a central location. -- We've eliminated the constraints of the chain, making the code easier to - refactor, remix, and rearrange. - -For non-trivial features, object literals are a clear improvement over a long -stretch of code stuffed in a `$( document ).ready()` block, as they get us -thinking about the pieces of our functionality. However, they aren't a whole -lot more advanced than simply having a bunch of function declarations inside of -that `$( document ).ready()` block. +The first thing you'll notice is that this approach is obviously far longer than the original — again, if this were the extent of our application, using an object literal would likely be overkill. Assuming it's not the extent of our application, though, we've gained several things: + +* We've broken our feature up into tiny methods. In the future, if we want to change how content is shown, it's clear where to change it. In the original code, this step is much harder to locate. +* We've eliminated the use of anonymous functions. +* We've moved configuration options out of the body of the code and put them in a central location. +* We've eliminated the constraints of the chain, making the code easier to refactor, remix, and rearrange. + +For non-trivial features, object literals are a clear improvement over a long stretch of code stuffed in a `$( document ).ready()` block, as they get us thinking about the pieces of our functionality. However, they aren't a whole lot more advanced than simply having a bunch of function declarations inside of that `$( document ).ready()` block. ### The Module Pattern -The module pattern overcomes some of the limitations of the object literal, -offering privacy for variables and functions while exposing a public API if -desired. +The module pattern overcomes some of the limitations of the object literal, offering privacy for variables and functions while exposing a public API if desired. ``` // The module pattern var feature = (function() { - // private variables and functions - var privateThing = "secret"; - var publicThing = "not secret"; - var changePrivateThing = function() { - privateThing = "super secret"; - }; + // Private variables and functions + var privateThing = "secret"; + var publicThing = "not secret"; - var sayPrivateThing = function() { - console.log( privateThing ); - changePrivateThing(); - }; + var changePrivateThing = function() { + privateThing = "super secret"; + }; - // public API - return { - publicThing: publicThing, - sayPrivateThing: sayPrivateThing - }; + var sayPrivateThing = function() { + console.log( privateThing ); + changePrivateThing(); + }; + // Public API + return { + publicThing: publicThing, + sayPrivateThing: sayPrivateThing + }; })(); feature.publicThing; // "not secret" - -// logs "secret" and changes the value -// of privateThing +// Logs "secret" and changes the value of privateThing feature.sayPrivateThing(); ``` -In the example above, we self-execute an anonymous function that returns an -object. Inside of the function, we define some variables. Because the variables -are defined inside of the function, we don't have access to them outside of the -function unless we put them in the return object. This means that no code -outside of the function has access to the `privateThing` variable or to the -`changePrivateThing` function. However, `sayPrivateThing` does have access to -`privateThing` and `changePrivateThing`, because both were defined in the same -scope as `sayPrivateThing`. +In the example above, we self-execute an anonymous function that returns an object. Inside of the function, we define some variables. Because the variables are defined inside of the function, we don't have access to them outside of the function unless we put them in the return object. This means that no code outside of the function has access to the `privateThing` variable or to the `changePrivateThing` function. However, `sayPrivateThing` does have access to `privateThing` and `changePrivateThing`, because both were defined in the same scope as `sayPrivateThing`. -This pattern is powerful because, as you can gather from the variable names, it -can give you private variables and functions while exposing a limited API -consisting of the returned object's properties and methods. +This pattern is powerful because, as you can gather from the variable names, it can give you private variables and functions while exposing a limited API consisting of the returned object's properties and methods. -Below is a revised version of the previous example, showing how we could create -the same feature using the module pattern while only exposing one public method -of the module, `showItemByIndex()`. +Below is a revised version of the previous example, showing how we could create the same feature using the module pattern while only exposing one public method of the module, `showItemByIndex()`. ``` // Using the module pattern for a jQuery feature $( document ).ready(function() { - - var feature = (function() { - - var $items = $("#myFeature li"); - var $container = $("
"); - var $currentItem = null; - var urlBase = "/foo.php?item="; - var createContainer = function() { - var $i = $( this ); - var $c = $container.clone().appendTo( $i ); - $i.data( "container", $c ); - }, - buildUrl = function() { - return urlBase + $currentItem.attr("id"); - }, - showItem = function() { - $currentItem = $( this ); - getContent( showContent ); - }, - showItemByIndex = function( idx ) { - $.proxy( showItem, $items.get( idx ) ); - }, - getContent = function( callback ) { - $currentItem.data("container").load( buildUrl(), callback ); - }, - showContent = function() { - $currentItem.data("container").show(); - hideContent(); - }, - hideContent = function() { - $currentItem.siblings().each(function() { - $( this ).data("container").hide(); - }); - }; - $items.each( createContainer ).click( showItem ); - - return { - showItemByIndex: showItemByIndex - }; - - })(); - - feature.showItemByIndex( 0 ); + var feature = (function() { + var items = $( "#myFeature li" ); + var container = $( "
" ); + var currentItem = null; + var urlBase = "/foo.php?item="; + + var createContainer = function() { + var item = $( this ); + var _container = container.clone().appendTo( item ); + item.data( "container", _container ); + }; + + var buildUrl = function() { + return urlBase + currentItem.attr( "id" ); + }; + + var showItem = function() { + currentItem = $( this ); + getContent( showContent ); + }; + + var showItemByIndex = function( idx ) { + $.proxy( showItem, items.get( idx ) )(); + }; + + var getContent = function( callback ) { + currentItem.data( "container" ).load( buildUrl(), callback ); + }; + + var showContent = function() { + currentItem.data( "container" ).show(); + hideContent(); + }; + + var hideContent = function() { + currentItem.siblings().each(function() { + $( this ).data( "container" ).hide(); + }); + }; + + items.each( createContainer ).click( showItem ); + + return { + showItemByIndex: showItemByIndex + }; + })(); + + feature.showItemByIndex( 0 ); }); ``` diff --git a/page/code-organization/deferreds.md b/page/code-organization/deferreds.md index 5e10b597..1c921b6e 100644 --- a/page/code-organization/deferreds.md +++ b/page/code-organization/deferreds.md @@ -1,114 +1,87 @@ ---- -title: Deferreds -level: advanced -source: http://msdn.microsoft.com/en-us/magazine/gg723713.aspx -attribution: - - Julian Aubourg - - Addy Osmani - - Andree Hansson ---- - -At a high-level, deferreds can be thought of as a way to represent -asynchronous operations which can take a long time to complete. They're the -asynchronous alternative to blocking functions and the general idea is -that rather than your application blocking while it awaits some request -to complete before returning a result, a deferred object can instead be -returned immediately. You can then attach callbacks to the deferred -object: they will be called once the request has actually completed. - -##Promises - -In its most basic form, a 'promise' is a model that provides a solution -for the concept of deferred (or future) results in software engineering. -The main idea behind it is something we've already covered: rather than -executing a call which may result in blocking, we instead return a -promise for a future value that will eventually be satisfied. - -If it helps to have an example here, consider that you are building a -web application which heavily relies on data from a third party API. A -common problem that's faced is having an unknown knowledge of the API -server's latency at a given time so it's possible that other parts of -your application may be blocked from running until a result from it is -returned. Deferreds provide a better solution to this problem, one which -is void of 'blocking' effects and completely decoupled. - -The [Promise/A](http://wiki.commonjs.org/wiki/Promises/A) proposal -defines a method called 'then' that can be used to register callbacks to -a promise and, thus, get the future result when it is available. The -pseudo-code for dealing with a third party API that returns a promise -may look like: + + +At a high-level, deferreds can be thought of as a way to represent asynchronous operations which can take a long time to complete. They're the asynchronous alternative to blocking functions and the general idea is that rather than your application blocking while it awaits some request to complete before returning a result, a deferred object can instead be returned immediately. You can then attach callbacks to the deferred object: they will be called once the request has actually completed. + +## Promises + +In its most basic form, a "promise" is a model that provides a solution for the concept of deferred (or future) results in software engineering. The main idea behind it is something we've already covered: rather than executing a call which may result in blocking, we instead return a promise for a future value that will eventually be satisfied. + +If it helps to have an example here, consider that you are building a web application which heavily relies on data from a third party API. A common problem that's faced is having an unknown knowledge of the API server's latency at a given time so it's possible that other parts of your application may be blocked from running until a result from it is returned. Deferreds provide a better solution to this problem, one which is void of "blocking" effects and completely decoupled. + +The [Promises/A](http://wiki.commonjs.org/wiki/Promises/A) proposal defines a method called "then" that can be used to register callbacks to a promise and, thus, get the future result when it is available. The pseudo-code for dealing with a third party API that returns a promise may look like: ``` -promise = callToAPI( arg1, arg2, ...); +var promise = callToAPI( arg1, arg2, ...); promise.then(function( futureValue ) { - /* handle futureValue */ + + // Handle futureValue + }); - + promise.then(function( futureValue ) { - /* do something else */ + + // Do something else + }); ``` -Furthermore, a promise can actually end up being in two different -states: +Furthermore, a promise can actually end up being in two different states: -- resolved: in which case data is available -- rejected: in which case something went wrong and no value is - available +* Resolved: in which case data is available +* Rejected: in which case something went wrong and no value is available -Thankfully, the 'then' method accepts two parameters: one for when the -promise was resolved, another for when the promise was rejected. If we -get back to pseudo-code, we may do things like: +Thankfully, the "then" method accepts two parameters: one for when the promise was resolved, another for when the promise was rejected. If we get back to pseudo-code, we may do things like: ``` -promise.then( function( futureValue ) { - /* we got a value */ -} , function() { - /* something went wrong */ -} ); +promise.then(function( futureValue ) { + + // We got a value + +}, function() { + + // Something went wrong + +}); ``` -In the case of certain applications, it is necessary to have several -results returned before your application can continue at all (for -example, displaying a dynamic set of options on a screen before a user -is able to select the option that interests them). Where this is the -case, a method called 'when' exists, which can be used to perform some -action once all the promises have been fully fulfilled: +In the case of certain applications, it is necessary to have several results returned before your application can continue at all (for example, displaying a dynamic set of options on a screen before a user is able to select the option that interests them). Where this is the case, a method called "when" exists, which can be used to perform some action once all the promises have been fully fulfilled: ``` when( - promise1, - promise2, - ... + promise1, + promise2, + ... ).then(function( futureValue1, futureValue2, ... ) { - /* all promises have completed and are resolved */ + + // All promises have completed and are resolved + }); ``` -A good example is a scenario where you may have multiple concurrent -animations that are being run. Without keeping track of each callback -firing on completion, it can be difficult to truly establish once all -your animations have finished running. Using promises and 'when' however -this is very straightforward as each of your animations can effectively -say 'we promise to let you know once we're done'. The compounded result -of this means it's a trivial process to execute a single callback once -the animations are done. For example: +A good example is a scenario where you may have multiple concurrent animations that are being run. Without keeping track of each callback firing on completion, it can be difficult to truly establish once all your animations have finished running. Using promises and "when" however this is very straightforward as each of your animations can effectively say "we promise to let you know once we're done". The compounded result of this means it's a trivial process to execute a single callback once the animations are done. For example: ``` -var promise1 = $("#id1").animate().promise(); -var promise2 = $("#id2").animate().promise(); +var promise1 = $( "#id1" ).animate().promise(); +var promise2 = $( "#id2" ).animate().promise(); when( - promise1, - promise2 -).then(function(){ - /* once both animations have completed - we can then run our additional logic */ + promise1, + promise2 +).then(function() { + + // Once both animations have completed + // we can then run our additional logic + }); ``` -This means that one can basically write non-blocking logic that can be -executed without synchronization. Rather than directly passing callbacks -to functions, something which can lead to tightly coupled interfaces, -using promises allows one to separate concerns for code that is -synchronous or asynchronous. +This means that one can basically write non-blocking logic that can be executed without synchronization. Rather than directly passing callbacks to functions, something which can lead to tightly coupled interfaces, using promises allows one to separate concerns for code that is synchronous or asynchronous. diff --git a/page/code-organization/deferreds/examples.md b/page/code-organization/deferreds/examples.md index 5ab68e62..d4b74e38 100644 --- a/page/code-organization/deferreds/examples.md +++ b/page/code-organization/deferreds/examples.md @@ -1,58 +1,45 @@ ---- -title: Deferred examples -level: advanced -source: http://msdn.microsoft.com/en-us/magazine/gg723713.aspx -attribution: - - Julian Aubourg - - Addy Osmani - - Andree Hansson ---- - -##Further Deferreds examples - -Deferreds are used behind the hood in Ajax but it doesn't mean they can't also -be used elsewhere. This section describes situations where deferreds will help -abstract away asynchronous behaviour and decouple our code. + + +## Further Deferreds examples + +Deferreds are used behind the hood in Ajax but it doesn't mean they can't also be used elsewhere. This section describes situations where deferreds will help abstract away asynchronous behavior and decouple our code. ### Caching #### Asynchronous cache -When it comes to asynchronous tasks, caching can be a bit demanding -since you have to make sure a task is only performed once for a given -key. As a consequence, the code has to somehow keep track of inbound -tasks. +When it comes to asynchronous tasks, caching can be a bit demanding since you have to make sure a task is only performed once for a given key. As a consequence, the code has to somehow keep track of inbound tasks. ``` $.cachedGetScript( url, callback1 ); $.cachedGetScript( url, callback2 ); ``` -The caching mechanism has to make sure the url is only requested once -even if the script isn't in cache yet. This shows some logic -to keep track of callbacks bound to a given url in order for the cache -system to properly handle both complete and inbound requests. +The caching mechanism has to make sure the URL is only requested once even if the script isn't in cache yet. This shows some logic to keep track of callbacks bound to a given URL in order for the cache system to properly handle both complete and inbound requests. ``` var cachedScriptPromises = {}; $.cachedGetScript = function( url, callback ) { - if ( !cachedScriptPromises[ url ] ) { - cachedScriptPromises[ url ] = $.Deferred(function( defer ) { - $.getScript( url ).then( defer.resolve, defer.reject ); - }).promise(); - } - return cachedScriptPromises[ url ].done( callback ); + if ( !cachedScriptPromises[ url ] ) { + cachedScriptPromises[ url ] = $.Deferred(function( defer ) { + $.getScript( url ).then( defer.resolve, defer.reject ); + }).promise(); + } + return cachedScriptPromises[ url ].done( callback ); }; ``` -One promise is cached per url. If there is no promise for the given url yet, -then a deferred is created and the request is issued. If it already exists, however, -the callback is attached to the existing deferred. The big advantage of this -solution is that it will handle both complete and inbound requests -transparently. Another advantage is that a deferred-based cache will deal with -failure gracefully. The promise will end up rejected which can be tested for by -providing an error callback: +One promise is cached per URL. If there is no promise for the given URL yet, then a deferred is created and the request is issued. When the request is complete, the deferred is resolved (with `defer.resolve`); if an error occurs, the deferred is rejected (with `defer.reject`). If the promise already exists, the callback is attached to the existing deferred; otherwise, the promise is first created and then the callback is attached. The big advantage of this solution is that it will handle both complete and inbound requests transparently. Another advantage is that a deferred-based cache will deal with failure gracefully. The promise will end up rejected which can be tested for by providing an error callback: ``` $.cachedGetScript( url ).then( successCallback, errorCallback ); @@ -60,35 +47,31 @@ $.cachedGetScript( url ).then( successCallback, errorCallback ); #### Generic asynchronous cache -It is also possible to make the code completely generic and build a -cache factory that will abstract out the actual task to be performed -when a key isn't in the cache yet: +It is also possible to make the code completely generic and build a cache factory that will abstract out the actual task to be performed when a key isn't in the cache yet: ``` $.createCache = function( requestFunction ) { - var cache = {}; - return function( key, callback ) { - if ( !cache[ key ] ) { - cache[ key ] = $.Deferred(function( defer ) { - requestFunction( defer, key ); - }).promise(); - } - return cache[ key ].done( callback ); - }; -} + var cache = {}; + return function( key, callback ) { + if ( !cache[ key ] ) { + cache[ key ] = $.Deferred(function( defer ) { + requestFunction( defer, key ); + }).promise(); + } + return cache[ key ].done( callback ); + }; +}; ``` -Now that the request logic is abstracted away, cachedGetScript can be rewritten -as follows: +Now that the request logic is abstracted away, `$.cachedGetScript()` can be rewritten as follows: ``` $.cachedGetScript = $.createCache(function( defer, url ) { - $.getScript( url ).then( defer.resolve, defer.reject ); + $.getScript( url ).then( defer.resolve, defer.reject ); }); ``` -This will work because every call to createCache will create a new cache -repository and return a new cache-retrieval function. +This will work because every call to `$.createCache()` will create a new cache repository and return a new cache-retrieval function. #### Image loading @@ -96,16 +79,16 @@ A cache can be used to ensure that the same image is not loaded multiple times. ``` $.loadImage = $.createCache(function( defer, url ) { - var image = new Image(); - function cleanUp() { - image.onload = image.onerror = null; - } - defer.then( cleanUp, cleanUp ); - image.onload = function() { - defer.resolve( url ); - }; - image.onerror = defer.reject; - image.src = url; + var image = new Image(); + function cleanUp() { + image.onload = image.onerror = null; + } + defer.then( cleanUp, cleanUp ); + image.onload = function() { + defer.resolve( url ); + }; + image.onerror = defer.reject; + image.src = url; }); ``` @@ -116,30 +99,27 @@ $.loadImage( "my-image.png" ).done( callback1 ); $.loadImage( "my-image.png" ).done( callback2 ); ``` -will work regardless of whether my-image.png has already been loaded or -not, or if it is actually in the process of being loaded. +will work regardless of whether `my-image.png` has already been loaded or not, or if it is actually in the process of being loaded. #### Caching Data API responses -API requests that are considered immutable during the lifetime of your -page are also perfect candidates. For instance, the following: +API requests that are considered immutable during the lifetime of your page are also perfect candidates. For instance, the following: ``` $.searchTwitter = $.createCache(function( defer, query ) { - $.ajax({ - url: "http://search.twitter.com/search.json", - data: { - q: query - }, - dataType: "jsonp", - success: defer.resolve, - error: defer.reject - }); + $.ajax({ + url: "http://search.twitter.com/search.json", + data: { + q: query + }, + dataType: "jsonp", + success: defer.resolve, + error: defer.reject + }); }); ``` -will allow you to perform searches on Twitter and cache them at the same -time: +will allow you to perform searches on Twitter and cache them at the same time: ``` $.searchTwitter( "jQuery Deferred", callback1 ); @@ -148,117 +128,94 @@ $.searchTwitter( "jQuery Deferred", callback2 ); #### Timing -This deferred-based cache is not limited to network requests; it can -also be used for timing purposes. +This deferred-based cache is not limited to network requests; it can also be used for timing purposes. -For instance, you may need to perform an action on the page after a -given amount of time so as to attract the user's attention to a specific -feature they may not be aware of or deal with a timeout (for a quiz -question for instance). While setTimeout is good for most use-cases it -doesn't handle the situation when the timer is asked for later, even -after it has theoretically expired. We can handle that with the -following caching system: +For instance, you may need to perform an action on the page after a given amount of time so as to attract the user's attention to a specific feature they may not be aware of or deal with a timeout (for a quiz question for instance). While `setTimeout()` is good for most use-cases it doesn't handle the situation when the timer is asked for later, even after it has theoretically expired. We can handle that with the following caching system: ``` var readyTime; - + $(function() { - readyTime = jQuery.now(); + readyTime = jQuery.now(); }); - + $.afterDOMReady = $.createCache(function( defer, delay ) { - delay = delay || 0; - $(function() { - var delta = $.now() - readyTime; - if ( delta >= delay ) { - defer.resolve(); - } else { - setTimeout( defer.resolve, delay - delta ); - } - }); + delay = delay || 0; + $(function() { + var delta = $.now() - readyTime; + if ( delta >= delay ) { + defer.resolve(); + } else { + setTimeout( defer.resolve, delay - delta ); + } + }); }); ``` -The new afterDOMReady helper method provides proper timing after the DOM -is ready while ensuring the bare minimum of timers will be used. If the -delay is already expired, any callback will be called right away. +The new `$.afterDOMReady()` helper method provides proper timing after the DOM is ready while ensuring the bare minimum of timers will be used. If the delay is already expired, any callback will be called right away. ### One-time event -While jQuery offers all the event binding one may need, it can become a -bit cumbersome to handle events that are only supposed to be dealt with -once. +While jQuery offers all the event binding one may need, it can become a bit cumbersome to handle events that are only supposed to be dealt with once. -For instance, you may wish to have a button that will open a panel the -first time it is clicked and leave it open afterwards or take special -initialization actions the first time said button is clicked. When -dealing with such a situation, one usually end up with code like this: +For instance, you may wish to have a button that will open a panel the first time it is clicked and leave it open afterward or take special initialization actions the first time said button is clicked. When dealing with such a situation, one usually ends up with code like this: ``` var buttonClicked = false; - + $( "#myButton" ).click(function() { - if ( !buttonClicked ) { - buttonClicked = true; - initializeData(); - showPanel(); - } + if ( !buttonClicked ) { + buttonClicked = true; + initializeData(); + showPanel(); + } }); ``` -then, later on, you may wish to take actions, but only if the panel is -opened: +then, later on, you may wish to take actions, but only if the panel is opened: ``` if ( buttonClicked ) { - /* perform specific action */ + + // Perform specific action + } ``` -This is a very coupled solution. If you want to add some other action, -you have to edit the bind code or just duplicate it all. If you don't, -your only option is to test for buttonClicked and you may lose that new -action because the buttonClicked variable may be false and your new code -may never be executed. +This is a very coupled solution. If you want to add some other action, you have to edit the bind code or just duplicate it all. If you don't, your only option is to test for `buttonClicked` and you may lose that new action because the `buttonClicked` variable may be `false` and your new code may never be executed. -We can do much better using deferreds (for simplification sake, the -following code will only work for a single element and a single event -type, but it can be easily generalized for full-fledged collections with -multiple event types): +We can do much better using deferreds (for simplification sake, the following code will only work for a single element and a single event type, but it can be easily generalized for full-fledged collections with multiple event types): ``` $.fn.bindOnce = function( event, callback ) { - var element = $( this[ 0 ] ), - defer = element.data( "bind_once_defer_" + event ); - if ( !defer ) { - defer = $.Deferred(); - function deferCallback() { - element.unbind( event, deferCallback ); - defer.resolveWith( this, arguments ); - } - element.bind( event, deferCallback ) - element.data( "bind_once_defer_" + event , defer ); - } - return defer.done( callback ).promise(); + var element = $( this[ 0 ] ), + defer = element.data( "bind_once_defer_" + event ); + + if ( !defer ) { + defer = $.Deferred(); + function deferCallback() { + element.unbind( event, deferCallback ); + defer.resolveWith( this, arguments ); + } + element.bind( event, deferCallback ) + element.data( "bind_once_defer_" + event , defer ); + } + + return defer.done( callback ).promise(); }; ``` The code works as follows: -- check if the element already has a deferred attached for the given - event -- if not, create it and make it so it is resolved when the event is - fired the first time around -- then attach the given callback to the deferred and return the - promise +* Check if the element already has a deferred attached for the given event +* if not, create it and make it so it is resolved when the event is fired the first time around +* then attach the given callback to the deferred and return the promise -While the code is definitely more verbose, it makes dealing with the -problem at hand much simpler in a compartmentalized and decoupled way. -But let's define a helper method first: +While the code is definitely more verbose, it makes dealing with the problem at hand much simpler in a compartmentalized and decoupled way. But let's define a helper method first: ``` $.fn.firstClick = function( callback ) { - return this.bindOnce( "click", callback ); + return this.bindOnce( "click", callback ); }; ``` @@ -266,7 +223,7 @@ Then the logic can be re-factored as follows: ``` var openPanel = $( "#myButton" ).firstClick(); - + openPanel.done( initializeData ); openPanel.done( showPanel ); ``` @@ -275,129 +232,113 @@ If an action should be performed only when a panel is opened later on: ``` openPanel.done(function() { - /* perform specific action */ + + // Perform specific action + }); ``` -Nothing is lost if the panel isn't opened yet, the action will just get -deferred until the button is clicked. +Nothing is lost if the panel isn't opened yet, the action will just get deferred until the button is clicked. ### Combining helpers -All of the samples above can seem a bit limited when looked at -separately. However, the true power of promises comes into play when you -mix them together. +All of the samples above can seem a bit limited when looked at separately. However, the true power of promises comes into play when you mix them together. #### Requesting panel content on first click and opening said panel -Following is the code for a button that, when clicked, opens a panel. -It requests its content over the wire and then fades the content in. Using -the helpers defined earlier, it could be defined as: +Following is the code for a button that, when clicked, opens a panel. It requests its content over the wire and then fades the content in. Using the helpers defined earlier, it could be defined as: ``` $( "#myButton" ).firstClick(function() { - var panel = $( "#myPanel" ); - $.when( - $.get( "panel.html" ), - panel.slideDownPromise() - ).done(function( ajaxResponse ) { - panel.html( ajaxResponse[ 0 ] ).fadeIn(); - }); + var panel = $( "#myPanel" ); + $.when( + $.get( "panel.html" ), + panel.slideDownPromise() + ).done(function( ajaxResponse ) { + panel.html( ajaxResponse[ 0 ] ).fadeIn(); + }); }); ``` #### Loading images in a panel on first click and opening said panel -Another possible goal is to have the panel fade in, only after the button -has been clicked and after all of the images have been loaded. +Another possible goal is to have the panel fade in, only after the button has been clicked and after all of the images have been loaded. -The html code for this would look something like: +The HTML code for this would look something like: ```
- - - - + + + +
``` -We use the data-src attribute to keep track of the real image location. -The code to handle our use case using our promise helpers is as follows: +We use the `data-src` attribute to keep track of the real image location. The code to handle our use case using our promise helpers is as follows: ``` $( "#myButton" ).firstClick(function() { - - var panel = $( "#myPanel" ), - promises = []; - - $( "img", panel ).each(function() { - var image = $( this ), - src = element.attr( "data-src" ); - if ( src ) { - promises.push( - $.loadImage( src ).then( function() { - image.attr( "src", src ); - }, function() { - image.attr( "src", "error.png" ); - } ) - ); - } - }); - - promises.push( - panel.slideDownPromise() - ); - - $.when.apply( null, promises ).done(function() { - panel.fadeIn(); - }); + var panel = $( "#myPanel" ), + promises = []; + + panel.find( "img" ).each(function() { + var image = $( this ), + src = element.attr( "data-src" ); + if ( src ) { + promises.push( + $.loadImage( src ).then(function() { + image.attr( "src", src ); + }, function() { + image.attr( "src", "error.png" ); + }) + ); + } + }); + + promises.push( panel.slideDownPromise() ); + + $.when.apply( null, promises ).done(function() { + panel.fadeIn(); + }); }); ``` -The trick here is to keep track of all the loadImage promises. We later -join them with the panel slideDown animation using $.when. So when the -button is first clicked, the panel will slideDown and the images will -start loading. Once the panel has finished sliding down and all the -images have been loaded, then, and only then, will the panel fade in. +The trick here is to keep track of all the `$.loadImage()` promises. We later join them with the panel `.slideDown()` animation using `$.when()`. So when the button is first clicked, the panel will slide down and the images will start loading. Once the panel has finished sliding down and all the images have been loaded, then, and only then, will the panel fade in. #### Loading images on the page after a specific delay -In order to implement deferred image display on the entire page, -the following format in HTML can be used. +In order to implement deferred image display on the entire page, the following format in HTML can be used. ``` - - - - + + + + ``` What it says is pretty straight-forward: -- load image1.png and show it immediately for the third image and - after one second for the first one -- load image2.png and show it after one second for the second image - and after two seconds for the fourth image - +* Load `image1.png` and show it immediately for the third image and after one second for the first one +* Load `image2.png` and show it after one second for the second image and after two seconds for the fourth image ``` $( "img" ).each(function() { - var element = $( this ), - src = element.attr( "data-src" ), - after = element.attr( "data-after" ); - if ( src ) { - $.when( - $.loadImage( src ), - $.afterDOMReady( after ) - ).then(function() { - element.attr( "src", src ); - }, function() { - element.attr( "src", "error.png" ); - } ).done(function() { - element.fadeIn(); - }); - } + var element = $( this ), + src = element.attr( "data-src" ), + after = element.attr( "data-after" ); + if ( src ) { + $.when( + $.loadImage( src ), + $.afterDOMReady( after ) + ).then(function() { + element.attr( "src", src ); + }, function() { + element.attr( "src", "error.png" ); + }).done(function() { + element.fadeIn(); + }); + } }); ``` @@ -405,23 +346,21 @@ In order to delay the loading of the images themselves: ``` $( "img" ).each(function() { - var element = $( this ), - src = element.attr( "data-src" ), - after = element.attr( "data-after" ); - if ( src ) { - $.afterDOMReady( after, function() { - $.loadImage( src ).then(function() { - element.attr( "src", src ); - }, function() { - element.attr( "src", "error.png" ); - } ).done(function() { - element.fadeIn(); - }); - } ); - } + var element = $( this ), + src = element.attr( "data-src" ), + after = element.attr( "data-after" ); + if ( src ) { + $.afterDOMReady( after, function() { + $.loadImage( src ).then(function() { + element.attr( "src", src ); + }, function() { + element.attr( "src", "error.png" ); + }).done(function() { + element.fadeIn(); + }); + }); + } }); ``` -Here, after the delay to be fulfilled then the image is loaded. It can make a -lot of sense when you want to limit the number or network requests on page -load. +Here, after the delay to be fulfilled then the image is loaded. It can make a lot of sense when you want to limit the number or network requests on page load. diff --git a/page/code-organization/deferreds/jquery-deferreds.md b/page/code-organization/deferreds/jquery-deferreds.md index e37c5a19..0c3621c8 100644 --- a/page/code-organization/deferreds/jquery-deferreds.md +++ b/page/code-organization/deferreds/jquery-deferreds.md @@ -1,112 +1,87 @@ ---- -title: jQuery Deferreds -level: advanced -source: http://msdn.microsoft.com/en-us/magazine/gg723713.aspx -attribution: - - Julian Aubourg - - Addy Osmani - - Andree Hansson ---- - -##jQuery Deferreds - -Deferreds were added as a part of a large rewrite of the ajax module, -led by Julian Auborg following the CommonJS Promises/A design. Whilst 1.5 and -above include deferred capabilities, former versions of jQuery had -jQuery.ajax() accept callbacks that would be invoked upon completion or -error of the request, but suffered from heavy coupling - the same -principle that would drive developers using other languages or toolkits -to opt for deferred execution. - -In practice what jQuery's version provides you with are several -enhancements to the way callbacks are managed, giving you significantly -more flexible ways to provide callbacks that can be invoked whether the -original callback dispatch has already fired or not. It is also worth -noting that jQuery's Deferred object supports having multiple callbacks -bound to the outcome of particular tasks (and not just one) where the -task itself can either be synchronous or asynchronous. - -At the heart of jQuery's implementation is jQuery.Deferred - a chainable -constructor which is able to create new deferred objects that can check -for the existence of a promise to establish whether the object can be -observed. It can also invoke callback queues and pass on the success of -synchronous and asynchronous functions. It's quite essential to note -that the default state of any Deferred object is unresolved. Callbacks -which may be added to it through .then() or .fail() are queued up and get -executed later on in the process. - -You are able to use Deferred objects in conjunction with the promise concept of -when(), implemented in jQuery as $.when() to wait for all of the Deferred -object's requests to complete executing (ie. for all of the promises to be -fulfilled). In technical terms, $.when() is effectively a way to execute -callbacks based on any number of promises that represent asynchronous events. - -An example of $.when() accepting multiple arguments can be seen below in -conjunction with .then(): + + +## jQuery Deferreds + +Deferreds were added as a part of a large rewrite of the Ajax module, led by Julian Aubourg following the CommonJS Promises/A design. Whilst 1.5 and above include deferred capabilities, former versions of jQuery had `jQuery.ajax()` accept callbacks that would be invoked upon completion or error of the request, but suffered from heavy coupling — the same principle that would drive developers using other languages or toolkits to opt for deferred execution. + +In practice what jQuery's version provides you with are several enhancements to the way callbacks are managed, giving you significantly more flexible ways to provide callbacks that can be invoked whether the original callback dispatch has already fired or not. It is also worth noting that jQuery's Deferred object supports having multiple callbacks bound to the outcome of particular tasks (and not just one) where the task itself can either be synchronous or asynchronous. + +At the heart of jQuery's implementation is `jQuery.Deferred` — a chainable constructor which is able to create new deferred objects that can check for the existence of a promise to establish whether the object can be observed. It can also invoke callback queues and pass on the success of synchronous and asynchronous functions. It's quite essential to note that the default state of any Deferred object is unresolved. Callbacks which may be added to it through `.then()` or `.fail()` are queued up and get executed later on in the process. + +You are able to use Deferred objects in conjunction with the promise concept of when(), implemented in jQuery as `$.when()` to wait for all of the Deferred object's requests to complete executing (i.e. for all of the promises to be fulfilled). In technical terms, `$.when()` is effectively a way to execute callbacks based on any number of promises that represent asynchronous events. + +An example of `$.when()` accepting multiple arguments can be seen below in conjunction with `.then()`: ``` -function successFunc(){ - console.log( "success!" ); -} +function successFunc() { + console.log( "success!" ); +} -function failureFunc(){ - console.log( "failure!" ); +function failureFunc() { + console.log( "failure!" ); } $.when( - $.ajax( "/main.php" ), - $.ajax( "/modules.php" ), - $.ajax( "/lists.php" ) + $.ajax( "/main.php" ), + $.ajax( "/modules.php" ), + $.ajax( "/lists.php" ) ).then( successFunc, failureFunc ); ``` -The $.when() implementation offered in jQuery is quite interesting as it not -only interprets deferred objects, but when passed arguments that are not -deferreds, it treats these as if they were resolved deferreds and executes any -callbacks (doneCallbacks) right away. It is also worth noting that jQuery's -deferred implementation, in addition to exposing deferred.then(), a jQuery -promise also supports the deferred.done() and deferred.fail() methods which can -also be used to add callbacks to the deferred's queues. - -We will now take a look at a code example that utilizes many of the deferred -features mentioned in the table presented earlier. Here is a very basic -application that consumes (1) an external news feed and (2) a reactions feed -for pulling in the latest comments via $.get() (which will return a promise). -The application also has a function (prepareInterface()) which returns a -promise to complete animating our containers for both the news and -reactions. +The `$.when()` implementation offered in jQuery is quite interesting as it not only interprets deferred objects, but when passed arguments that are not deferreds, it treats these as if they were resolved deferreds and executes any callbacks (`doneCallbacks`) right away. It is also worth noting that jQuery's deferred implementation, in addition to exposing `deferred.then()`, also supports the `deferred.done()` and `deferred.fail()` methods which can also be used to add callbacks to the deferred's queues. +We will now take a look at a code example that uses many deferred features. This very basic script begins by consuming (1) an external news feed and (2) a reactions feed for pulling in the latest comments via `$.get()` (which will return a promise-like object). When both requests are received, the `showAjaxedContent()` function is called. The `showAjaxedContent()` function returns a promise that is resolved when animating both containers has completed. When the `showAjaxedContent()` promise is resolved, `removeActiveClass()` is called. The `removeActiveClass()` returns a promise that is resolved inside a `setTimeout()` after 4 seconds have elapsed. Finally, after the `removeActiveClass()` promise is resolved, the last `then()` callback is called, provided no errors occurred along the way. ``` function getLatestNews() { - return $.get( "latestNews.php", function(data){ - console.log( "news data received" ); - $( ".news" ).html(data); - } ); + return $.get( "latestNews.php", function( data ) { + console.log( "news data received" ); + $( ".news" ).html( data ); + }); } function getLatestReactions() { - return $.get( "latestReactions.php", function(data){ - console.log( "reactions data received" ); - $( ".reactions" ).html(data); - } ); + return $.get( "latestReactions.php", function( data ) { + console.log( "reactions data received" ); + $( ".reactions" ).html( data ); + }); +} + +function showAjaxedContent() { + // The .promise() is resolved *once*, after all animations complete + return $( ".news, .reactions" ).slideDown( 500, function() { + // Called once *for each element* when animation completes + $(this).addClass( "active" ); + }).promise(); } -function prepareInterface() { - return $.Deferred(function( dfd ) { - var latest = $( ".news, .reactions" ); - latest.slideDown( 500, dfd.resolve ); - latest.addClass( "active" ); - }).promise(); +function removeActiveClass() { + return $.Deferred(function( dfd ) { + setTimeout(function () { + $( ".news, .reactions" ).removeClass( "active" ); + dfd.resolve(); + }, 4000); + }).promise(); } $.when( - getLatestNews(), - getLatestReactions(), - prepareInterface() -).then(function(){ - console.log( "fire after requests succeed" ); -}).fail(function(){ - console.log( "something went wrong!" ); + getLatestNews(), + getLatestReactions() +) +.then(showAjaxedContent) +.then(removeActiveClass) +.then(function() { + console.log( "Requests succeeded and animations completed" ); +}).fail(function() { + console.log( "something went wrong!" ); }); ``` diff --git a/page/code-organization/dont-repeat-yourself.md b/page/code-organization/dont-repeat-yourself.md index 320575dd..c01a1cab 100644 --- a/page/code-organization/dont-repeat-yourself.md +++ b/page/code-organization/dont-repeat-yourself.md @@ -1,42 +1,32 @@ ---- -title: Keep Things DRY -level: beginner -attribution: -source: http://jqfundamentals.com/legacy - - jQuery Fundamentals ---- + + Don't repeat yourself; if you're repeating yourself, you're doing it wrong. ``` // BAD -if ( $eventfade.data("currently") !== "showing" ) { - - $eventfade.stop(); - +if ( eventfade.data( "currently" ) !== "showing" ) { + eventfade.stop(); } -if ( $eventhover.data("currently") !== "showing" ) { - - $eventhover.stop(); - +if ( eventhover.data( "currently" ) !== "showing" ) { + eventhover.stop(); } -if ( $spans.data("currently") !== "showing" ) { - - $spans.stop(); - +if ( spans.data( "currently" ) !== "showing" ) { + spans.stop(); } // GOOD!! -var $elems = [ $eventfade, $eventhover, $spans ]; - -$.each( $elems, function( i, elem ) { - - if ( elem.data("currently") !== "showing" ) { - - elem.stop(); - - } +var elems = [ eventfade, eventhover, spans ]; +$.each( elems, function( i, elem ) { + if ( elem.data( "currently" ) !== "showing" ) { + elem.stop(); + } }); ``` diff --git a/page/code-organization/feature-browser-detection.md b/page/code-organization/feature-browser-detection.md index 58f5d095..c54b8ea9 100644 --- a/page/code-organization/feature-browser-detection.md +++ b/page/code-organization/feature-browser-detection.md @@ -1,11 +1,11 @@ ---- -title: Feature & Browser Detection -level: beginner ---- + ### Can I Use This Browser Feature? -There are a couple of common ways to check whether or not a particular feature is supported by a user's browser: +There are a couple of common ways to check whether or not a particular feature is supported by a user's browser: * Browser Detection * Specific Feature Detection @@ -14,19 +14,19 @@ In general, we recommend specific feature detection. Let's look at why. ### Browser Detection -Browser detection is a method where the browser's User Agent (UA) string is checked for a particular pattern unique to a browser family or version. For example, this is Chrome 18's UA string on Mac OS X Lion: +Browser detection is a method where the browser's User Agent (UA) string is checked for a particular pattern unique to a browser family or version. For example, this is Chrome 39's UA string on Mac OS X Yosemite: ``` -Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.142 Safari/535.19 +Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.99 Safari/537.36 ``` -Browser UA detection may check this string for something like "Chrome" or "Chrome/18" or any other part the developer feels identifies the browser they intend to target. +Browser UA detection may check this string for something like "Chrome" or "Chrome/39" or any other part the developer feels identifies the browser they intend to target. While this seems to be an easy solution, there are several problems: #### Other browsers other than your target may have the same issue. -If we target a specific browser for different functionality, we implicitly exclude any browser we did not account for. This is also not future-proof. If the browser we target receives a bug fix or change, we may not be able to discern between a 'working' and 'non-working' UA string. We may also need to update our test for each new release. This isn't a maintainable solution. +If we target a specific browser for different functionality, we implicitly exclude any browser we did not account for. This is also not future-proof. If the browser we target receives a bug fix or change, we may not be able to discern between a "working" and "non-working" UA string. We may also need to update our test for each new release. This isn't a maintainable solution. #### User Agents are unreliable. @@ -51,7 +51,6 @@ Now how would you go about doing that? There are several ways to go about feature detection: * Straight JavaScript -* $.support * A Helper Library #### Straight JavaScript @@ -61,84 +60,51 @@ Let's take a look at how to check whether or not a `` element exists in ``` // We want to show a graph in browsers that support canvas, // but a data table in browsers that don't. -var elem = document.createElement("canvas"); - -if ( elem.getContext && elem.getContext("2d") ) { - - showGraph(); +var elem = document.createElement( "canvas" ); +if ( elem.getContext && elem.getContext( "2d" ) ) { + showGraph(); } else { - - showTable(); - + showTable(); } ``` -This is a very simple way to provide conditional experiences, depending on the features present in the user's browser. We can extract this into a helper function for reuse, but still have to write a test for every feature we're concerned about. This can be time-consuming and error-prone. - -What if someone else wrote all of that for us? - -#### $.support - -jQuery performs many tests to determine feature support to allow cross-browser use of many of the features we've come to love. jQuery's internal feature detection can be accessed through [jQuery.support](http://api.jquery.com/jQuery.support/). - -However, we do not recommend this for general use. As the API page says: - -> Since jQuery requires these tests internally, they must be performed on every page load. Although some of these properties are documented below, they are not subject to a long deprecation/removal cycle and may be removed once internal jQuery code no longer needs them. - -This detection may be removed from jQuery without notice. That's reason enough not to use it. What other options do we have? +This is a very simple way to provide conditional experiences, depending on the features present in the user's browser. We can extract this into a helper function for reuse, but still have to write a test for every feature we're concerned about. This can be time-consuming and error-prone. Instead you might be interested in using a helper library. #### A Helper Library -Thankfully, there are some great helper libraries (like [Modernizr](http://modernizr.com)) that provide a simple, high-level API for determining if a browser has a specific feature available or not. +Thankfully, there are some great helper libraries (like [Modernizr](http://modernizr.com/)) that provide a simple, high-level API for determining if a browser has a specific feature available or not. For example, utilizing Modernizr, we are able to do the same canvas detection test with this code: ``` if ( Modernizr.canvas ) { - - showGraphWithCanvas(); - + showGraphWithCanvas(); } else { - - showTable(); - + showTable(); } ``` -That's it. Easy. +For more in-depth information on Modernizr, feel free to check out their [documentation](http://modernizr.com/docs/). ### Performance Considerations So, while the Modernizr syntax is great, it can end up being quite cumbersome to have several conditionals. Secondly, we're sending the code for both conditions to every browser, regardless if we'll need it or not. -The Modernizr object exposes a `load()` method that many prefer over the syntax mentioned previously. This is due to the another library that Modernizr now uses internally: [yepnope](http://yepnopejs.com/). Testing for canvas can now become something like this: - -``` -Modernizr.load({ - test: Modernizr.canvas, - yep : "canvas.js", - nope: "canvas-polyfill.js" -}); -``` - -Using the `load` method allows us to send only the required polyfills and code over the wire. You can also pass an array of objects as an argument to `.load()`, and it will serve the correct files to the correct audience. - -Additionally, Modernizr has a [production build configurator](http://modernizr.com/download/) that allows you to specify exactly what parts of Modernizr you want to include and exclude the parts you don't. +If you're using Modernizr, we highly encourage you to use the [build configurator](http://modernizr.com/download/), a tool that allows you to create custom builds of the library. You can exclude checks you don't need, which will save bytes and reduce the time it takes the page to load. Running every check that Modernizr can do, even when you don't need them, can slow down the page load. ### Other Resources #### Feature Detection Tools -- [modernizr](http://modernizr.com/) - conditionally check to see if a specific feature is available in a browser -- [html5please](http://html5please.com/) - use the new and shiny responsibly -- [html5please api](http://api.html5please.com/) - an API you can query to see how good (or bad) support for a specific feature is. -- [caniuse](http://caniuse.com/) - browser compatibility tables for HTML5, CSS3, SVG, etc… -- [yepnope](http://yepnopejs.com/) - conditional polyfill loader +* [modernizr](http://modernizr.com/) — Conditionally check to see if a specific feature is available in a browser. +* [html5please](http://html5please.com/) — Use the new and shiny responsibly. +* [html5please API](http://api.html5please.com/) — An API you can query to see how good (or bad) support for a specific feature is. +* [caniuse](http://caniuse.com/) — Browser compatibility tables for HTML5, CSS3, SVG, etc. #### Helpful Articles -- [Browser Feature Detection](https://developer.mozilla.org/en-US/docs/Browser_Feature_Detection) -- [Using Modernizr to detect HTML5 and CSS3 browser support](http://www.adobe.com/devnet/dreamweaver/articles/using-modernizr.html) -- [polyfilling the html5 gap](http://addyosmani.com/polyfillthehtml5gaps/slides/#1) by Addy Osmani -- [Feature, Browser, and Form Factor Detection: It's Good for the Environment](http://www.html5rocks.com/en/tutorials/detection/index.html) by Michael Mahemoff +* [Browser Feature Detection](https://developer.mozilla.org/en-US/docs/Browser_Feature_Detection) +* [Using Modernizr to detect HTML5 and CSS3 browser support](http://www.adobe.com/devnet/dreamweaver/articles/using-modernizr.html) +* [Polyfilling the HTML5 gaps](http://addyosmani.com/polyfillthehtml5gaps/slides/#1) by Addy Osmani +* [Feature, Browser, and Form Factor Detection: It's Good for the Environment](http://www.html5rocks.com/en/tutorials/detection/index.html) by Michael Mahemoff diff --git a/page/contributing.md b/page/contributing.md deleted file mode 120000 index 44fcc634..00000000 --- a/page/contributing.md +++ /dev/null @@ -1 +0,0 @@ -../CONTRIBUTING.md \ No newline at end of file diff --git a/page/contributing.md b/page/contributing.md new file mode 100644 index 00000000..3b75228c --- /dev/null +++ b/page/contributing.md @@ -0,0 +1,11 @@ + + +@partial(CONTRIBUTING.md) diff --git a/page/effects.md b/page/effects.md index 2c01d1c8..2d32c1ca 100644 --- a/page/effects.md +++ b/page/effects.md @@ -1,14 +1,14 @@ ---- -title: Effects -level: beginner -customFields: - - - key: "icon" - value: "picture" ---- -jQuery makes it trivial to add simple effects to your page. Effects can use -the built-in settings, or provide a customized duration. You can also create -custom animations of arbitrary CSS properties. + -For complete details on jQuery effects, visit the -[Effects documentation on api.jquery.com](http://api.jquery.com/category/effects/). +jQuery makes it trivial to add simple effects to your page. Effects can use the built-in settings, or provide a customized duration. You can also create custom animations of arbitrary CSS properties. + +For complete details on jQuery effects, visit the [Effects documentation on api.jquery.com](http://api.jquery.com/category/effects/). diff --git a/page/effects/custom-effects.md b/page/effects/custom-effects.md index 0d460c33..082f827c 100644 --- a/page/effects/custom-effects.md +++ b/page/effects/custom-effects.md @@ -1,53 +1,44 @@ ---- -title : Custom Effects with $.fn.animate -level: beginner -source: http://jqfundamentals.com/legacy -attribution: - - jQuery Fundamentals ---- -jQuery makes it possible to animate arbitrary CSS properties via the -`$.fn.animate` method. The `$.fn.animate` method lets you animate to a set -value, or to a value relative to the current value. + + +jQuery makes it possible to animate arbitrary CSS properties via the `.animate()` method. The `.animate()` method lets you animate to a set value, or to a value relative to the current value. ``` -// Custom effects with `$.fn.animate`"> -$("div.funtimes").animate({ - left : "+=50", - opacity : 0.25 - }, - // duration: - 300, - // callback: - function() { - console.log("done!"); - } -}); +// Custom effects with .animate() +$( "div.funtimes" ).animate( + { + left: "+=50", + opacity: 0.25 + }, + + // Duration + 300, + + // Callback to invoke when the animation is finished + function() { + console.log( "done!" ); + } +); ``` -
-Color-related properties cannot be animated with `$.fn.animate` using jQuery -out of the box. Color animations can easily be accomplished by including the -[color plugin](http://github.com/jquery/jquery-color). We'll discuss using -plugins later in the book. -
+**Note:** Color-related properties cannot be animated with `.animate()` using jQuery out of the box. Color animations can easily be accomplished by including the [color plugin](https://github.com/jquery/jquery-color). We'll discuss using plugins later in the book. ### Easing -Definition: Easing describes the manner in which an effect occurs — whether -the rate of change is steady, or varies over the duration of the animation. -jQuery includes only two methods of easing: swing and linear. If you want more -natural transitions in your animations, various easing plugins are available. +Definition: Easing describes the manner in which an effect occurs — whether the rate of change is steady, or varies over the duration of the animation. jQuery includes only two methods of easing: swing and linear. If you want more natural transitions in your animations, various easing plugins are available. -As of jQuery 1.4, it is possible to do per-property easing when using the -`$.fn.animate` method. +As of jQuery 1.4, it is possible to do per-property easing when using the `.animate()` method. ``` -// Per-property easing"> -$("div.funtimes").animate({ - left: [ "+=50", "swing" ], - opacity: [ 0.25, "linear" ] +// Per-property easing +$( "div.funtimes" ).animate({ + left: [ "+=50", "swing" ], + opacity: [ 0.25, "linear" ] }, 300 ); ``` -For more details on easing options, see -[Animation documentation on api.jquery.com](http://api.jquery.com/animate/). +For more details on easing options, see [Animation documentation on api.jquery.com](http://api.jquery.com/animate/). diff --git a/page/effects/intro-to-effects.md b/page/effects/intro-to-effects.md index a04cf3fc..abbcfbe1 100644 --- a/page/effects/intro-to-effects.md +++ b/page/effects/intro-to-effects.md @@ -1,193 +1,166 @@ ---- -title: Introduction to Effects -level: beginner ---- + ## Showing and Hiding Content -jQuery can show or hide content instantaneously with `$.fn.show` or `$.fn.hide`: +jQuery can show or hide content instantaneously with `.show()` or `.hide()`: ``` // Instantaneously hide all paragraphs -$("p").hide(); +$( "p" ).hide(); // Instantaneously show all divs that have the hidden style class -$("div.hidden").show(); +$( "div.hidden" ).show(); ``` -When jQuery hides an element, it sets its CSS `display` property to `none`. This means the content will have -zero width and height; it does not mean that the content will simply become transparent and leave an empty area on the page. +When jQuery hides an element, it sets its CSS `display` property to `none`. This means the content will have zero width and height; it does not mean that the content will simply become transparent and leave an empty area on the page. -jQuery can also show or hide content by means of animation effects. You can tell -`$.fn.show` and `$.fn.hide` to use animation in a couple of ways. One is to pass -in a string-valued argument of `slow`, `normal`, or `fast`: +jQuery can also show or hide content by means of animation effects. You can tell `.show()` and `.hide()` to use animation in a couple of ways. One is to pass in an argument of `'slow'`, `'normal'`, or `'fast'`: ``` // Slowly hide all paragraphs -$("p").hide("slow"); +$( "p" ).hide( "slow" ); // Quickly show all divs that have the hidden style class -$("div.hidden").show("fast"); +$( "div.hidden" ).show( "fast" ); ``` -If you prefer more direct control over the duration of the animation effect, you -can pass the desired duration in milliseconds to `$.fn.show` and `$.fn.hide`: +If you prefer more direct control over the duration of the animation effect, you can pass the desired duration in milliseconds to `.show()` and `.hide()`: ``` // Hide all paragraphs over half a second -$("p").hide( 500 ); +$( "p" ).hide( 500 ); // Show all divs that have the hidden style class over 1.25 seconds -$("div.hidden").show( 1250 ); +$( "div.hidden" ).show( 1250 ); ``` -Most developers pass in a number of milliseconds to have more precise control -over the duration. +Most developers pass in a number of milliseconds to have more precise control over the duration. -##Fade and Slide Animations +## Fade and Slide Animations -You may have noticed that `$.fn.show` and `$.fn.hide` use a combination of slide and fade effects -when showing and hiding content in an animated way. If you would rather show or hide content with -one effect or the other, there are additional methods that can help. `$.fn.slideDown` and `$.fn.slideUp` -show and hide content, respectively, using only a slide effect. Slide animations are accomplished by -rapidly making changes to an element's CSS `height` property. +You may have noticed that `.show()` and `.hide()` use a combination of slide and fade effects when showing and hiding content in an animated way. If you would rather show or hide content with one effect or the other, there are additional methods that can help. `.slideDown()` and `.slideUp()` show and hide content, respectively, using only a slide effect. Slide animations are accomplished by rapidly making changes to an element's CSS `height` property. ``` // Hide all paragraphs using a slide up animation over 0.8 seconds -$("p").slideUp( 800 ); +$( "p" ).slideUp( 800 ); // Show all hidden divs using a slide down animation over 0.6 seconds -$("div.hidden").slideDown( 600 ); -``` +$( "div.hidden" ).slideDown( 600 ); +``` -Similarly `$.fn.fadeIn` and `$.fn.fadeOut` show and hide content, respectively, by means of a fade -animation. Fade animations involve rapidly making changes to an element's CSS `opacity` property. +Similarly `.fadeIn()` and `.fadeOut()` show and hide content, respectively, by means of a fade animation. Fade animations involve rapidly making changes to an element's CSS `opacity` property. ``` // Hide all paragraphs using a fade out animation over 1.5 seconds -$("p").fadeOut( 1500 ); +$( "p" ).fadeOut( 1500 ); // Show all hidden divs using a fade in animation over 0.75 seconds -$("div.hidden").fadeIn( 750 ); -``` +$( "div.hidden" ).fadeIn( 750 ); +``` -##Changing Display Based on Current Visibility State +## Changing Display Based on Current Visibility State -jQuery can also let you change a content's visibility based on its current visibility state. `$.fn.toggle` -will show content that is currently hidden and hide content that is currently visible. You can pass the -same arguments to `$.fn.toggle` as you pass to any of the effects methods above. +jQuery can also let you change a content's visibility based on its current visibility state. `.toggle()` will show content that is currently hidden and hide content that is currently visible. You can pass the same arguments to `.toggle()` as you pass to any of the effects methods above. ``` // Instantaneously toggle the display of all paragraphs -$("p").toggle(); +$( "p" ).toggle(); // Slowly toggle the display of all images -$("img").toggle("slow"); +$( "img" ).toggle( "slow" ); // Toggle the display of all divs over 1.8 seconds -$("div").toggle( 1800 ); +$( "div" ).toggle( 1800 ); ``` -`$.fn.toggle` will use a combination of slide and fade effects, just as `$.fn.show` and `$.fn.hide` do. You can -toggle the display of content with just a slide or a fade using `$.fn.slideToggle` and `$.fn.fadeToggle`. +`.toggle()` will use a combination of slide and fade effects, just as `.show()` and `.hide()` do. You can toggle the display of content with just a slide or a fade using `.slideToggle()` and `.fadeToggle()`. ``` // Toggle the display of all ordered lists over 1 second using slide up/down animations -$("ol").slideToggle( 1000 ); +$( "ol" ).slideToggle( 1000 ); // Toggle the display of all blockquotes over 0.4 seconds using fade in/out animations -$("blockquote").fadeToggle( 400 ); +$( "blockquote" ).fadeToggle( 400 ); ``` -##Doing Something After an Animation Completes +## Doing Something After an Animation Completes -A common mistake when implementing jQuery effects is assuming that the execution of the next method in your -chain will wait until the animation runs to completion. +A common mistake when implementing jQuery effects is assuming that the execution of the next method in your chain will wait until the animation runs to completion. ``` // Fade in all hidden paragraphs; then add a style class to them (not quite right) -$("p.hidden").fadeIn( 750 ).addClass("lookAtMe"); +$( "p.hidden" ).fadeIn( 750 ).addClass( "lookAtMe" ); ``` -It is important to realize that `$.fn.fadeIn` above only *kicks off* the animation. Once started, the -animation is implemented by rapidly changing CSS properties in a JavaScript `setInterval()` loop. When -you call `$.fn.fadeIn`, it starts the animation loop and then returns the jQuery object, passing it along -to `$.fn.addClass` which will then add the `lookAtMe` style class while the animation loop is just -getting started. +It is important to realize that `.fadeIn()` above only *kicks off* the animation. Once started, the animation is implemented by rapidly changing CSS properties in a JavaScript `setInterval()` loop. When you call `.fadeIn()`, it starts the animation loop and then returns the jQuery object, passing it along to `.addClass()` which will then add the `lookAtMe` style class while the animation loop is just getting started. -To defer an action until after an animation has run to completion, you need to use an animation callback -function. You can specify your animation callback as the second argument passed to any of the -animation methods discussed above. For the code snippet above, we can implement a callback as follows: +To defer an action until after an animation has run to completion, you need to use an animation callback function. You can specify your animation callback as the second argument passed to any of the animation methods discussed above. For the code snippet above, we can implement a callback as follows: ``` // Fade in all hidden paragraphs; then add a style class to them (correct with animation callback) -$("p.hidden").fadeIn( 750, function(){ - // this = DOM element which has just finished being animated - $( this ).addClass("lookAtMe"); +$( "p.hidden" ).fadeIn( 750, function() { + // this = DOM element which has just finished being animated + $( this ).addClass( "lookAtMe" ); }); ``` -Note that you can use the keyword `this` to refer to the DOM element being animated. Also note -that the callback will be called for each element in the jQuery object. This means that if your -selector returns no elements, your animation callback will never run! You can solve this problem by -testing whether your selection returned any elements; if not, you can just run the callback immediately. +Note that you can use the keyword `this` to refer to the DOM element being animated. Also note that the callback will be called for each element in the jQuery object. This means that if your selector returns no elements, your animation callback will never run! You can solve this problem by testing whether your selection returned any elements; if not, you can just run the callback immediately. ``` // Run a callback even if there were no elements to animate -var $someElement = $("#nonexistent"); +var someElement = $( "#nonexistent" ); var cb = function() { - console.log("done!"); + console.log( "done!" ); }; -if ( $someElement.length ) { - $someElement.fadeIn( 300, cb ); +if ( someElement.length ) { + someElement.fadeIn( 300, cb ); } else { - cb(); + cb(); } ``` -##Managing Animation Effects +## Managing Animation Effects jQuery provides some additional features for controlling your animations: -### `$.fn.stop` +### `.stop()` -`$.fn.stop` will immediately terminate all animations running on the elements in your selection. You might give -end-users control over page animations by rigging a button they can click to stop the animations. +`.stop()` will immediately terminate all animations running on the elements in your selection. You might give end-users control over page animations by rigging a button they can click to stop the animations. ``` // Create a button to stop all animations on the page: -$("input").attr({ - type: "button", - value: "Stop All Animations" -}).on( "click", function() { - $("body *").filter(":animated").stop(); -}).appendTo( document.body ); +$( "" ) + .text( "Stop All Animations" ) + .on( "click", function() { + $( "body *" ).filter( ":animated" ).stop(); + }) + .appendTo( document.body ); ``` -### `$.fn.delay` +### `.delay()` -`$.fn.delay` is used to introduce a delay between successive animations. For example: +`.delay()` is used to introduce a delay between successive animations. For example: ``` -// Hide all level 1 headings over half a second; then wait for 1.5 seconds +// Hide all level 1 headings over half a second; then wait for 1.5 seconds // and reveal all level 1 headings over 0.3 seconds -$("h1").hide( 500 ).delay( 1500 ).show( 300 ); +$( "h1" ).hide( 500 ).delay( 1500 ).show( 300 ); ``` ### `jQuery.fx` -The `jQuery.fx` object has a number of properties that control how effects are implemented. `jQuery.fx.speeds` maps -the `slow`, `normal`, and `fast` duration arguments mentioned above to a specific -number of milliseconds. The default value of `jQuery.fx.speeds` is: +The `jQuery.fx` object has a number of properties that control how effects are implemented. `jQuery.fx.speeds` maps the `slow`, `normal`, and `fast` duration arguments mentioned above to a specific number of milliseconds. The default value of `jQuery.fx.speeds` is: ``` { - slow: 600, - fast: 200, - // Default speed, used for "normal" - _default: 400 + slow: 600, + fast: 200, + _default: 400 // Default speed, used for "normal" } ``` @@ -199,23 +172,15 @@ jQuery.fx.speeds.blazing = 100; jQuery.fx.speeds.excruciating = 60000; ``` -`jQuery.fx.interval` controls the number of frames per second that are -displayed in an animation. The default value is 13 milliseconds between -successive frames. You can set this a lower value for faster browsers -to make the animations run smoother. However this will mean more frames -per second and thus a higher computational load for the browser, so you -should be sure to test the performance implications of doing so thoroughly. +`jQuery.fx.interval` controls the number of frames per second that is displayed in an animation. The default value is 13 milliseconds between successive frames. You can set this to a lower value for faster browsers to make the animations run smoother. However this will mean more frames per second and thus a higher computational load for the browser, so you should be sure to test the performance implications of doing so thoroughly. -Finally, `jQuery.fx.off` can be set to true to disable all animations. Elements -will immediately be set to the target final state instead. This can be -especially useful when dealing with older browsers; you also may want to -provide the option to disable all animations to your users. +Finally, `jQuery.fx.off` can be set to true to disable all animations. Elements will immediately be set to the target final state instead. This can be especially useful when dealing with older browsers; you also may want to provide the option to disable all animations to your users. ``` -$("input").attr({ - type : "button", - value : "Disable Animations" -}).on( "click", function(){ - jQuery.fx.off = true; -}).appendTo( document.body ); +$( "" ) + .text( "Disable Animations" ) + .on( "click", function() { + jQuery.fx.off = true; + }) + .appendTo( document.body ); ``` diff --git a/page/effects/queue-and-dequeue-explained.md b/page/effects/queue-and-dequeue-explained.md index 3634c2ce..0bc59825 100644 --- a/page/effects/queue-and-dequeue-explained.md +++ b/page/effects/queue-and-dequeue-explained.md @@ -1,67 +1,118 @@ ---- -title: Queue & Dequeue Explained -level: beginner -source: http://jqueryfordesigners.com/api-queue-dequeue/ ---- + -When you use the animate and show, hide, slideUp, etc effect methods, you’re -adding a job on to the fx queue. By default, using queue and passing a function, -will add to the fx queue. So we’re creating our own bespoke animation step: +Queues are the foundation for all animations in jQuery, they allow a series functions to be executed asynchronously on an element. Methods such as `.slideUp()`, `.slideDown()`, `.fadeIn()`, and `.fadeOut()` all use `.animate()`, which leverages *queues* to build up the series of steps that will transition one or more CSS values throughout the duration of the animation. +We can pass a callback function to the `.animate()` method, which will execute once the animation has completed. + +``` +$( ".box" ) + .animate( { + height: 20 + }, "slow", function() { + $( "#title" ).html( "We're in the callback, baby!" ); + } ); ``` -$(".box").animate({ - height : 20 -}, "slow" ).queue(function() { - $("#title").html("We're in the animation, baby!"); +## Queues As Callbacks + +Instead of passing a callback as an argument, we can add another function to the *queue* that will act as our callback. This will execute after all of the steps in the animation have completed. -}); ``` +$( ".box" ) + .animate( { + height: 20 + }, "slow") + .queue( function() { + $( "#title" ).html( "We're in the animation, baby!" ); -As I said though, these methods come in pairs, so anything you add using queue, -you need to dequeue to allow the process to continue. In the code above, if I -chained more animations on, until I call `$( this ).dequeue()`, the subsequent -animations wouldn’t run: + // This tells jQuery to continue to the next item in the queue + $( this ).dequeue(); + } ); ``` -$(".box").animate({ - height : 20 -}, "slow" ).queue(function() { - $("#title").html("We're in the animation, baby!"); +In this example, the queued function will execute right after the animation. - $( this ).dequeue(); +jQuery does not have any insight into how the queue items function, so we need to call `.dequeue()`, which tells jQuery when to move to the next item in the queue. -}).animate({ - height: 150 -}); +Another way of *dequeuing* is by calling the function that is passed to your callback. That function will automatically call `.dequeue()` for you. + +``` +.queue( function( next ) { + console.log( "I fired!" ); + next(); +} ); ``` -Keeping in mind that the animation won’t continue until we’ve explicitly called -dequeue, we can easily create a pausing plugin, by adding a step in the queue -that sets a timer and triggers after n milliseconds, at which time, it dequeues -the element: +## Custom Queues +Up to this point all of our animation and queue examples have been using the default queue name which is `fx`. Elements can have multiple queues attached to them, and we can give each of these queues a different name. We can specify a custom queue name as the first argument to the `.queue()` method. + +``` +$( ".box" ) + .queue( "steps", function( next ) { + console.log( "Step 1" ); + next(); + } ) + .queue( "steps", function( next ) { + console.log( "Step 2" ); + next(); + } ) + .dequeue( "steps" ); ``` -$.fn.pause = function( n ) { - return this.queue(function() { +Notice that we have to call the `.dequeue()` method passing it the name of our custom queue to start the execution. Every queue except for the default, `fx`, has to be manually kicked off by calling `.dequeue()` and passing it the name of the queue. - var el = this; - setTimeout( function() { - return $( el ).dequeue(); - }, n ); - }); +## Clearing The Queue -}; +Since queues are just a set of ordered operations, our application may have some logic in place that needs to prevent the remaining queue entries from executing. We can do this by calling the `.clearQueue()` method, which will empty the queue. -$(".box").animate({ - height : 20 -}, "slow" ).pause( 1000 ).animate({ - height: 150 -}); ``` +$( ".box" ) + .queue( "steps", function( next ) { + console.log( "Will never log because we clear the queue" ); + next(); + } ) + .clearQueue( "steps" ) + .dequeue( "steps" ); +``` + +In this example, nothing will happen as we removed everything from the `steps` queue. + +Another way of clearing the queue is to call `.stop( true )`. That will stop the currently running animations and will clear the queue. +## Replacing The Queue -Remember that the first argument for queue and dequeue are `fx`, and that in -all of these examples I’m not including it because jQuery set the argument to `fx` by default - so I don’t have to specify it. +When you pass an array of functions as the second argument to `.queue()`, that array will replace the queue. + +``` +$( ".box" ) + .queue( "steps", function( next ) { + console.log( "I will never fire as we totally replace the queue" ); + next(); + } ) + .queue( "steps", [ + function( next ) { + console.log( "I fired!" ); + next(); + } + ] ) + .dequeue( "steps" ); +``` + +You can also call `.queue()` without passing it functions, which will return the queue of that element as an array. + +``` +$( ".box" ).queue( "steps", function( next ) { + console.log( "I fired!" ); + next(); +} ); + +console.log( $( ".box" ).queue( "steps" ) ); + +$( ".box" ).dequeue( "steps" ); +``` diff --git a/page/effects/uses-of-queue-and-dequeue.md b/page/effects/uses-of-queue-and-dequeue.md deleted file mode 100644 index a44d14ad..00000000 --- a/page/effects/uses-of-queue-and-dequeue.md +++ /dev/null @@ -1,263 +0,0 @@ ---- -title: The uses of jQuery .queue() and .dequeue() -level: beginner -source: http://gnarf.net/2010/09/30/the-uses-of-jquery-queue-and-dequeue/ ---- - -Queues in jQuery are used for animations. You can use them for any purpose you -like. They are an array of functions stored on a per element basis, using -`jQuery.data()`. The are First-In-First-Out (FIFO). You can add a function to the -queue by calling `.queue()`, and you remove (by calling) the functions using -`.dequeue()`. - -To understand the internal jQuery queue functions, reading the source and -looking at examples helps me out tremendously. One of the best examples of a -queue function I’ve seen is `.delay()`: - -``` -$.fn.delay = function( time, type ) { - - time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; - - type = type || "fx"; - - return this.queue( type, function() { - - var elem = this; - - setTimeout(function() { - - jQuery.dequeue( elem, type ); - - }, time ); - - }); - -}; -``` - -## The default queue – fx - -The default queue in jQuery is fx. The default queue has some special -properties that are not shared with other queues. - -- Auto Start: When calling `$(elem).queue(function() {});` the fx queue will - automatically dequeue the next function and run it if the queue hasn’t - started. -- ‘inprogress’ sentinel: Whenever you `dequeue()` a function from the fx queue, - it will `unshift()` (push into the first location of the array) the string - "inprogress" – which flags that the queue is currently being run. -- It’s the default! The fx queue is used by `.animate()` and all functions that - call it by default. - -
-If you are using a custom queue, you must manually `.dequeue()` the functions, they will not auto start! -
- -## Retrieving/Setting the queue - -You can retrieve a reference to a jQuery queue by calling `.queue()` without a -function argument. You can use the method if you want to see how many items are -in the queue. You can use push, pop, unshift, shift to manipulate the queue in -place. You can replace the entire queue by passing an array to the `.queue()` -function. - -## Quick Examples: - -``` -// lets assume $elem is a jQuery object that points to some element we are animating. -var queue = $elem.queue(); - -// remove the last function from the animation queue. -var lastFunc = queue.pop(); - -// insert it at the beginning: -queue.unshift( lastFunc ); - -// replace queue with the first three items in the queue -$elem.queue( queue.slice( 0, 3 ) ); -``` - -### An animation (fx) queue example: - -``` -$(function() { - - // lets do something with google maps: - var $map = $("#map_canvas"); - - var myLatlng = new google.maps.LatLng( -34.397, 150.644 ); - - var myOptions = { - zoom: 8, - center: myLatlng, - mapTypeId: google.maps.MapTypeId.ROADMAP - }; - - var geocoder = new google.maps.Geocoder(); - - var map = new google.maps.Map( $map[0], myOptions ); - - var resized = function() { - - // simple animation callback - let maps know we resized - google.maps.event.trigger( map, "resize" ); - - }; - - // wait 2 seconds - $map.delay( 2000 ); - - // resize the div: - $map.animate({ - width: 250, - height: 250, - marginLeft: 250, - marginTop:250 - }, resized ); - - // geocode something - $map.queue(function( next ) { - - // find stackoverflow's whois address: - geocoder.geocode( { - address: "55 Broadway New York NY 10006" - }, handleResponse ); - - function handleResponse( results, status ) { - - if ( status === google.maps.GeocoderStatus.OK ) { - - var location = results[ 0 ].geometry.location; - - map.setZoom( 13 ); - - map.setCenter( location ); - - new google.maps.Marker({ - map: map, - position: location - }); - - } - - // geocoder result returned, continue with animations: - next(); - - } - - }); - - // after we find stack overflow, wait 3 more seconds - $map.delay( 3000 ); - - // and resize the map again - $map.animate({ - width: 500, - height: 500, - marginLeft:0, - marginTop: 0 - }, resized ); -}); -``` - -### Queueing something like Ajax Calls: - -``` -// jQuery on an empty object, we are going to use this as our Queue -var ajaxQueue = $({}); - -$.ajaxQueue = function( ajaxOpts ) { - - // hold the original complete function - var oldComplete = ajaxOpts.complete; - - // queue our ajax request - ajaxQueue.queue(function( next ) { - - // create a complete callback to fire the next event in the queue - ajaxOpts.complete = function() { - - // fire the original complete if it was there - if ( oldComplete ) { - - oldComplete.apply( this, arguments ); - - } - - // run the next query in the queue - next(); - - }; - - // run the query - $.ajax( ajaxOpts ); - - }); - -}; - -// get each item we want to copy -$("#items li").each(function( idx ) { - - // queue up an ajax request - $.ajaxQueue({ - url: "/ajax_html_echo/", - data: { - html : "[" + idx + "] " + $( this ).html() - }, - type: "POST", - success: function( data ) { - - // Write to #output - $("#output").append( $("
  • ", { - html: data - })); - - } - }); - -}); -``` - -### Another custom queue example - -``` -var theQueue = $({}); // jQuery on an empty object - a perfect queue holder - -$.each( [1,2,3], function( i, num ) { - - // lets add some really simple functions to a queue: - theQueue.queue( "alerts", function(next) { - - // show something, and if they hit "yes", run the next function. - if ( confirm("index:" + i + " = " + num + "\nRun the next function?") ) { - - next(); - - } - - }); - -}); - -// create a button to run the queue: -$("" ).appendTo( document.body ); }); ``` -Consult the article on event delegation to see how to use `$.fn.on` so that -event behaviors will be extended to new elements without having to rebind them. +Consult the article on event delegation to see how to use `.on()` so that event behaviors will be extended to new elements without having to rebind them. ### Inside the Event Handler Function -Every event handling function receives an event object, which contains many -properties and methods. The event object is most commonly used to prevent the -default action of the event via the preventDefault method. However, the event -object contains a number of other useful properties and methods, including: +Every event handling function receives an event object, which contains many properties and methods. The event object is most commonly used to prevent the default action of the event via the `.preventDefault()` method. However, the event object contains a number of other useful properties and methods, including: #### pageX, pageY -The mouse position at the time the event occurred, relative to the top left corner of -the page display area (not the entire browser window). +The mouse position at the time the event occurred, relative to the top left corner of the page display area (not the entire browser window). #### type -The type of the event (e.g. "click"). +The type of the event (e.g., "click"). #### which @@ -85,13 +69,13 @@ The button or key that was pressed. Any data that was passed in when the event was bound. For example: ``` -// Event setup using the `$.fn.on` method with data -$('input').on( - 'change', - {foo : 'bar'}, // associate data with event binding - function(eventObject) { - console.log('An input value has changed! ', eventObject.data.foo); - } +// Event setup using the `.on()` method with data +$( "input" ).on( + "change", + { foo: "bar" }, // Associate data with event binding + function( eventObject ) { + console.log("An input value has changed! ", eventObject.data.foo); + } ); ``` @@ -115,123 +99,106 @@ Prevent the default action of the event (e.g. following a link). Stop the event from bubbling up to other elements. -In addition to the event object, the event handling function also has access to -the DOM element that the handler was bound to via the keyword `this`. To turn -the DOM element into a jQuery object that we can use jQuery methods on, we -simply do `$(this)`, often following this idiom: +In addition to the event object, the event handling function also has access to the DOM element that the handler was bound to via the keyword `this`. To turn the DOM element into a jQuery object that we can use jQuery methods on, we simply do `$( this )`, often following this idiom: ``` -var $this = $(this); +var element = $( this ); ``` A fuller example would be: ``` // Preventing a link from being followed -$('a').click(function(eventObject) { - var $this = $(this); - if ($this.attr('href').match(/evil/)) { - eventObject.preventDefault(); - $this.addClass('evil'); - } +$( "a" ).click(function( eventObject ) { + var elem = $( this ); + if ( elem.attr( "href" ).match( /evil/ ) ) { + eventObject.preventDefault(); + elem.addClass( "evil" ); + } }); ``` ### Setting Up Multiple Event Responses -Quite often elements in your application will be bound to multiple events. If -multiple events are to share the same handling function, you can provide the event types -as a space-separated list to `$.fn.on`: +Quite often elements in your application will be bound to multiple events. If multiple events are to share the same handling function, you can provide the event types as a space-separated list to `.on()`: ``` // Multiple events, same handler -$('input').on( - 'click change', // bind listeners for multiple events - function() { - console.log('An input was clicked or changed!') - } +$( "input" ).on( + "click change", // Bind handlers for multiple events + function() { + console.log( "An input was clicked or changed!" ); + } ); ``` -When each event has its own handler, you can pass an object into `$.fn.on` with one or -more key/value pairs, with the key being the event name and the value being the function -to handle the event. +When each event has its own handler, you can pass an object into `.on()` with one or more key/value pairs, with the key being the event name and the value being the function to handle the event. ``` // Binding multiple events with different handlers -$('p').on({ - 'click': function() { console.log('clicked!'); }, - 'mouseover': function() { console.log('hovered!'); } +$( "p" ).on({ + "click": function() { console.log( "clicked!" ); }, + "mouseover": function() { console.log( "hovered!" ); } }); ``` ### Namespacing Events -For complex applications and for plugins you share with others, it can be -useful to namespace your events so you don't unintentionally disconnect events -that you didn't or couldn't know about. +For complex applications and for plugins you share with others, it can be useful to namespace your events so you don't unintentionally disconnect events that you didn't or couldn't know about. ``` // Namespacing events -$('p').on('click.myNamespace', function() { /* ... */ }); -$('p').off('click.myNamespace'); -$('p').off('.myNamespace'); // unbind all events in the namespace +$( "p" ).on( "click.myNamespace", function() { /* ... */ } ); +$( "p" ).off( "click.myNamespace" ); +$( "p" ).off( ".myNamespace" ); // Unbind all events in the namespace ``` ### Tearing Down Event Listeners -To remove an event listener, you use the `$.fn.off` method and pass in -the event type to off. If you attached a named function to the event, then -you can isolate the event tear down to just that named function by passing it as the -second argument. +To remove an event listener, you use the `.off()` method and pass in the event type to off. If you attached a named function to the event, then you can isolate the event tear down to just that named function by passing it as the second argument. ``` // Tearing down all click handlers on a selection -$('p').off('click'); +$( "p" ).off( "click" ); ``` ``` // Tearing down a particular click handler, using a reference to the function -var foo = function() { console.log('foo'); }; -var bar = function() { console.log('bar'); }; +var foo = function() { console.log( "foo" ); }; +var bar = function() { console.log( "bar" ); }; -$('p').on('click', foo).on('click', bar); -$('p').off('click', bar); // foo is still bound to the click event +$( "p" ).on( "click", foo ).on( "click", bar ); +$( "p" ).off( "click", bar ); // foo is still bound to the click event ``` ### Setting Up Events to Run Only Once -Sometimes you need a particular handler to run only once — after that, you may -want no handler to run, or you may want a different handler to run. jQuery -provides the `$.fn.one` method for this purpose. +Sometimes you need a particular handler to run only once — after that, you may want no handler to run, or you may want a different handler to run. jQuery provides the `.one()` method for this purpose. ``` -// Switching handlers using the `$.fn.one` method -$('p').one('click', firstClick); +// Switching handlers using the `.one()` method +$( "p" ).one( "click", firstClick ); + +function firstClick() { + console.log( "You just clicked this for the first time!" ); -function firstClick(){ - console.log('You just clicked this for the first time!'); - // Now set up the new handler for subsequent clicks; - // omit this step if no further click responses are needed - $(this).click(function() { console.log('You have clicked this before!'); }); + // Now set up the new handler for subsequent clicks; + // omit this step if no further click responses are needed + $( this ).click( function() { console.log( "You have clicked this before!" ); } ); } ``` -Note that in the code snippet above, the `firstClick` function will be executed for -the first click on *each* paragraph element rather than the function being removed from -*all* paragraphs when *any* paragraph is clicked for the first time. +Note that in the code snippet above, the `firstClick` function will be executed for the first click on *each* paragraph element rather than the function being removed from *all* paragraphs when *any* paragraph is clicked for the first time. -`$.fn.one` can also be used to bind multiple events: +`.one()` can also be used to bind multiple events: ``` -// Using $.fn.one to bind several events -$('input[id]').one('focus mouseover keydown', firstEvent); +// Using .one() to bind several events +$( "input[id]" ).one( "focus mouseover keydown", firstEvent); -function firstEvent(eventObject){ - console.log('A ' + eventObject.type + ' event occurred for the first time on the input with id ' + this.id) +function firstEvent( eventObject ) { + console.log( "A " + eventObject.type + " event occurred for the first time on the input with id " + this.id ); } ``` -In this case, the `firstEvent` function will be executed once *for each event*. For the snippet above, this means -that once an input element gains focus, the handler function will still execute for the first keydown event on that -element. +In this case, the `firstEvent` function will be executed once *for each event*. For the snippet above, this means that once an input element gains focus, the handler function will still execute for the first keydown event on that element. diff --git a/page/events/event-delegation.md b/page/events/event-delegation.md index 2391af74..7dff6ef6 100644 --- a/page/events/event-delegation.md +++ b/page/events/event-delegation.md @@ -1,103 +1,102 @@ ---- -title : Understanding Event Delegation -level: intermediate -source: http://jqfundamentals.com/legacy -attribution: - - jQuery Fundamentals ---- - -Say you have to add new line items to your page, given the following HTML: + + +## Introduction + +Event delegation allows us to attach a single event listener, to a parent element, that will fire for all descendants matching a selector, whether those descendants exist now or are added in the future. + +## Example + +For the remainder of the lesson, we will reference the following HTML structure: ``` - -
    - -
    - + + + ``` -We need to attach the same event handler to multiple elements. In this example we want to attach an event that will log the text of the anchor tag to the console whenever it is clicked. +When an anchor in our `#list` group is clicked, we want to log its text to the console. Normally we could directly bind to the click event of each anchor using the `.on()` method: -We can attach a direct bind click event to each `
  • ` using the `.on()` method, that will alert the text inside of it by doing the following: ``` -// attach a directly bound event -$("#list a").on( "click", function( event ) { - event.preventDefault(); - console.log( $( this ).text() ); +// Attach a directly bound event handler +$( "#list a" ).on( "click", function( event ) { + event.preventDefault(); + console.log( $( this ).text() ); }); ``` -While this works perfectly fine, there are drawbacks. Consider this: +While this works perfectly fine, there are drawbacks. Consider what happens when we add a new anchor after having already bound the above listener: + ``` -// add a new element on to our existing list -$("#list").append("
  • Item #101
  • "); +// Add a new element on to our existing list +$( "#list" ).append( "
  • Item #5
  • " ); ``` -If we were to click our newly added item, nothing would happen. This is because of the directly bound event that we attached previously. Direct events are only attached to elements at the time we called the `.on()` method for our existing collection of ``"s, that is only the ``"s that were found when we call `$()` + +If we were to click our newly added item, nothing would happen. This is because of the directly bound event handler that we attached previously. Direct events are only attached to elements at the time the `.on()` method is called. In this case, since our new anchor did not exist when `.on()` was called, it does not get the event handler. ## Event Propagation -Understanding how events propagate is an important factor in being able to leverage Event Delegation. Any time an anchor tags is clicked, a *click* event is fired for the: + +Understanding how events propagate is an important factor in being able to leverage Event Delegation. Any time one of our anchor tags is clicked, a *click* event is fired for that anchor, and then bubbles up the DOM tree, triggering each of its parent click event handlers: * `` * `
  • ` -* `
      ` -* `
      ` +* `
        ` +* `
        ` * `` * `` * *document* root -Anytime one of these links is clicked you can think of it as if you were clicking the entire document body. This is called *event bubbling* or *event propagation*. +This means that anytime you click one of our bound anchor tags, you are effectively clicking the entire document body! This is called *event bubbling* or *event propagation*. + +Since we know how events bubble, we can create a *delegated* event: -Since we know how events bubble we can created a delegated event that listens for a specific event to happen on our element ``` -// attach a delegated event -$("#list").on( "click", "a", function( event ) { - event.preventDefault(); - console.log( $( this ).text() ); +// Attach a delegated event handler +$( "#list" ).on( "click", "a", function( event ) { + event.preventDefault(); + console.log( $( this ).text() ); }); ``` -Notice for the second parameter to the `.on()` method we are telling it which selector to listen for. Now when a *click* event is triggered on our `
          `, our delegated event will check to see if the triggering element matches our selector (`"a"`). If it does, our anonymous function will execute. We have now attached a single *click* event listener to our `
            ` instead of an unknown number of directly bound events on our ``"s. -Now lets say that whenever a link is clicked we want to check and see if the `href` attribute starts with "http" and if it does we want to set the `target` attribute to `_blank`. -``` -// attach a delegated event -$("#list").on( "click", "a", function( event ) { - - var $elem = $( this ); +Notice how we have moved the `a` part from the selector to the second parameter position of the `.on()` method. This second, selector parameter tells the handler to listen for the specified event, and when it hears it, check to see if the triggering element for that event matches the second parameter. In this case, the triggering event is our anchor tag, which matches that parameter. Since it matches, our anonymous function will execute. We have now attached a single *click* event listener to our `
              ` that will listen for clicks on its descendant anchors, instead of attaching an unknown number of directly bound events to the existing anchor tags only. - if( $elem.is("[href^=http]") ) { +### Using the Triggering Element - $elem.attr( "target", "_blank" ); - - } +What if we wanted to open the link in a new window if that link is an external one (as denoted here by beginning with "http")? +``` +// Attach a delegated event handler +$( "#list" ).on( "click", "a", function( event ) { + var elem = $( this ); + if ( elem.is( "[href^='http']" ) ) { + elem.attr( "target", "_blank" ); + } }); ``` -This simply passes the `.is()` method a selector to see if the element"s `href` attributes starts with "http". Also we have removed the `event.preventDefault();` statement, this is because we want the default action to happen (which is to following the `href`) -We can actually take this a step further and make our code simpler and more concise by allowing the selector argument to `.on()` do our logic for us. -``` -// attach a delegated event with a more refined selector -$("#list").on( "click", "a[href^=http]", function( event ) { +This simply passes the `.is()` method a selector to see if the `href` attribute of the element starts with "http". We have also removed the `event.preventDefault();` statement as we want the default action to happen (which is to follow the `href`). - $( this ).attr( "target", "_blank" ); +We can actually simplify our code by allowing the selector parameter of `.on()` do our logic for us: +``` +// Attach a delegated event handler with a more refined selector +$( "#list" ).on( "click", "a[href^='http']", function( event ) { + $( this ).attr( "target", "_blank" ); }); ``` -##Summary -Event delegation refers to the process of using event bubbling to handle events at a higher level in the DOM than the element on which the event originated. It allows us to attach a single event listener for elements that exist now or in the future. +## Summary + +Event delegation refers to the process of using event propagation (bubbling) to handle events at a higher level in the DOM than the element on which the event originated. It allows us to attach a single event listener for elements that exist now or in the future. diff --git a/page/events/event-extensions.md b/page/events/event-extensions.md index 314387b0..39f9bd7d 100644 --- a/page/events/event-extensions.md +++ b/page/events/event-extensions.md @@ -1,22 +1,19 @@ ---- -title: jQuery Event Extensions -level: advanced ---- -jQuery offers several ways to extend its event system to provide custom functionality when events are attached to elements. Internally in jQuery, these extensions are primarily used to ensure that standard events such as `submit` and `change` behave consistently across browsers. However, they can also be used to define new events with custom behavior. + -This document covers the extensions available starting with jQuery 1.7; a sparsely documented subset of this functionality has been available since jQuery 1.3 but the differences in functionality are extensive. For an overview of special events in earlier versions, see [Ben Alman's jQuery Special Events][1] article. +jQuery offers several ways to extend its event system to provide custom functionality when events are attached to elements. Internally in jQuery, these extensions are primarily used to ensure that standard events such as `submit` and `change` behave consistently across browsers. However, they can also be used to define new events with custom behavior. - [1]: http://benalman.com/news/2010/03/jquery-special-events/ +This document covers the extensions available starting with jQuery 1.7; a sparsely documented subset of this functionality has been available since jQuery 1.3 but the differences in functionality are extensive. For an overview of special events in earlier versions, see [Ben Alman's jQuery Special Events](http://benalman.com/news/2010/03/jquery-special-events/) article. -> **Note:** jQuery event extensions are an advanced feature; they require deeper knowledge of both browser behavior and jQuery internals than most of the API. Most users of jQuery will not need to use event extensions, and those who do should use them with care. For example, on a large project with third-party plugins, changing the behavior of standard events such as `click` or `mouseover` can cause serious compatibility issues. +
              **Note:** jQuery event extensions are an advanced feature; they require a deeper knowledge of both browser behavior and jQuery internals than most of the API. Most users of jQuery will not need to use event extensions, and those who do should use them with care. For example, on a large project with third-party plugins, changing the behavior of standard events such as `click` or `mouseover` can cause serious compatibility issues.
              ### Events overview and general advice -When writing an event extension, it is essential to understand the flow of events through jQuery's internal event system. For a description of the event system from the API level, including a discussion of event delegation, see the [`.on()`][2] method. +When writing an event extension, it is essential to understand the flow of events through jQuery's internal event system. For a description of the event system from the API level, including a discussion of event delegation, see the [`.on()`](http://api.jquery.com/on/) method. - [2]: http://api.jquery.com/on - -To simplify event management, jQuery only attaches a single event handler per element per event type (using addEventListener on W3C-compliant browsers or attachEvent on older IE) and then dispatches to event handlers that are attached via jQuery's APIs. For example, if three "click" event handlers are attached to an element, jQuery attaches its own handler when the first handler is attached and adds the user's event handler to a list to be executed when the event occurs. For subsequent event handlers, jQuery only adds them to its own internal list since it has already called the browser to attach its solitary handler. Conversely, jQuery removes its own event handler from the browser when the final event of a particular type is removed from the element. +To simplify event management, jQuery only attaches a single event handler per element per event type (using `addEventListener` on W3C-compliant browsers or `attachEvent` on older IE) and then dispatches to event handlers that are attached via jQuery's APIs. For example, if three "click" event handlers are attached to an element, jQuery attaches its own handler when the first handler is attached and adds the user's event handler to a list to be executed when the event occurs. For subsequent event handlers, jQuery only adds them to its own internal list since it has already called the browser to attach its solitary handler. Conversely, jQuery removes its own event handler from the browser when the final event of a particular type is removed from the element. An event can be a *native* event defined by the W3C and fired by the browser in response to something such as a user clicking a mouse button or pressing a key. It can also be a *custom* event, triggered only by code via jQuery's `.trigger()` or `.triggerHandler()` methods. Code can also trigger native browser events, which is convenient for simulating user actions. @@ -30,41 +27,39 @@ Although jQuery's event system is oriented towards delivering DOM events to DOM ### jQuery.event.props: Array -jQuery defines an [Event object][3] that represents a cross-browser subset of the information available when an event occurs. The `jQuery.event.props` property is an array of string names for properties that are always copied when jQuery processes a *native* browser event. (Events fired in code by `.trigger()` do not use this list, since the code can construct a `jQuery.Event` object with the needed values and trigger using that object.) - - [3]: http://api.jquery.com/jQuery.Event/ +jQuery defines an [Event object](https://api.jquery.com/category/events/event-object/) that represents a cross-browser subset of the information available when an event occurs. The `jQuery.event.props` property is an array of string names for properties that are always copied when jQuery processes a *native* browser event. (Events fired in code by `.trigger()` do not use this list, since the code can construct a `jQuery.Event` object with the needed values and trigger using that object.) -To add a property name to this list, use `jQuery.event.props.push("newPropertyName")`. However, be aware that every event processed by jQuery will now attempt to copy this property name from the native browser event to jQuery's constructed event. If the property does not exist for that event type, it will get an undefined value. Adding many properties to this list can significantly reduce event delivery performance, so for infrequently-needed properties it is more efficient to use the value directly from `event.originalEvent` instead. If properties must be copied, you are strongly advised to use `jQuery.event.fixHooks` as of version 1.7. +To add a property name to this list, use `jQuery.event.props.push( "newPropertyName" )`. However, be aware that every event processed by jQuery will now attempt to copy this property name from the native browser event to jQuery's constructed event. If the property does not exist for that event type, it will get an undefined value. Adding many properties to this list can significantly reduce event delivery performance, so for infrequently-needed properties it is more efficient to use the value directly from `event.originalEvent` instead. If properties must be copied, you are strongly advised to use `jQuery.event.fixHooks` as of version 1.7. ### jQuery.event.fixHooks: Object The `fixHooks` interface provides a per-event-type way to extend or normalize the event object that jQuery creates when it processes a *native* browser event. A `fixHooks` entry is an object that has two properties, each being optional: -props: Array -: Strings representing properties that should be copied from the browser's event object to the jQuery event object. If omitted, no additional properties are copied beyond the standard ones that jQuery copies and normalizes (e.g., `event.target` and `event.relatedTarget`). +`props`: Array: +Strings representing properties that should be copied from the browser's event object to the jQuery event object. If omitted, no additional properties are copied beyond the standard ones that jQuery copies and normalizes (e.g. `event.target` and `event.relatedTarget`). -filter: Function( event, originalEvent ) -: jQuery calls this function after it constructs the `jQuery.Event` object, copies standard properties from `jQuery.event.props`, and copies the `fixHooks`-specific props (if any) specified above. The function can create new properties on the event object or modify existing ones. The second argument is the browser's native event object, which is also available in `event.originalEvent`. +`filter`: Function( event, originalEvent ): +jQuery calls this function after it constructs the `jQuery.Event` object, copies standard properties from `jQuery.event.props`, and copies the `fixHooks`-specific props (if any) specified above. The function can create new properties on the event object or modify existing ones. The second argument is the browser's native event object, which is also available in `event.originalEvent`. Note that for all events, the browser's native event object is available in `event.originalEvent`; if the jQuery event handler examines the properties there instead of jQuery's normalized `event` object, there is no need to create a `fixHooks` entry to copy or modify the properties. -For example, to set a hook for the "drop" event that copies the "dataTransfer" property, assign an object to `jQuery.event.fixHooks.drop`: +For example, to set a hook for the "drop" event that copies the `dataTransfer` property, assign an object to `jQuery.event.fixHooks.drop`: ``` jQuery.event.fixHooks.drop = { - props: [ "dataTransfer" ] + props: [ "dataTransfer" ] }; ``` -Since `fixHooks` are an advanced feature and rarely used externally, jQuery does not include code or interfaces to deal with conflict resolution. If there is a chance that some other code may be assigning `fixHooks` to the same events, the code should check for an existing hook and take appropriate measures. A simple solution might look like this: +Since `fixHooks` is an advanced feature and rarely used externally, jQuery does not include code or interfaces to deal with conflict resolution. If there is a chance that some other code may be assigning `fixHooks` to the same events, the code should check for an existing hook and take appropriate measures. A simple solution might look like this: ``` if ( jQuery.event.fixHooks.drop ) { - throw new Error("Someone else took the jQuery.event.fixHooks.drop hook!"); + throw new Error( "Someone else took the jQuery.event.fixHooks.drop hook!" ); } jQuery.event.fixHooks.drop = { - props: [ "dataTransfer" ] + props: [ "dataTransfer" ] }; ``` @@ -74,15 +69,15 @@ When there are known cases of different plugins wanting to attach to the drop ho var existingHook = jQuery.event.fixHooks.drop; if ( !existingHook ) { - jQuery.event.fixHooks.drop = { - props: [ "dataTransfer" ] - }; + jQuery.event.fixHooks.drop = { + props: [ "dataTransfer" ] + }; } else { - if ( existingHook.props ) { - existingHook.props.push( "dataTransfer" ); - } else { - existingHook.props = [ "dataTransfer" ]; - } + if ( existingHook.props ) { + existingHook.props.push( "dataTransfer" ); + } else { + existingHook.props = [ "dataTransfer" ]; + } } ``` @@ -98,43 +93,43 @@ Indicates whether this event type should be bubbled when the `.trigger()` method #### bindType: String, delegateType: String -When defined, these string properties specify that a special event should be handled like another event type until the event is delivered. The bindType is used if the event is attached directly, and the delegateType is used for delegated events. These types are generally DOM event types, and *should not* be a special event themselves. +When defined, these string properties specify that a special event should be handled like another event type until the event is delivered. The `bindType` is used if the event is attached directly, and the `delegateType` is used for delegated events. These types are generally DOM event types, and *should not* be a special event themselves. The behavior of these properties is easiest to see with an example. Assume a special event defined as follows: ``` jQuery.event.special.pushy = { - bindType: "click", - delegateType: "click" + bindType: "click", + delegateType: "click" }; ``` When these properties are defined, the following behavior occurs in the jQuery event system: -* Event handlers for the "pushy" event are actually attached to "click" -- both directly bound and delegated events. +* Event handlers for the "pushy" event are actually attached to "click" — both directly bound and delegated events. * Special event hooks for "click" are called if they exist, *except* the `handle` hook for "pushy" is called when an event is delivered if one exists. * Event handlers for "pushy" must be removed using the "pushy" event name, and are unaffected if "click" events are removed from the same elements. So given the special event above, this code shows that a pushy isn't removed by removing clicks. That might be an effective way to defend against an ill-behaved plugin that didn't namespace its removal of click events, for example: ``` -var $p = $("p"); +var elem = $( "p" ); -$p.on( "click", function( e ) { - $("body").append( "I am a " + e.type + "!" )); +elem.on( "click", function( event ) { + $( "body" ).append( "I am a " + event.type + "!" ); }); -$p.on( "pushy", function( e ) { - $("body").append( "I am pushy but still a " + e.type + "!" ); +elem.on( "pushy", function( event ) { + $( "body" ).append( "I am pushy but still a " + event.type + "!" ); }); -$p.trigger("click"); // triggers both handlers +elem.trigger( "click" ); // Triggers both handlers -$p.off("click"); +elem.off( "click" ); -$p.trigger("click"); // still triggers "pushy" +elem.trigger( "click" ); // Still triggers "pushy" -$p.off("pushy"); +elem.off( "pushy" ); ``` These two properties are often used in conjunction with a `handle` hook function; the hook might, for example, change the event name from "click" to "pushy" before calling event handlers. See below for an example. @@ -144,28 +139,28 @@ These two properties are often used in conjunction with a `handle` hook function Many of the special event hook functions below are passed a `handleObj` object that provides more information about the event, how it was attached, and its current state. This object and its contents should be treated as read-only data, and only the properties below are documented for use by special event handlers. For the discussion below, assume an event is attached with this code: ``` -$(".dialog").on( "click.myPlugin", "button", { - mydata: 42 +$( ".dialog" ).on( "click.myPlugin", "button", { + mydata: 42 }, myHandler ); ``` -type: String -: The type of event, such as `"click"`. When special event mapping is used via `bindType` or `delegateType`, this will be the mapped type. +`type`: String: +The type of event, such as `"click"`. When special event mapping is used via `bindType` or `delegateType`, this will be the mapped type. -origType: String -: The original type name (in this case, `"click"`) regardless of whether it was mapped via bindType or delegateType. So when a "pushy" event is mapped to "click" its `origType` would be "pushy". See the examples in those special event properties above for more detail. +`origType`: String: +The original type name (in this case, `"click"`) regardless of whether it was mapped via `bindType` or `delegateType`. So when a "pushy" event is mapped to "click" its `origType` would be "pushy". See the examples in those special event properties above for more detail. -namespace: String -: Namespace(s), if any, provided when the event was attached, such as `"myPlugin"`. When multiple namespaces are given, they are separated by periods and sorted in ascending alphabetical order. If no namespaces are provided, this property is an empty string. +`namespace`: String: +Namespace(s), if any, provided when the event was attached, such as `"myPlugin"`. When multiple namespaces are given, they are separated by periods and sorted in ascending alphabetical order. If no namespaces are provided, this property is an empty string. -selector: String -: For delegated events, this is the selector used to filter descendant elements and determine if the handler should be called. In the example it is `"button"`. For directly bound events, this property is `null`. +`selector`: String: +For delegated events, this is the selector used to filter descendant elements and determine if the handler should be called. In the example it is `"button"`. For directly bound events, this property is `null`. -data: Object -: The data, if any, passed to jQuery during event binding, e.g., `{ myData: 42 }`. If the data argument was omitted or `undefined`, this property is `undefined` as well. +`data`: Object: +The data, if any, passed to jQuery during event binding, e.g. `{ myData: 42 }`. If the data argument was omitted or `undefined`, this property is `undefined` as well. -handler: function( event: jQuery.Event ) -: Event handler function passed to jQuery during event binding; in the example it is a reference to `myHandler`. If `false` was passed during event binding, the handler refers to a single shared function that simply returns `false`. +`handler`: function( event: jQuery.Event ): +Event handler function passed to jQuery during event binding; in the example it is a reference to `myHandler`. If `false` was passed during event binding, the handler refers to a single shared function that simply returns `false`. #### setup: function( data: Object, namespaces, eventHandle: function ) @@ -191,11 +186,11 @@ When an event handler is removed from an element using an API such as `.off()`, Called when the `.trigger()` or `.triggerHandler()` methods are used to trigger an event for the special type from code, as opposed to events that originate from within the browser. The `this` keyword will be the element being triggered, and the event argument will be a `jQuery.Event` object constructed from the caller's input. At minimum, the event type, data, namespace, and target properties are set on the event. The data argument represents additional data passed by `.trigger()` if present. -The trigger hook is called early in the process of triggering an event, just after the `jQuery.Event` object is constructed and before any handlers have been called. It can process the triggered event in any way, for example by calling `event.stopPropagation()` or `event.preventDefault()` before returning. If the hook returns `false`, jQuery does not perform any further event triggering actions and returns immediately. Otherwise, it performs the normal trigger processing, calling any event handlers for the element and bubbling the event (unless propagation is stopped in advance or noBubble was specified for the special event) to call event handlers attached to parent elements. +The trigger hook is called early in the process of triggering an event, just after the `jQuery.Event` object is constructed and before any handlers have been called. It can process the triggered event in any way, for example by calling `event.stopPropagation()` or `event.preventDefault()` before returning. If the hook returns `false`, jQuery does not perform any further event triggering actions and returns immediately. Otherwise, it performs the normal trigger processing, calling any event handlers for the element and bubbling the event (unless propagation is stopped in advance or `noBubble` was specified for the special event) to call event handlers attached to parent elements. #### _default: function( event: jQuery.Event, data: Object ) -When the `.trigger()` method finishes running all the event handlers for an event, it also looks for and runs any method on the target object by the same name unless of the handlers called `event.preventDefault()`. So, `.trigger("submit")` will execute the `submit()` method on the element if one exists. When a _default hook is specified, the hook is called just prior to checking for and executing the element's default method. If this hook returns the value `false` the element's default method will be called; otherwise it is not. +When the `.trigger()` method finishes running all the event handlers for an event, it also looks for and runs any method on the target object by the same name unless of the handlers called `event.preventDefault()`. So, `.trigger( "submit" )` will execute the `submit()` method on the element if one exists. When a `_default` hook is specified, the hook is called just prior to checking for and executing the element's default method. If this hook returns the value `false` the element's default method will be called; otherwise it is not. #### handle: function( event: jQuery.Event, data: Object ) @@ -212,29 +207,29 @@ The hook stores the current click count in the data object, so multiclick handle ``` jQuery.event.special.multiclick = { - delegateType: "click", - bindType: "click", - handle: function( event ) { - var handleObj = event.handleObj; - var targetData = jQuery.data( event.target ); - var ret = null; - - // If a multiple of the click count, run the handler - targetData.clicks = ( targetData.clicks || 0 ) + 1; - - if ( targetData.clicks % event.data === 0 ) { - event.type = handleObj.origType; - ret = handleObj.handler.apply( this, arguments ); - event.type = handleObj.type; - return ret; - } - } + delegateType: "click", + bindType: "click", + handle: function( event ) { + var handleObj = event.handleObj; + var targetData = jQuery.data( event.target ); + var ret = null; + + // If a multiple of the click count, run the handler + targetData.clicks = ( targetData.clicks || 0 ) + 1; + + if ( targetData.clicks % event.data.clicks === 0 ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = handleObj.type; + return ret; + } + } }; // Sample usage -$("p").on( "multiclick", { - clicks: 3 +$( "p" ).on( "multiclick", { + clicks: 3 }, function( event ) { - alert("clicked 3 times"); + alert( "clicked 3 times" ); }); ``` diff --git a/page/events/event-helpers.md b/page/events/event-helpers.md index 59a5e698..209b1ae6 100644 --- a/page/events/event-helpers.md +++ b/page/events/event-helpers.md @@ -1,45 +1,23 @@ ---- -title: Event Helpers -level: beginner -source: http://jqfundamentals.com/legacy -attribution: - - jQuery Fundamentals ---- -jQuery offers two event-related helper functions that save you a few keystrokes. + -### `$.fn.hover` +jQuery offers a few event-related helper functions that save you a few keystrokes. Here is an example of one, the `.hover()` function. -The `$.fn.hover` method lets you pass one or two functions to be run when the -`mouseenter` and `mouseleave` events occur on an element. If you pass one -function, it will be run for both events; if you pass two functions, the first -will run for `mouseenter`, and the second will run for `mouseleave`. +### `.hover()` -
              -Prior to jQuery 1.4, the `$.fn.hover` method required two functions. -
              +The [`.hover()`](http://api.jquery.com/hover/) method lets you pass one or two functions to be run when the `mouseenter` and `mouseleave` events occur on an element. If you pass one function, it will be run for both events; if you pass two functions, the first will run for `mouseenter`, and the second will run for `mouseleave`. + +**Note:** Prior to jQuery 1.4, the `.hover()` method required two functions. ``` // The hover helper function -$("#menu li").hover(function() { - - $( this ).toggleClass("hover"); - +$( "#menu li" ).hover(function() { + $( this ).toggleClass( "hover" ); }); ``` -### `$.fn.toggle` - -The `$.fn.toggle` method is triggered by the "click" event and accepts two or -more functions. Each time the click event occurs, the next function in the -list is called. Generally, `$.fn.toggle` is used with just two functions; -however, it will accept an unlimited number of functions. Be careful, though: -providing a long list of functions can be difficult to debug. - -``` -// The toggle helper function -$("p.expander").toggle( function() { - $( this ).prev().addClass("open"); -}, function() { - $( this ).prev().removeClass("open"); -}); -``` +You can find more helper functions on the [API site for Events](https://api.jquery.com/category/events/). \ No newline at end of file diff --git a/page/events/handling-events.md b/page/events/handling-events.md index 491b3df6..d1ca45af 100644 --- a/page/events/handling-events.md +++ b/page/events/handling-events.md @@ -1,165 +1,141 @@ ---- -title : Handling Events -level: beginner -source: http://jqfundamentals.com/legacy -attribution: - - jQuery Fundamentals ---- -jQuery provides a method `.on()` to -respond to any event on the selected elements. This is called an _event binding_. -Although `.on()` isn't the only method provided for event binding, it is a best -practice to use this for jQuery 1.7+. To learn more, [read more about -the evolution of event binding in jQuery](/events/history-of-events). - -The on method provides several useful features: -
              + + +jQuery provides a method `.on()` to respond to any event on the selected elements. This is called an _event binding_. Although `.on()` isn't the only method provided for event binding, it is a best practice to use this for jQuery 1.7+. To learn more, [read more about the evolution of event binding in jQuery](/events/history-of-events/). + +The `.on()` method provides several useful features: + +- [Bind any event triggered on the selected elements to an event handler](#simple-event-binding) +- [Bind multiple events to one event handler](#many-events-but-only-one-event-handler) +- [Bind multiple events and multiple handlers to the selected elements](#many-events-and-handlers) +- [Use details about the event in the event handler](#the-event-object) +- [Pass data to the event handler for custom events](#passing-data-to-the-event-handler) +- [Bind events to elements that will be rendered in the future](#binding-events-to-elements-that-don-39-t-exist-yet) + ### Examples -#### Simple event binding +#### Simple event binding + ``` // When any

              tag is clicked, we expect to see '

              was clicked' in the console. -$("p").on( "click", function() { - console.log("

              was clicked"); +$( "p" ).on( "click", function() { + console.log( "

              was clicked" ); }); ``` -#### Many events, but only one event handler +#### Many events, but only one event handler -Suppose you want to trigger the same event whenever the mouse hovers over or leaves -the selected elements. The best practice for this is to use "mouseenter mouseleave". -Note the difference between this and the next example. +Suppose you want to trigger the same event whenever the mouse hovers over or leaves the selected elements. The best practice for this is to use "mouseenter mouseleave". Note the difference between this and the next example. ``` -// When a user focuses on or changes any input element, we expect a console message -// bind to multiple events -$("div").on( "mouseenter mouseleave", function() { - console.log("mouse hovered over or left a div"); +// When a user focuses on or changes any input element, +// we expect a console message bind to multiple events +$( "div" ).on( "mouseenter mouseleave", function() { + console.log( "mouse hovered over or left a div" ); }); ``` -#### Many events and handlers +#### Many events and handlers -Suppose that instead you want different event handlers for when the mouse enters and -leaves an element. This is more common than the previous example. For example, if you -want to show and hide a tooltip on hover, you would use this. +Suppose that instead you want different event handlers for when the mouse enters and leaves an element. This is more common than the previous example. For example, if you want to show and hide a tooltip on hover, you would use this. `.on()` accepts an object containing multiple events and handlers. ``` -$("div").on({ - mouseenter: function() { - console.log("hovered over a div"); - }, - mouseleave: function() { - console.log("mouse left a div"); - }, - click: function() { - console.log("clicked on a div"); - } +$( "div" ).on({ + mouseenter: function() { + console.log( "hovered over a div" ); + }, + mouseleave: function() { + console.log( "mouse left a div" ); + }, + click: function() { + console.log( "clicked on a div" ); + } }); ``` -#### The event object +#### The event object -Handling events can be tricky. It's often helpful to use the extra information contained -in the event object passed to the event handler for more control. To become familiar with -the event object, use this code to inspect it in your browser console after you click on -a `

              ` in the page. For a breakdown of the event object, see -Inside the Event Handling Function. +Handling events can be tricky. It's often helpful to use the extra information contained in the event object passed to the event handler for more control. To become familiar with the event object, use this code to inspect it in your browser console after you click on a `
              ` in the page. For a breakdown of the event object, see [Inside the Event Handling Function](/events/inside-event-handling-function/). ``` -$("div").on( "click", function( event ) { - console.log("event object:"); - console.dir( event ); +$( "div" ).on( "click", function( event ) { + console.log( "event object:" ); + console.dir( event ); }); ``` -#### Passing data to the event handler +#### Passing data to the event handler You can pass your own data to the event object. ``` -$("p").on( "click", { - foo: "bar" +$( "p" ).on( "click", { + foo: "bar" }, function( event ) { - console.log( "event data: " + event.data.foo + " (should be 'bar')" ); + console.log( "event data: " + event.data.foo + " (should be 'bar')" ); }); ``` -#### Binding events to elements that don't exist yet +#### Binding events to elements that don't exist yet -This is called _event delegation_. Here's an example just for completeness, but see the -page on Event Delegation for a full explanation. +This is called _event delegation_. Here's an example just for completeness, but see the page on [Event Delegation](/events/event-delegation/) for a full explanation. ``` -$("ul").on( "click", "li", function() { - console.log("Something in a
                was clicked, and we detected that it was an
              • element."); +$( "ul" ).on( "click", "li", function() { + console.log( "Something in a
                  was clicked, and we detected that it was an
                • element." ); }); ``` ### Connecting Events to Run Only Once -Sometimes you need a particular handler to run only once — after that, you may -want no handler to run, or you may want a different handler to run. jQuery -provides the `.one()` method for this purpose. +Sometimes you need a particular handler to run only once — after that, you may want no handler to run, or you may want a different handler to run. jQuery provides the `.one()` method for this purpose. ``` // Switching handlers using the `.one()` method -$("p").one( "click", function() { - console.log("You just clicked this for the first time!"); - $( this ).click(function() { - console.log("You have clicked this before!"); - }); +$( "p" ).one( "click", function() { + console.log( "You just clicked this for the first time!" ); + $( this ).click(function() { + console.log( "You have clicked this before!" ); + }); }); ``` -The `.one()` method is especially useful if you need to do some complicated -setup the first time an element is clicked, but not subsequent times. +The `.one()` method is especially useful if you need to do some complicated setup the first time an element is clicked, but not subsequent times. -`.one()` accepts the same arguments as `on()` which means it supports multiple events to one -or multiple handlers, passing custom data and event delegation. +`.one()` accepts the same arguments as `.on()` which means it supports multiple events to one or multiple handlers, passing custom data and event delegation. ### Disconnecting Events -Although all the fun of jQuery occurs in the `.on()` method, it's counterpart is just as important -if you want to be a responsible developer. `.off()` cleans up that event -binding when you don't need it anymore. Complex user interfaces with lots of event bindings -can bog down browser performance, so using the `.off()` method diligently is a best practice to -ensure that you only have the event bindings that you need, when you need them. +Although all the fun of jQuery occurs in the `.on()` method, its counterpart is just as important if you want to be a responsible developer. `.off()` cleans up that event binding when you don't need it anymore. Complex user interfaces with lots of event bindings can bog down browser performance, so using the `.off()` method diligently is a best practice to ensure that you only have the event bindings that you need, when you need them. ``` // Unbinding all click handlers on a selection -$("p").off("click"); +$( "p" ).off( "click" ); ``` ``` // Unbinding a particular click handler, using a reference to the function var foo = function() { - console.log("foo"); + console.log( "foo" ); }; var bar = function() { - console.log("bar"); + console.log( "bar" ); }; -$("p").on( "click", foo ).on( "click", bar ); +$( "p" ).on( "click", foo ).on( "click", bar ); // foo will stay bound to the click event -$("p").off( "click", bar ); +$( "p" ).off( "click", bar ); ``` ### Namespacing Events -For complex applications and for plugins you share with others, it can be -useful to namespace your events so you don't unintentionally disconnect events -that you didn't or couldn't know about. For details, see Event Namespacing. - - +For complex applications and for plugins you share with others, it can be useful to namespace your events so you don't unintentionally disconnect events that you didn't or couldn't know about. For details, see [Event Namespacing](/events/event-basics/#namespacing-events). diff --git a/page/events/history-of-events.md b/page/events/history-of-events.md index 3ebfe1d5..280749bf 100644 --- a/page/events/history-of-events.md +++ b/page/events/history-of-events.md @@ -1,68 +1,75 @@ ---- -title: History of jQuery Events -level: intermediate ---- -Throughout the evolution of jQuery the means of event binding has changed for various reasons ranging from performance to semantics. As of jQuery v1.7 the `.on()` method is the accepted means of both directly binding events and creating delegated events. This article aims to explore the history of *event delegation* from jQuery v1.0 - present and how each version leverages it. + -Given the following html, for our example we want to log the text of the each `
                • ` to console whenever it is clicked. +Throughout the evolution of jQuery the means of event binding has changed for various reasons ranging from performance to semantics. As of jQuery v1.7 the `.on()` method is the accepted means of both directly binding events and creating delegated events. This article aims to explore the history of *event delegation* from jQuery v1.0 to the present and how each version leverages it. + +Given the following HTML, for our example we want to log the text of the each `
                • ` to console whenever it is clicked. ```
                  -
                    -
                  • Item #1
                  • -
                  • Item #2
                  • -
                  • Item #3
                  • -
                  • ...
                  • -
                  • Item #100
                  • -
                  +
                    +
                  • Item #1
                  • +
                  • Item #2
                  • +
                  • Item #3
                  • +
                  • ...
                  • +
                  • Item #100
                  • +
                  ​ ``` ### [.bind()](http://api.jquery.com/bind/) (Deprecated) + Introduced in jQuery v1.0 It is possible to use `.bind()` and attach a handler to every element. ``` -​$("#list li").bind( "click", function(event) { - console.log( $elem.text() ); +​$( "#list li" ).bind( "click", function( event ) { + var elem = $( event.target ); + console.log( elem.text() ); });​​​​​​​​​​​​​​​​​​​​​ ``` -As discussed in the [event delegation](/event/event-delegation) article, this is not optimal. + +As discussed in the [event delegation](/event/event-delegation/) article, this is not optimal. ### liveQuery -*liveQuery* was a popular jQuery plugin that allowed for the creation of events which would be triggered for elements that existed now or in the future. This plugin did not use event delegation and used expensive CPU processing to poll the DOM for changes every 20ms and fire events accordingly. +*liveQuery* was a popular jQuery plugin that allowed for the creation of events which would be triggered for elements that existed now or in the future. This plugin did not use event delegation and used expensive CPU processing to poll the DOM for changes every 20ms and fire events accordingly. ### [.bind()](http://api.jquery.com/bind/) delegation (Deprecated) + Introduced in jQuery v1.0 Generally we don't associate `.bind()` with *event delegation*, however prior to jQuery v1.3 it was the only means of delegation available to us. ``` -​$("#list").bind( "click", function(event) { - var $elem = $( event.target ); - if ( $elem.is("li") ){ - console.log( $elem.text() ); - } +​$( "#list" ).bind( "click", function( event ) { + var elem = $( event.target ); + if ( elem.is( "li" ) ) { + console.log( elem.text() ); + } });​​​​​​​​​​​​​​​​​​​​​ ``` -We are able to take advantage of *event bubbling* here by attaching a *click* event to the parent `
                    ` element. Whenever the `
                  • ` is clicked, the event bubbles up to its parent, the `
                      `, which executes our event handler. Our event handler checks to see if the **event.target** (the element that caused the event to fire) matches our selector. + +We are able to take advantage of *event bubbling* here by attaching a *click* event to the parent `
                        ` element. Whenever the `
                      • ` is clicked, the event bubbles up to its parent, the `
                          `, which executes our event handler. Our event handler checks to see if the **event.target** (the element that caused the event to fire) matches our selector. ### [.live()](http://api.jquery.com/live/) (Deprecated) + Introduced in jQuery v1.3 All `.live()` event handlers are bound to the *document* root by default. ``` -​$("#list li").live( "click", function(event) { - var $elem = $( this ); - console.log( $elem.text() ); +​$( "#list li" ).live( "click", function( event ) { + var elem = $( this ); + console.log( elem.text() ); });​​​​​​​​​​​​​​​​​​​​​ ``` -When we use `.live()` our event is bound to `$( document )`. When the `
                        • ` is clicked, bubbling occurs and our *click* event is fired for each of the following elements: +When we use `.live()` our event is bound to `$( document )`. When the `
                        • ` is clicked, bubbling occurs and our *click* event is fired for each of the following elements: * `
                            ` * `
                            ` @@ -70,57 +77,61 @@ When we use `.live()` our event is bound to `$( document )`. When the `
                          • ` is * `` * *document* root -The last element to receive the *click* event is *document*, this is where our `.live()` event is bound. `.live()` will then check to see if our selector `#list li` is the element that triggered the event, if so our event handler is executed. +The last element to receive the *click* event is *document*, this is where our `.live()` event is bound. `.live()` will then check to see if our selector `#list li` is the element that triggered the event, if so our event handler is executed. ### [.live()](http://api.jquery.com/live/) w/ context (Deprecated) + Introduced in jQuery v1.4 -Passing the *context* as a second optional argument to the `$()` function has been supported since v1.0. However support for using this *context* with the `$.live()` method was not added until v1.4. +Passing the *context* as a second optional argument to the `$()` function has been supported since v1.0. However support for using this *context* with the `$.live()` method was not added until v1.4. If we were take our previous `.live()` example and provide it the default *context*, it would look like: ``` -​$( "#list li", document ).live( "click", function(event) { - var $elem = $( this ); - console.log( $elem.text() ); +​$( "#list li", document ).live( "click", function( event ) { + var elem = $( this ); + console.log( elem.text() ); });​​​​​​​​​​​​​​​​​​​​​ ``` Since we can override the *context* when using the `.live()` method, we can specify a *context* that is closer to the element in the DOM hierarchy ``` -$( "li", "#list" ).live( "click", function(event) { - var $elem = $( this ); - console.log( $elem.text() ); +$( "li", "#list" ).live( "click", function( event ) { + var elem = $( this ); + console.log( elem.text() ); });​​​​​​​​​​​​​​​​​​​​​ ``` -In this instance when an `
                          • ` is clicked the event still bubbles all the way up the *document tree* as it did before. However, our event handler is now bound to the parent `
                              ` tag, so we do not have to wait for the event to bubble all the way up to the *document* root. +In this instance when an `
                            • ` is clicked the event still bubbles all the way up the *document tree* as it did before. However, our event handler is now bound to the parent `
                                ` tag, so we do not have to wait for the event to bubble all the way up to the *document* root. ### [.delegate()](http://api.jquery.com/delegate/) (Deprecated) + First introduced in jQuery v1.4.2 The `.delegate()` method provides a clear difference between the *context* of where to attach delegated event handler, and the *selector* to match when the event bubbles up to the delegated element. ``` -$("#list").delegate( "li", "click", function(event) { - var $elem = $( this ); - console.log( $elem.text() ); +$( "#list" ).delegate( "li", "click", function( event ) { + var elem = $( this ); + console.log( elem.text() ); });​​​​​​​​​​​​​​​​​​​​​ ``` ### [.on()](http://api.jquery.com/on/) + First introduced in jQuery v1.7 -The `on.()` method gives us a semantic approach for creating directly bound events as well as delegated events. It eliminates the need to use the deprecated`.bind()`, `.live()` and `.delegate()` methods, providing a single API for creating events. +The `.on()` method gives us a semantic approach for creating directly bound events as well as delegated events. It eliminates the need to use the deprecated `.bind()`, `.live()`, and `.delegate()` methods, providing a single API for creating events. ``` -$("#list").on( "click", "li", function(event) { - var $elem = $( this ); - console.log( $elem.text() ); +$( "#list" ).on( "click", "li", function( event ) { + var elem = $( this ); + console.log( elem.text() ); });​​​​​​​​​​​​​​​​​​​​​ ``` ### Summary -All of these ways of *event delegation* were innovative and considered a best practice at the time of their release. Depending on what version of jQuery you have implemented use the appropriate means of *event delegation*. + +All of these ways of *event delegation* were innovative and considered a best practice at the time of their release. Depending on what version of jQuery you have implemented use the appropriate means of *event delegation*. diff --git a/page/events/inside-event-handling-function.md b/page/events/inside-event-handling-function.md index ac57a4e7..0e0f9519 100644 --- a/page/events/inside-event-handling-function.md +++ b/page/events/inside-event-handling-function.md @@ -1,19 +1,15 @@ ---- -title : Inside the Event Handling Function -level: beginner -source: http://jqfundamentals.com/legacy -attribution: - - jQuery Fundamentals ---- -Every event handling function receives an event object, which contains many -properties and methods. The event object is most commonly used to prevent the -default action of the event via the preventDefault method. However, the event -object contains a number of other useful properties and methods, including: + + +Every event handling function receives an event object, which contains many properties and methods. The event object is most commonly used to prevent the default action of the event via the `.preventDefault()` method. However, the event object contains a number of other useful properties and methods, including: #### pageX, pageY -The mouse position at the time the event occurred, relative to the top left of -the page. +The mouse position at the time the event occurred, relative to the top left of the page. #### type @@ -39,28 +35,19 @@ Prevent the default action of the event (e.g. following a link). Stop the event from bubbling up to other elements. -In addition to the event object, the event handling function also has access to -the DOM element that the handler was bound to via the keyword this. To turn -the DOM element into a jQuery object that we can use jQuery methods on, we -simply do $( this ), often following this idiom: +In addition to the event object, the event handling function also has access to the DOM element that the handler was bound to via the keyword `this`. To turn the DOM element into a jQuery object that we can use jQuery methods on, we simply do `$( this )`, often following this idiom: ``` -var $this = $( this ); +var elem = $( this ); ``` ``` // Preventing a link from being followed -$("a").click(function(e) { - - var $this = $( this ); - - if ( $this.attr("href").match("evil") ) { - - e.preventDefault(); - - $this.addClass("evil"); - - } - +$( "a" ).click(function( event ) { + var elem = $( this ); + if ( elem.attr( "href" ).match( "evil" ) ) { + event.preventDefault(); + elem.addClass( "evil" ); + } }); ``` diff --git a/page/events/introduction-to-custom-events.md b/page/events/introduction-to-custom-events.md index 0811c310..56be0577 100644 --- a/page/events/introduction-to-custom-events.md +++ b/page/events/introduction-to-custom-events.md @@ -1,487 +1,145 @@ ---- -title: Introducing Custom Events -level: intermediate -source: http://jqfundamentals.com/legacy -attribution: - - jQuery Fundamentals ---- + ## Custom Events -We're all familiar with the basic events — click, mouseover, focus, blur, -submit, etc. — that we can latch on to as a user interacts with the browser. -Custom events open up a whole new world of event-driven programming. In this -chapter, we'll use jQuery's custom events system to make a simple Twitter -search application. +We're all familiar with the basic events — click, mouseover, focus, blur, submit, etc. — that we can latch on to as a user interacts with the browser. Custom events open up a whole new world of event-driven programming. -It can be difficult at first to understand why you'd want to use custom events, -when the built-in events seem to suit your needs just fine. It turns out that -custom events offer a whole new way of thinking about event-driven JavaScript. -Instead of focusing on the element that triggers an action, custom events put -the spotlight on the element being acted upon. This brings a bevy of benefits, -including: +It can be difficult at first to understand why you'd want to use custom events, when the built-in events seem to suit your needs just fine. It turns out that custom events offer a whole new way of thinking about event-driven JavaScript. Instead of focusing on the element that triggers an action, custom events put the spotlight on the element being acted upon. This brings a bevy of benefits, including: -- Behaviors of the target element can easily be triggered by different elements using the same code. -- Behaviors can be triggered across multiple, similar, target elements at once. -- Behaviors are more clearly associated with the target element in code, making code easier to read and maintain. +* Behaviors of the target element can easily be triggered by different elements using the same code. +* Behaviors can be triggered across multiple, similar, target elements at once. +* Behaviors are more clearly associated with the target element in code, making code easier to read and maintain. -Why should you care? An example is probably the best way to explain. Suppose -you have a lightbulb in a room in a house. The lightbulb is currently turned -on, and it's controlled by two three-way switches and a clapper: +Why should you care? An example is probably the best way to explain. Suppose you have a lightbulb in a room in a house. The lightbulb is currently turned on, and it's controlled by two three-way switches and a clapper: ```
                                -
                                -
                                -
                                -
                                +
                                Kitchen light
                                +
                                Kitchen switch 1
                                +
                                Kitchen switch 2
                                +
                                Kitchen clapper switch
                                ``` -Triggering the clapper or either of the switches will change the state of the -lightbulb. The switches and the clapper don't care what state the lightbulb is -in; they just want to change the state. +Triggering the clapper or either of the switches will change the state of the lightbulb. The switches and the clapper don't care what state the lightbulb is in; they just want to change the state. Without custom events, you might write some code like this: ``` -$(".switch, .clapper").click(function() { - - var $light = $( this ).parent().find(".lightbulb"); - - if ( $light.hasClass("on") ) { - - $light.removeClass("on").addClass("off"); - - } else { - - $light.removeClass("off").addClass("on"); - - } - +$( ".switch, .clapper" ).click(function() { + var light = $( this ).closest( ".room" ).find( ".lightbulb" ); + if ( light.is( ".on" ) ) { + light.removeClass( "on" ).addClass( "off" ); + } else { + light.removeClass( "off" ).addClass( "on" ); + } }); ``` With custom events, your code might look more like this: ``` -$(".lightbulb").on( "changeState", function(e) { - - var $light = $( this ); - - if ( $light.hasClass("on") ) { - - $light.removeClass("on").addClass("off"); - - } else { - - $light.removeClass("off").addClass("on"); - - } - +$( ".lightbulb" ).on( "light:toggle", function( event ) { + var light = $( this ); + if ( light.is( ".on" ) ) { + light.removeClass( "on" ).addClass( "off" ); + } else { + light.removeClass( "off" ).addClass( "on" ); + } }); - $(".switch, .clapper").click(function() { - - $( this ).parent().find(".lightbulb").trigger("changeState"); - - }); +$( ".switch, .clapper" ).click(function() { + var room = $( this ).closest( ".room" ); + room.find( ".lightbulb" ).trigger( "light:toggle" ); +}); ``` -This last bit of code is not that exciting, but something important has happened: we've moved the behavior of the lightbulb to the lightbulb, and away from the switches and the clapper. +This last bit of code is not that exciting, but something important has happened: we've moved the behavior of the lightbulb away from the switches and the clapper and to the lightbulb itself. -Let's make our example a little more interesting. We'll add another room to our house, along with a master switch, as shown here: +Let's make our example a little more interesting. We'll add another room to our house, along with a master switch, as shown here: ```
                                -
                                -
                                -
                                -
                                +
                                Kitchen light
                                +
                                Kitchen switch 1
                                +
                                Kitchen switch 2
                                +
                                Kitchen clapper switch
                                -
                                -
                                -
                                -
                                +
                                Bedroom light
                                +
                                Bedroom switch 1
                                +
                                Bedroom switch 2
                                +
                                Bedroom clapper switch
                                -
                                +
                                Master switch
                                ``` -If there are any lights on in the house, we want the master switch to turn all -the lights off; otherwise, we want it to turn all lights on. To accomplish -this, we'll add two more custom events to the lightbulbs: `turnOn` and -`turnOff`. We'll make use of them in the `changeState` custom event, and use -some logic to decide which one the master switch should trigger: +If there are any lights on in the house, we want the master switch to turn all the lights off; otherwise, we want it to turn all lights on. To accomplish this, we'll add two more custom events to the lightbulbs: `light:on` and `light:off`. We'll make use of them in the `light:toggle` custom event, and use some logic to decide which one the master switch should trigger: ``` -$(".lightbulb").on( "changeState", function(e) { - - var $light = $( this ); - - if ($light.hasClass("on")) { - - $light.trigger("turnOff"); - - } else { - - $light.trigger("turnOn"); - } - -}).on( "turnOn", function(e) { - - $( this ).removeClass("off").addClass("on"); - -}).on( "turnOff", function(e) { - - $( this ).removeClass("on").addClass("off"); - +$( ".lightbulb" ).on( "light:toggle", function( event ) { + var light = $( this ); + if ( light.is( ".on" ) ) { + light.trigger( "light:off" ); + } else { + light.trigger( "light:on" ); + } +}).on( "light:on", function( event ) { + $( this ).removeClass( "off" ).addClass( "on" ); +}).on( "light:off", function( event ) { + $( this ).removeClass( "on" ).addClass( "off" ); }); -$(".switch, .clapper").click(function() { - - $( this ).parent().find(".lightbulb").trigger("changeState"); - +$( ".switch, .clapper" ).click(function() { + var room = $( this ).closest( ".room" ); + room.find( ".lightbulb" ).trigger( "light:toggle" ); }); -$("#master_switch").click(function() { - - if ( $(".lightbulb.on").length ) { - - $(".lightbulb").trigger("turnOff"); - - } else { - - $(".lightbulb").trigger("turnOn"); - - } +$( "#master_switch" ).click(function() { + var lightbulbs = $( ".lightbulb" ); + // Check if any lightbulbs are on + if ( lightbulbs.is( ".on" ) ) { + lightbulbs.trigger( "light:off" ); + } else { + lightbulbs.trigger( "light:on" ); + } }); ``` -Note how the behavior of the master switch is attached to the master switch; -the behavior of a lightbulb belongs to the lightbulbs. +Note how the behavior of the master switch is attached to the master switch; the behavior of a lightbulb belongs to the lightbulbs. + +### Naming Custom Events -If you're accustomed to object-oriented programming, you may find it useful to -think of custom events as methods of objects. Loosely speaking, the object to -which the method belongs is created via the jQuery selector. Binding the -changeState custom event to all `$(".light")` elements is akin to having a -class called `Light` with a method of `changeState`, and then instantiating new -`Light` objects for each element with a classname of light. +You can use any name for a custom event, however you should beware of creating new events with names that might be used by future DOM events. For this reason, in this article we have chosen to use `light:` for all of our event names, as events with colons are unlikely to be used by a future DOM spec. -### Recap: $.fn.on and $.fn.trigger +### Recap: `.on()` and `.trigger()` -In the world of custom events, there are two important jQuery methods: -`$.fn.on` and `$.fn.trigger`. In the Events chapter, we saw how to use these -methods for working with user events; for this chapter, it's important to -remember two things: +In the world of custom events, there are two important jQuery methods: `.on()` and `.trigger()`. In the [Events](/events/) chapter, we saw how to use these methods for working with user events; for this chapter, it's important to remember two things: -- `$.fn.on` method takes an event type and an event handling function as - arguments. Optionally, it can also receive event-related data as its second - argument, pushing the event handling function to the third argument. Any data - that is passed will be available to the event handling function in the `data` - property of the event object. The event handling function always receives the - event object as its first argument. +* `.on()` method takes an event type and an event handling function as arguments. Optionally, it can also receive event-related data as its second argument, pushing the event handling function to the third argument. Any data that is passed will be available to the event handling function in the `data` property of the event object. The event handling function always receives the event object as its first argument. -- `$.fn.trigger` method takes an event type as its argument. Optionally, it can - also take an array of values. These values will be passed to the event - handling function as arguments after the event object. +* `.trigger()` method takes an event type as its argument. Optionally, it can also take an array of values. These values will be passed to the event handling function as arguments after the event object. -Here is an example of the usage of `$.fn.on` and `$.fn.trigger` that uses -custom data in both cases: +Here is an example of the usage of `.on()` and `.trigger()` that uses custom data in both cases: ``` $( document ).on( "myCustomEvent", { - foo: "bar" + foo: "bar" }, function( event, arg1, arg2 ) { - console.log( event.data.foo ); // "bar" - console.log( arg1 ); // "bim" - console.log( arg2 ); // "baz" -}); - -$( document ).trigger( "myCustomEvent", [ "bim", "baz" ]); -``` - -### A Sample Application - -To demonstrate the power of custom events, we're going to create a simple tool -for searching Twitter. The tool will offer several ways for a user to add -search terms to the display: by entering a search term in a text box, by -entering multiple search terms in the URL, and by querying Twitter for trending -terms. - -The results for each term will be shown in a results container; these -containers will be able to be expanded, collapsed, refreshed, and removed, -either individually or all at once. - -When we're done, it will look like this: - -![Our finished application](http://gyazo.com/70415e9fffab1c47953f5264ecf722fe.png) - -``` -

                                Twitter Search

                                - - -
                                - - -
                                - -
                                -
                                -

                                Search Results for -

                                -
                                -
                                -``` - -This gives us a container (#twitter) for our widget, a template for our results -containers (hidden via CSS), and a simple form where users can input a search -term. (For the sake of simplicity, we're going to assume that our application -is JavaScript-only and that our users will always have CSS.) - -There are two types of objects we'll want to act on: the results containers, -and the Twitter container. - -The results containers are the heart of the application. We'll create a plugin -that will prepare each results container once it's added to the Twitter -container. Among other things, it will bind the custom events for each -container and add the action buttons at the top right of each container. Each -results container will have the following custom events: - -- `refresh` - Mark the container as being in the "refreshing" state, and fire - the request to fetch the data for the search term. - -- `populate` - Receive the returned JSON data and use it to populate the container. - -- `remove` - Remove the container from the page after the user verifies the - request to do so. Verification can be bypassed by passing true as the second - argument to the event handler. The remove event also removes the term - associated with the results container from the global object containing the - search terms. - -- `collapse` - Add a class of collapsed to the container, which will hide the - results via CSS. It will also turn the container's "Collapse" button into an - "Expand" button. - -- `expand` - Remove the collapsed class from the container. It will also turn - the container's "Expand" button into a "Collapse" button. - -The plugin is also responsible for adding the action buttons to the container. -It binds a click event to each action's list item, and uses the list item's -class to determine which custom event will be triggered on the corresponding -results container. - -``` -$.fn.twitterResult = function( settings ) { - return this.each(function() { - var $results = $( this ); - var $actions = $.fn.twitterResult.actions = $.fn.twitterResult.actions || $.fn.twitterResult.createActions(); - var $a = $actions.clone().prependTo( $results ); - var term = settings.term; - - $results.find("span.search_term").text( term ); - $.each([ "refresh", "populate", "remove", "collapse", "expand" ], function( i, ev ) { - - $results.on( ev, { - term: term - }, $.fn.twitterResult.events[ ev ] ); - - }); - - // use the class of each action to figure out - // which event it will trigger on the results panel - $a.find("li").click(function() { - - // pass the li that was clicked to the function - // so it can be manipulated if needed - $results.trigger( $( this ).attr("class"), [ $( this ) ] ); - }); - - }); - -}; - -$.fn.twitterResult.createActions = function() { - return $("
                                  ").append( - "
                                • Refresh
                                • " + - "
                                • Remove
                                • " + - "
                                • Collapse
                                • " - ); -}; - -$.fn.twitterResult.events = { - - refresh: function( e ) { - // indicate that the results are refreshing - var $this = $( this ).addClass("refreshing"); - - $this.find("p.tweet").remove(); - $results.append("

                                  Loading ...

                                  "); - - // get the twitter data using jsonp - $.getJSON("http://search.twitter.com/search.json?q=" + escape( e.data.term ) + "&rpp=5&callback=?", function( json ) { - $this.trigger( "populate", [ json ] ); - }); - }, - populate: function( e, json ) { - var results = json.results; - var $this = $( this ); - - $this.find("p.loading").remove(); - $.each( results, function( i, result ) { - var tweet = "

                                  " + - "" + - result.from_user + - ": " + - result.text + - " " + - result.created_at + - "" + - "

                                  "; - - $this.append( tweet ); - }); - - // indicate that the results - // are done refreshing - $this.removeClass("refreshing"); - }, - remove: function( e, force ) { - if ( !force && !confirm("Remove panel for term " + e.data.term + "?") ) { - return; - } - $( this ).remove(); - - // indicate that we no longer have a panel for the term - search_terms[ e.data.term ] = 0; - }, - collapse: function( e ) { - $( this ).find("li.collapse") - .removeClass("collapse") - .addClass("expand") - .text("Expand"); - - $( this ).addClass("collapsed"); - }, - - expand: function( e ) { - $( this ).find("li.expand") - .removeClass("expand") - .addClass("collapse") - .text("Collapse"); - - $( this ).removeClass("collapsed"); - } - -}; -``` - -The Twitter container itself will have just two custom events: - -- `getResults` - Receives a search term and checks to determine whether there's - already a results container for the term; if not, adds a results container - using the results template, set up the results container using the - `$.fn.twitterResult` plugin discussed above, and then triggers the `refresh` - event on the results container in order to actually load the results. - Finally, it will store the search term so the application knows not to - re-fetch the term. - -- `getTrends` - Queries Twitter for the top 10 trending terms, then iterates - over them and triggers the `getResults` event for - each of them, thereby adding a results container for each term. - -Here's how the Twitter container bindings look: - -``` -$("#twitter").on( "getResults", function( e, term ) { - - // make sure we don"t have a box for this term already - if ( !search_terms[ term ] ) { - var $this = $( this ); - var $template = $this.find("div.template"); - - // make a copy of the template div - // and insert it as the first results box - $results = $template.clone() - .removeClass("template") - .insertBefore( $this.find("div:first") ) - .twitterResult({ - "term": term - }); - - // load the content using the "refresh" - // custom event that we bound to the results container - $results.trigger("refresh"); - - search_terms[ term ] = 1; - } -}).on( "getTrends", function( e ) { - var $this = $( this ); - - $.getJSON( "http://search.twitter.com/trends.json?callback=?", function( json ) { - var trends = json.trends; - - $.each( trends, function( i, trend ) { - $this.trigger( "getResults", [ trend.name ] ); - }); - }); -}); -``` - -So far, we've written a lot of code that does approximately nothing, but that's -OK. By specifying all the behaviors that we want our core objects to have, -we've created a solid framework for rapidly building out the interface. - -Let's start by hooking up our text input and the "Load Trending Terms" button. -For the text input, we'll capture the term that was entered in the input and -pass it as we trigger the Twitter container's `getResults` event. Clicking the -"Load Trending Terms" will trigger the Twitter container's `getTrends` event: - -``` -$("form").submit(function( event ) { - var term = $("#search_term").val(); - $("#twitter").trigger( "getResults", [ term ] ); - - event.preventDefault(); -}); - -$("#get_trends").click(function() { - $("#twitter").trigger("getTrends"); -}); -``` - -By adding a few buttons with the appropriate IDs, we can make it possible to -remove, collapse, expand, and refresh all results containers at once, as shown -below. For the remove button, note how we're passing a value of true to the -event handler as its second argument, telling the event handler that we don't -want to verify the removal of individual containers. - -``` -$.each([ "refresh", "expand", "collapse" ], function( i, ev ) { - $( "#" + ev ).click( function( e ) { - $("#twitter div.results").trigger( ev ); - }); + console.log( event.data.foo ); // "bar" + console.log( arg1 ); // "bim" + console.log( arg2 ); // "baz" }); -$("#remove").click(function( e ) { - if ( confirm("Remove all results?") ) { - $("#twitter div.results").trigger( "remove", [ true ] ); - } -}); +$( document ).trigger( "myCustomEvent", [ "bim", "baz" ] ); ``` ### Conclusion -Custom events offer a new way of thinking about your code: they put the -emphasis on the target of a behavior, not on the element that triggers it. If -you take the time at the outset to spell out the pieces of your application, as -well as the behaviors those pieces need to exhibit, custom events can provide a -powerful way for you to "talk" to those pieces, either one at a time or en -masse. Once the behaviors of a piece have been described, it becomes trivial to -trigger those behaviors from anywhere, allowing for rapid creation of and -experimentation with interface options. Finally, custom events can enhance code -readability and maintainability, by making clear the relationship between an -element and its behaviors. +Custom events offer a new way of thinking about your code: they put the emphasis on the target of a behavior, not on the element that triggers it. If you take the time at the outset to spell out the pieces of your application, as well as the behaviors those pieces need to exhibit, custom events can provide a powerful way for you to "talk" to those pieces, either one at a time or en masse. Once the behaviors of a piece have been described, it becomes trivial to trigger those behaviors from anywhere, allowing for rapid creation of and experimentation with interface options. Finally, custom events can enhance code readability and maintainability, by making clear the relationship between an element and its behaviors. diff --git a/page/events/introduction-to-events.md b/page/events/introduction-to-events.md index 455b9867..c105de82 100644 --- a/page/events/introduction-to-events.md +++ b/page/events/introduction-to-events.md @@ -1,19 +1,18 @@ ---- -title: Introducing Events -level: beginner -source: http://jqfundamentals.com/legacy -attribution: - - jQuery Fundamentals ---- + ## Introduction -Web pages are all about interaction. Users perform a countless number of actions such as moving their mice over the page, clicking on elements, and typing in textboxes—all of these are examples of events. In addition to these user events, there are a slew of others that occur, like when the page is loaded, when video begins playing or is paused, etc. Whenever something interesting occurs on the page, an event is fired, meaning that the browser basically announces that something has happened. It's this announcement that allows developers to "listen" for events and react to them appropriately. +Web pages are all about interaction. Users perform a countless number of actions such as moving their mice over the page, clicking on elements, and typing in textboxes — all of these are examples of events. In addition to these user events, there are a slew of others that occur, like when the page is loaded, when video begins playing or is paused, etc. Whenever something interesting occurs on the page, an event is fired, meaning that the browser basically announces that something has happened. It's this announcement that allows developers to "listen" for events and react to them appropriately. ## What's a DOM event? -As mentioned, there are a myriad of event types, but perhaps the ones that are easiest to understand are user events, like when someone clicks on an element or types into a form. These types of events occur on an element, meaning that when a user clicks on a button for example, the button has had an event occur on it. While user interactions aren't the only types of DOM events, they're certainly the easiest to understand when starting out. MDN has a good reference of [available DOM events](https://developer.mozilla.org/en/DOM/DOM_event_reference). +As mentioned, there are a myriad of event types, but perhaps the ones that are easiest to understand are user events, like when someone clicks on an element or types into a form. These types of events occur on an element, meaning that when a user clicks on a button for example, the button has had an event occur on it. While user interactions aren't the only types of DOM events, they're certainly the easiest to understand when starting out. Mozilla Developer Network has a good reference of [available DOM events](https://developer.mozilla.org/en-US/docs/Web/Events). ## Ways to listen for events @@ -44,73 +43,59 @@ To accomplish the desired task unobtrusively, let's change our HTML a little bit If we wanted to be informed when a user clicks on that button unobtrusively, we might do something like the following in a separate script file: ``` -//Event binding using addEventListener -var helloBtn = document.getElementById("helloBtn"); - -helloBtn.addEventListener( "click", function(event) { - - alert("Hello."); +// Event binding using addEventListener +var helloBtn = document.getElementById( "helloBtn" ); +helloBtn.addEventListener( "click", function( event ) { + alert( "Hello." ); }, false ); ``` Here we're saving a reference to the button element by calling `getElementById` and assigning its return value to a variable. We then call `addEventListener` and provide an event handler function that will be called whenever that event occurs. While there's nothing wrong with this code as it will work fine in modern browsers, it won't fare well in versions of IE prior to IE9. This is because Microsoft chose to implement a different method, `attachEvent`, as opposed to the W3C standard `addEventListener`, and didn't get around to changing it until IE9 was released. For this reason, it's beneficial to utilize jQuery because it abstracts away browser inconsistencies, allowing developers to use a single API for these types of tasks, as seen below. ``` -//Event binding using a convenience method -$("#helloBtn").click(function( event ) { - - alert("Hello."); - +// Event binding using a convenience method +$( "#helloBtn" ).click(function( event ) { + alert( "Hello." ); }); ``` -The `$("#helloBtn")` code selects the button element using the `$` (aka `jQuery`) function and returns a jQuery object. The jQuery object has a bunch of methods (functions) available to it, one of them named `click`, which resides in the jQuery object's prototype. We call the `click` method on the jQuery object and pass along an anonymous function event handler that's going to be executed when a user clicks the button, alerting "Hello." to the user. +The `$( "#helloBtn" )` code selects the button element using the `$` (a.k.a. `jQuery`) function and returns a jQuery object. The jQuery object has a bunch of methods (functions) available to it, one of them named `click`, which resides in the jQuery object's prototype. We call the `click` method on the jQuery object and pass along an anonymous function event handler that's going to be executed when a user clicks the button, alerting "Hello." to the user. There are a number of ways that events can be listened for using jQuery: ``` -//The many ways to bind events with jQuery +// The many ways to bind events with jQuery // Attach an event handler directly to the button using jQuery's // shorthand `click` method. -$("#helloBtn").click(function( event ) { - - alert("Hello."); - +$( "#helloBtn" ).click(function( event ) { + alert( "Hello." ); }); -// Attach an event handler directly the to button using jQuery's +// Attach an event handler directly to the button using jQuery's // `bind` method, passing it an event string of `click` -$("#helloBtn").bind( "click", function( event ) { - - alert("Hello."); - +$( "#helloBtn" ).bind( "click", function( event ) { + alert( "Hello." ); }); // As of jQuery 1.7, attach an event handler directly to the button // using jQuery's `on` method. -$("#helloBtn").on( "click", function( event ) { - - alert("Hello."); - +$( "#helloBtn" ).on( "click", function( event ) { + alert( "Hello." ); }); // As of jQuery 1.7, attach an event handler to the `body` element that // is listening for clicks, and will respond whenever *any* button is // clicked on the page. -$("body").on({ - click: function( event ) { - - alert("Hello."); - - } +$( "body" ).on({ + click: function( event ) { + alert( "Hello." ); + } }, "button" ); // An alternative to the previous example, using slightly different syntax. -$("body").on( "click", "button", function( event ) { - - alert("Hello."); - +$( "body" ).on( "click", "button", function( event ) { + alert( "Hello." ); }); ``` @@ -120,11 +105,13 @@ Let's look at the `on` examples from above and discuss their differences. In the In the second `on` example, we're passing an object (denoted by the curly braces `{}`), which has a property of `click` whose value is an anonymous function. The second argument to the `on` method is a jQuery selector string of `button`. While examples 1–3 are functionally equivalent, example 4 is different in that the `body` element is listening for click events that occur on *any* button element, not just `#helloBtn`. The final example above is exactly the same as the one preceding it, but instead of passing an object, we pass an event string, a selector string, and the callback. Both of these are examples of event delegation, a process by which an element higher in the DOM tree listens for events occurring on its children. -Event delegation works because of the notion of *event bubbling*. For most events, whenever something occurs on a page (like an element is clicked), the event travels from the element it occurred on, up to its parent, then up to the parent's parent, and so on, until it reaches the root element, aka the `window`. So in our table example, whenever a `td` is clicked, its parent `tr` would also be notified of the click, the parent `table` would be notified, the `body` would be notified, and ultimately the `window` would be notified as well. While event bubbling and delegation work well, the delegating element (in our example, the `table`) should always be as close to the delegatees as possible so the event doesn't have to travel way up the DOM tree before its handler function is called. +## Event delegation -The two main pros of event delegation over binding directly to an element (or set of elements) are performance and the aforementioned event bubbling. Imagine having a large table of 1000 cells and binding to an event for each cell. That's 1000 separate event handlers that the browser has to attach, even if they're all mapped to the same function. Instead of binding to each individual cell though, we could instead use delegation to listen for events that occur on the parent table and react accordingly. One event would be bound instead of 1000, resulting in way better performance and memory management. +Event delegation works because of the notion of *event bubbling*. For most events, whenever something occurs on a page (like an element is clicked), the event travels from the element it occurred on, up to its parent, then up to the parent's parent, and so on, until it reaches the root element, a.k.a. the `window`. So in our table example, whenever a `td` is clicked, its parent `tr` would also be notified of the click, the parent `table` would be notified, the `body` would be notified, and ultimately the `window` would be notified as well. While event bubbling and delegation work well, the delegating element (in our example, the `table`) should always be as close to the delegatees as possible so the event doesn't have to travel way up the DOM tree before its handler function is called. -The event bubbling that occurs affords us the ability to add cells via AJAX for example, without having to bind events directly to those cells since the parent table is listening for clicks and is therefore notified of clicks on its children. If we weren't using delegation, we'd have to constantly bind events for every cell that's added which is not only a performance issue, but could also become a maintenance nightmare. +The two main pros of event delegation over binding directly to an element (or set of elements) are performance and the aforementioned event bubbling. Imagine having a large table of 1,000 cells and binding to an event for each cell. That's 1,000 separate event handlers that the browser has to attach, even if they're all mapped to the same function. Instead of binding to each individual cell though, we could instead use delegation to listen for events that occur on the parent table and react accordingly. One event would be bound instead of 1,000, resulting in way better performance and memory management. + +The event bubbling that occurs affords us the ability to add cells via Ajax for example, without having to bind events directly to those cells since the parent table is listening for clicks and is therefore notified of clicks on its children. If we weren't using delegation, we'd have to constantly bind events for every cell that's added which is not only a performance issue, but could also become a maintenance nightmare. ## The event object @@ -132,54 +119,50 @@ The event bubbling that occurs affords us the ability to add cells via AJAX for In all of the previous examples, we've been using anonymous functions and specifying an `event` argument within that function. Let's change it up a little bit. ``` -//Binding a named function +// Binding a named function function sayHello( event ) { - - alert("Hello."); - + alert( "Hello." ); } -$("#helloBtn").on( "click", sayHello ); +$( "#helloBtn" ).on( "click", sayHello ); ``` -In this slightly different example, we're defining a function called `sayHello` and then passing that function into the `on` method instead of an anonymous function. So many online examples show anonymous functions used as event handlers, but it's important to realize that you can also pass defined functions as event handlers as well. This is important if different elements or different events should perform the same functionality. This helps to keep your code DRY. +In this slightly different example, we're defining a function called `sayHello` and then passing that function into the `on` method instead of an anonymous function. So many online examples show anonymous functions used as event handlers, but it's important to realize that you can also pass defined functions as event handlers as well. This is important if different elements or different events should perform the same functionality. This helps to keep your code [DRY](http://en.wikipedia.org/wiki/Don%27t_repeat_yourself). -But what about that `event` argument in the `sayHello` function—what is it and why does it matter? In all DOM event callbacks, jQuery passes an *event object* argument which contains information about the event, such as precisely when and where it occurred, what type of event it was, which element the event occurred on, and a plethora of other information. Of course you don't have to call it `event`; you could call it `e` or whatever you want to, but `event` is a pretty common convention. +But what about that `event` argument in the `sayHello` function — what is it and why does it matter? In all DOM event callbacks, jQuery passes an *event object* argument which contains information about the event, such as precisely when and where it occurred, what type of event it was, which element the event occurred on, and a plethora of other information. Of course you don't have to call it `event`; you could call it `e` or whatever you want to, but `event` is a pretty common convention. -If the element has default functionality for a specific event (like a link opens a new page, a button in a form submits the form, etc), that default functionality can be cancelled. This is often useful for AJAX requests. When a user clicks on a button to submit a form via AJAX, we'd want to cancel the button/form's default action (to submit it to the form's `action` attribute), and we would instead do an AJAX request to accomplish the same task for a more seamless experience. To do this, we would utilize the event object and call its `preventDefault` method. We can also prevent the event from bubbling up the DOM tree using `stopPropagation` so that parent elements aren't notified of its occurrence (in the case that event delegation is being used). +If the element has default functionality for a specific event (like a link opens a new page, a button in a form submits the form, etc.), that default functionality can be canceled. This is often useful for Ajax requests. When a user clicks on a button to submit a form via Ajax, we'd want to cancel the button/form's default action (to submit it to the form's `action` attribute), and we would instead do an Ajax request to accomplish the same task for a more seamless experience. To do this, we would utilize the event object and call its `.preventDefault()` method. We can also prevent the event from bubbling up the DOM tree using `.stopPropagation()` so that parent elements aren't notified of its occurrence (in the case that event delegation is being used). ``` -//Preventing a default action from occurring and stopping the event bubbling -$("form").on( "submit", function( event ) { +// Preventing a default action from occurring and stopping the event bubbling +$( "form" ).on( "submit", function( event ) { - // Prevent the form's default submission. - event.preventDefault(); + // Prevent the form's default submission. + event.preventDefault(); - // Prevent event from bubbling up DOM tree, prohibiting delegation - event.stopPropagation(); - - // Make an AJAX request to submit the form data + // Prevent event from bubbling up DOM tree, prohibiting delegation + event.stopPropagation(); + // Make an AJAX request to submit the form data }); ``` -When utilizing both `preventDefault` and `stopPropagation` simultaneously, you can instead `return false` to achieve both in a more concise manner, but it's advisable to only `return false` when both are actually necessary and not just for the sake of terseness. A final note on `stopPropagation` is that when using it in delegated events, the soonest that event bubbling can be stopped is when the event reaches the element that is delegating it. +When utilizing both `.preventDefault()` and `.stopPropagation()` simultaneously, you can instead `return false` to achieve both in a more concise manner, but it's advisable to only `return false` when both are actually necessary and not just for the sake of terseness. A final note on `.stopPropagation()` is that when using it in delegated events, the soonest that event bubbling can be stopped is when the event reaches the element that is delegating it. It's also important to note that the event object contains a property called `originalEvent`, which is the event object that the browser itself created. jQuery wraps this native event object with some useful methods and properties, but in some instances, you'll need to access the original event via `event.originalEvent` for instance. This is especially useful for touch events on mobile devices and tablets. Finally, to inspect the event itself and see all of the data it contains, you should log the event in the browser's console using `console.log`. This will allow you to see all of an event's properties (including the `originalEvent`) which can be really helpful for debugging. ``` -//Logging an event's information -$("form").on( "submit", function( event ) { - - // Prevent the form's default submission. - event.preventDefault(); +// Logging an event's information +$( "form" ).on( "submit", function( event ) { - // Log the event object for inspectin' - console.log( event ); + // Prevent the form's default submission. + event.preventDefault(); - // Make an AJAX request to submit the form data + // Log the event object for inspectin' + console.log( event ); + // Make an AJAX request to submit the form data }); ``` diff --git a/page/events/triggering-event-handlers.md b/page/events/triggering-event-handlers.md index 0ef21fc0..99d3a3a3 100644 --- a/page/events/triggering-event-handlers.md +++ b/page/events/triggering-event-handlers.md @@ -1,42 +1,34 @@ ---- -title : Triggering Event Handlers -level: intermediate -source: http://jqfundamentals.com/legacy -attribution: - - jQuery Fundamentals ---- - -jQuery provides a way to trigger the event handlers bound to an element without any user interaction via the -`.trigger()` method. - -## What handlers can be .trigger()'d - -jQuery's event handling system is a layer on top of native browser events. When an event handler is added using -`.on("click",function() {...})`, it can be triggered using jQuery's `.trigger("click")`because jQuery stores a -reference to that handler when it is originally added. Additionally, it will trigger the javascript inside the -"onclick" attribute. The `.trigger()` function cannot be used to mimic native browser events, such as -clicking on a file input box or an anchor tag. This is because, there is no event handler attached using jQuery's -event system that corresponds to these events. + + +jQuery provides a way to trigger the event handlers bound to an element without any user interaction via the `.trigger()` method. + +## What handlers can be .trigger()'d? + +jQuery's event handling system is a layer on top of native browser events. When an event handler is added using `.on( "click", function() {...} )`, it can be triggered using jQuery's `.trigger( "click" )` because jQuery stores a reference to that handler when it is originally added. Additionally, it will trigger the JavaScript inside the `onclick` attribute. The `.trigger()` function cannot be used to mimic native browser events, such as clicking on a file input box or an anchor tag. This is because, there is no event handler attached using jQuery's event system that corresponds to these events. ``` Learn jQuery ``` ``` -//This will not change the current page -$("a").trigger("click"); +// This will not change the current page +$( "a" ).trigger( "click" ); ``` ## How can I mimic a native browser event, if not `.trigger()`? -In order to trigger a native browser event, you have to use [document.createEventObject](http://msdn.microsoft.com/en-us/library/ie/ms536390%28v=vs.85%29.aspx) for < IE9 and [document.createEvent](https://developer.mozilla.org/en/DOM/document.createEvent) for all other browsers. -Using these two APIs, you can programmatically create an event that behaves exactly as if someone has actually clicked on a file input box. The default action will happen, and the browse file dialog will display. +In order to trigger a native browser event, you have to use [document.createEventObject](http://msdn.microsoft.com/en-us/library/ie/ms536390%28v=vs.85%29.aspx) for < IE9 and [document.createEvent](https://developer.mozilla.org/en-US/docs/Web/API/Document/createEvent) for all other browsers. Using these two APIs, you can programmatically create an event that behaves exactly as if someone has actually clicked on a file input box. The default action will happen, and the browse file dialog will display. -The jQuery UI Team created [jquery.simulate.js](https://github.com/eduardolundgren/jquery-simulate/blob/master/jquery.simulate.js) in order to simplify triggering a native browser event for use in their automated testing. Its usage is modeled after jQuery's trigger. +The jQuery UI Team created [jquery.simulate.js](https://github.com/jquery/jquery-simulate/) in order to simplify triggering a native browser event for use in their automated testing. Its usage is modeled after jQuery's trigger. ``` -//Triggering a native browser event using the simulate plugin -$("a").simulate("click"); +// Triggering a native browser event using the simulate plugin +$( "a" ).simulate( "click" ); ``` This will not only trigger the jQuery event handlers, but also follow the link and change the current page. @@ -51,37 +43,25 @@ There are four differences between `.trigger()` and `.triggerHandler()` 3. `.triggerHandler()` will not cause the default behavior of the event (such as a form submission). 4. Events triggered by `.triggerHandler()`, will not bubble up the DOM hierarchy. Only the handlers on the single element will fire. -For more information see the [triggerHandler documentation](http://api.jquery.com/triggerHandler) +For more information see the [triggerHandler documentation](http://api.jquery.com/triggerHandler/) ## Don't use `.trigger()` simply to execute specific functions -While this method has its uses, it should not be used simply to call a function that was bound as a click -handler. Instead, you should store the function you want to call in a -variable, and pass the variable name when you do your binding. Then, you can -call the function itself whenever you want, without the need for -`.trigger()`. +While this method has its uses, it should not be used simply to call a function that was bound as a click handler. Instead, you should store the function you want to call in a variable, and pass the variable name when you do your binding. Then, you can call the function itself whenever you want, without the need for `.trigger()`. ``` -//Triggering an event handler the right way +// Triggering an event handler the right way var foo = function( event ) { - - if ( event ) { - - console.log( event ); - - } else { - - console.log("this didn't come from an event!"); - - } - + if ( event ) { + console.log( event ); + } else { + console.log( "this didn't come from an event!" ); + } }; -$("p").on( 'click', foo ); +$( "p" ).on( "click", foo ); -foo(); // instead of $("p").trigger("click") +foo(); // instead of $( "p" ).trigger( "click" ) ``` -A more complex architecture can built on top of trigger using the [publish-subscribe pattern](http://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern) using [jQuery plugins](https://gist.github.com/661855). -With this technique, `$.fn.trigger` can be used to notify other sections of code that an application specific event has happened. - +A more complex architecture can be built on top of trigger using the [publish-subscribe pattern](http://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern) using [jQuery plugins](https://gist.github.com/661855). With this technique, `.trigger()` can be used to notify other sections of code that an application specific event has happened. diff --git a/page/index.html b/page/index.html index 562fbce5..fe5825a0 100644 --- a/page/index.html +++ b/page/index.html @@ -1,26 +1,30 @@ -