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/.gitmodules b/.gitmodules deleted file mode 100644 index eb9acf20..00000000 --- a/.gitmodules +++ /dev/null @@ -1,6 +0,0 @@ -[submodule "demos/large-jquery-apps"] - path = demos/large-jquery-apps - url = git://github.com/rmurphey/large-jquery-apps.git -[submodule "book/src/pt-BR"] - path = book/src/pt-BR - url = http://github.com/herberthamaral/jqfundamentals-pt-BR.git 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 fa29aad9..97125d06 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,188 +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 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! -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 -[`web-base-template`](http://github.com/jquery/web-base-template), 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/web-base-template/tree/master/themes/jquery) -controls most of the layout for all of our sites, and there is a [child -theme](https://github.com/jquery/web-base-template/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. -[`web-base-template`](http://github.com/jquery/web-base-template) 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 -[`web-base-template`](http://github.com/jquery/web-base-template) 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 editing files -directly via [GitHub's in-browser -editor](https://github.com/blog/905-edit-like-an-ace). But you won't be able to -create new content, and you'll still need a GitHub account and a fork of this -repository. So we encourage you to [learn how to use Git and -GitHub](http://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'll need - -* [git](http://git-scm.com) -* [GitHub](http://github.com) account -* Local PHP/MySQL environment, e.g., [MAMP](http://www.mamp.info/en/index.html), [XAMPP](http://www.apachefriends.org/en/xampp.html) -* [node.js](http://nodejs.org) +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. -### Initial Deploy +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). -Once you've got these major pieces in place, you'll want to get WordPress and -`web-base-template` running locally by following [these -instructions](https://github.com/jquery/web-base-template/blob/master/README.md). +*(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.)* -If you're get everything working right, you should be able navigate to -[local.learn.jquery.com](http://local.learn.jquery.com) in a browser, you -should see a site that looks exactly like the [live -site](http://learn.jquery.com), only without any actual articles. That's where -your the `learn.jquery.com` content repo comes into play. +## Local Development -1. Fork the [repository](https://github.com/jquery/learn.jquery.com) -2. Clone the repo -- `git clone git@github.com:/learn.jquery.com.git` -3. Enter the directory where you cloned the repo -- `cd learn.jquery.com` -4. Set up an upstream remote back to the jQuery repo -- 'git remote add upstream git@github.com:jquery/learn.jquery.com.git' -5. Install grunt (if you haven't already) -- `npm install -g grunt` -6. Install local build dependencies -- `npm install` -7. Copy the `config-sample.json` file to `config.json` -8. Edit `config.json` to use the username and password for your local WordPress network -9. Build and deploy the files to your local WordPress -- `grunt` +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 ``` -At this point, if you refresh your `local.learn.jquery.com`, your local -instance should be populated with all of the site content. If it isn't, -or you're having trouble with any of these steps, please come and -[seek out some assistance](#getting-help). - -### 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). -### Adding A New Article +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 -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/LICENSE.txt b/LICENSE.txt new file mode 100644 index 00000000..5bbeec51 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,44 @@ +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 +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +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 ebc3d3ff..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,64 +9,39 @@ 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/web-base-template/tree/master/themes/learn-jquery-com) of the jQuery [web base template](https://github.com/jquery/web-base-template), and any issues with the presentation should be directed to [that repository](https://github.com/jquery/web-base-template). +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. - +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. -### YAML Conventions -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: +### Front Matter -* title - the title of the article as it will appear in the site. if it contains special characters, put the string in quotes +Each of the articles on the site has some JSON "Front Matter" that contains metadata. All articles should include the following: -`title: "jQuery Event Extensions"` +* `title` - The title of the article as it will appear in the site. -* level - the approximate level of jQuery experience required to find the article useful. Options: `beginner`, `intermediate`, or `advanced` +`"title": "jQuery Event Extensions"` -`level: advanced` +* `level` - The approximate level of jQuery experience required to find the article useful. Options: `beginner`, `intermediate`, or `advanced`. -## Building && Working Locally +`"level": "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/web-base-template). To preview the site locally, first follow the [instructions there](https://github.com/jquery/web-base-template) to set up a local version of the jQuery WordPress network. Then, clone this repo and run the following steps (node.js required). +## Building and Deploying -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` +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/). -*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 -``` ## How Can I Help? We encourage contribution from anyone. For more comprehensive documentation on how to get involved, please read our [contributing guide](http://learn.jquery.com/contributing). - -# Copyright & Licensing - -This material is Copyright ©2011 The jQuery Foundation and licensed under -the [Creative Commons Attribution-Share Alike 3.0 United States -license](http://creativecommons.org/licenses/by-sa/3.0/us/). You are free to -copy, distribute, transmit, and remix this work, provided you attribute the -work to The jQuery Foundation as the original author and reference [this -repository](http://github.com/jquery/learn.jquery.com). If you alter, -transform, or build upon this work, you may distribute the resulting work only -under the same, similar or a compatible license. Any of the above conditions -can be waived if you get permission from the copyright holder. For any reuse or -distribution, you must make clear to others the license terms of this work. The -best way to do this is with a link to the [Creative Commons Attribution-Share -Alike 3.0 United States -license](http://creativecommons.org/licenses/by-sa/3.0/us/). 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 6ea213f9..00000000 --- a/order.yml +++ /dev/null @@ -1,99 +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 - - feature-browser-detection - - 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 - - working-with-events-part-1 - - event-delegation - - working-with-events-part-2 - - 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 - - clever-conditionals - - 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-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 2e698eed..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.1.0", - "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" - } - ], - "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": "1.0.2" - } + "name": "learn.jquery.com", + "private": true, + "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" + } } 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 004093ee..454bfe9f 100644 --- a/page/about.md +++ b/page/about.md @@ -1,69 +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 [Creative Commons Attribution-Share Alike 3.0 United States -license](http://creativecommons.org/licenses/by-sa/3.0/us/). 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 887f2e8a..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 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 noted 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 fdde6695..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() { - var $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 da1842bd..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 ad1186b0..d4b74e38 100644 --- a/page/code-organization/deferreds/examples.md +++ b/page/code-organization/deferreds/examples.md @@ -1,58 +1,45 @@ ---- -title: Deferreds -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 6c818fc8..0c3621c8 100644 --- a/page/code-organization/deferreds/jquery-deferreds.md +++ b/page/code-organization/deferreds/jquery-deferreds.md @@ -1,112 +1,87 @@ ---- -title: 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 067ca8b8..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 milleseconds 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 d06aa700..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 43061d42..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 the 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: * `` * `
  • ` -* `