diff --git a/.eslintignore b/.eslintignore
deleted file mode 100644
index da83a73..0000000
--- a/.eslintignore
+++ /dev/null
@@ -1,4 +0,0 @@
-!.eslintrc.js
-js/*.min.js
-js/vendor
-test/vendor
diff --git a/.eslintrc.js b/.eslintrc.js
deleted file mode 100644
index ccad27b..0000000
--- a/.eslintrc.js
+++ /dev/null
@@ -1,7 +0,0 @@
-module.exports = {
- extends: 'standard',
- plugins: [
- 'standard',
- 'promise'
- ]
-}
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..048b1cf
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1 @@
+github: [blueimp]
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000..7323d4f
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,30 @@
+name: Node CI
+
+on: [push, pull_request]
+
+jobs:
+ lint:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ node-version: [14, 16]
+ steps:
+ - uses: actions/checkout@v2
+ - uses: actions/setup-node@v2
+ with:
+ node-version: ${{ matrix.node-version }}
+ - run: npm install
+ - run: npm run lint
+
+ unit:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - name: mocha
+ run: docker-compose run --rm mocha
+ - name: docker-compose logs
+ if: always()
+ run: docker-compose logs nginx
+ - name: docker-compose down
+ if: always()
+ run: docker-compose down -v
diff --git a/.gitignore b/.gitignore
index 9daa824..3c3629e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1 @@
-.DS_Store
node_modules
diff --git a/.npmignore b/.npmignore
deleted file mode 100644
index a508bcb..0000000
--- a/.npmignore
+++ /dev/null
@@ -1,3 +0,0 @@
-*
-!js/*.js
-!js/*.js.map
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 878ef22..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,9 +0,0 @@
-dist: trusty
-addons:
- chrome: stable
-# Using sudo as workaround for
-# https://github.com/shellscape/mocha-chrome/pull/21
-sudo: required
-language: node_js
-node_js:
- - "stable"
diff --git a/LICENSE.txt b/LICENSE.txt
index e1b2c83..d6a9d74 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -2,20 +2,19 @@ MIT License
Copyright © 2011 Sebastian Tschan, https://blueimp.net
-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:
+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.
+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.
diff --git a/README.md b/README.md
index 2a03e0b..5759a12 100644
--- a/README.md
+++ b/README.md
@@ -2,35 +2,86 @@
> A JavaScript library to load and transform image files.
-## Table of contents
+## Contents
-- [Demo](#demo)
+- [Demo](https://blueimp.github.io/JavaScript-Load-Image/)
- [Description](#description)
- [Setup](#setup)
- [Usage](#usage)
-- [Image loading](#image-loading)
-- [Image scaling](#image-scaling)
+ - [Image loading](#image-loading)
+ - [Image scaling](#image-scaling)
- [Requirements](#requirements)
+- [Browser support](#browser-support)
- [API](#api)
+ - [Callback](#callback)
+ - [Function signature](#function-signature)
+ - [Cancel image loading](#cancel-image-loading)
+ - [Callback arguments](#callback-arguments)
+ - [Error handling](#error-handling)
+ - [Promise](#promise)
- [Options](#options)
-- [Meta data parsing](#meta-data-parsing)
-- [Exif parser](#exif-parser)
+ - [maxWidth](#maxwidth)
+ - [maxHeight](#maxheight)
+ - [minWidth](#minwidth)
+ - [minHeight](#minheight)
+ - [sourceWidth](#sourcewidth)
+ - [sourceHeight](#sourceheight)
+ - [top](#top)
+ - [right](#right)
+ - [bottom](#bottom)
+ - [left](#left)
+ - [contain](#contain)
+ - [cover](#cover)
+ - [aspectRatio](#aspectratio)
+ - [pixelRatio](#pixelratio)
+ - [downsamplingRatio](#downsamplingratio)
+ - [imageSmoothingEnabled](#imagesmoothingenabled)
+ - [imageSmoothingQuality](#imagesmoothingquality)
+ - [crop](#crop)
+ - [orientation](#orientation)
+ - [meta](#meta)
+ - [canvas](#canvas)
+ - [crossOrigin](#crossorigin)
+ - [noRevoke](#norevoke)
+- [Metadata parsing](#metadata-parsing)
+ - [Image head](#image-head)
+ - [Exif parser](#exif-parser)
+ - [Exif Thumbnail](#exif-thumbnail)
+ - [Exif IFD](#exif-ifd)
+ - [GPSInfo IFD](#gpsinfo-ifd)
+ - [Interoperability IFD](#interoperability-ifd)
+ - [Exif parser options](#exif-parser-options)
+ - [Exif writer](#exif-writer)
+ - [IPTC parser](#iptc-parser)
+ - [IPTC parser options](#iptc-parser-options)
- [License](#license)
- [Credits](#credits)
-## Demo
-[JavaScript Load Image Demo](https://blueimp.github.io/JavaScript-Load-Image/)
-
## Description
-JavaScript Load Image is a library to load images provided as File or Blob
-objects or via URL.
-It returns an optionally scaled and/or cropped HTML img or canvas element via an
-asynchronous callback.
-It also provides a method to parse image meta data to extract Exif tags and
-thumbnails and to restore the complete image header after resizing.
+
+JavaScript Load Image is a library to load images provided as `File` or `Blob`
+objects or via `URL`. It returns an optionally **scaled**, **cropped** or
+**rotated** HTML `img` or `canvas` element.
+
+It also provides methods to parse image metadata to extract
+[IPTC](https://iptc.org/standards/photo-metadata/) and
+[Exif](https://en.wikipedia.org/wiki/Exif) tags as well as embedded thumbnail
+images, to overwrite the Exif Orientation value and to restore the complete
+image header after resizing.
## Setup
-Include the (combined and minified) JavaScript Load Image script in your HTML
+
+Install via [NPM](https://www.npmjs.com/package/blueimp-load-image):
+
+```sh
+npm install blueimp-load-image
+```
+
+This will install the JavaScript files inside
+`./node_modules/blueimp-load-image/js/` relative to your current directory, from
+where you can copy them into a folder that is served by your web server.
+
+Next include the combined and minified JavaScript Load Image script in your HTML
markup:
```html
@@ -40,287 +91,980 @@ markup:
Or alternatively, choose which components you want to include:
```html
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
```
## Usage
### Image loading
-In your application code, use the **loadImage()** function like this:
+
+In your application code, use the `loadImage()` function with
+[callback](#callback) style:
```js
-document.getElementById('file-input').onchange = function (e) {
- loadImage(
- e.target.files[0],
- function (img) {
- document.body.appendChild(img);
- },
- {maxWidth: 600} // Options
- );
-};
+document.getElementById('file-input').onchange = function () {
+ loadImage(
+ this.files[0],
+ function (img) {
+ document.body.appendChild(img)
+ },
+ { maxWidth: 600 } // Options
+ )
+}
+```
+
+Or use the [Promise](#promise) based API like this ([requires](#requirements) a
+polyfill for older browsers):
+
+```js
+document.getElementById('file-input').onchange = function () {
+ loadImage(this.files[0], { maxWidth: 600 }).then(function (data) {
+ document.body.appendChild(data.image)
+ })
+}
+```
+
+With
+[async/await](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Async_await)
+(requires a modern browser or a code transpiler like
+[Babel](https://babeljs.io/) or [TypeScript](https://www.typescriptlang.org/)):
+
+```js
+document.getElementById('file-input').onchange = async function () {
+ let data = await loadImage(this.files[0], { maxWidth: 600 })
+ document.body.appendChild(data.image)
+}
```
### Image scaling
-It is also possible to use the image scaling functionality with an existing
-image:
+
+It is also possible to use the image scaling functionality directly with an
+existing image:
```js
var scaledImage = loadImage.scale(
- img, // img or canvas element
- {maxWidth: 600}
-);
+ img, // img or canvas element
+ { maxWidth: 600 }
+)
```
## Requirements
-The JavaScript Load Image library has zero dependencies.
-However, JavaScript Load Image is a very suitable complement to the
-[Canvas to Blob](https://github.com/blueimp/JavaScript-Canvas-to-Blob) library.
+The JavaScript Load Image library has zero dependencies, but benefits from the
+following two
+[polyfills](https://developer.mozilla.org/en-US/docs/Glossary/Polyfill):
+
+- [blueimp-canvas-to-blob](https://github.com/blueimp/JavaScript-Canvas-to-Blob)
+ for browsers without native
+ [HTMLCanvasElement.toBlob](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob)
+ support, to create `Blob` objects out of `canvas` elements.
+- [promise-polyfill](https://github.com/taylorhakes/promise-polyfill) to be able
+ to use the
+ [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
+ based `loadImage` API in Browsers without native `Promise` support.
+
+## Browser support
+
+Browsers which implement the following APIs support all options:
+
+- Loading images from File and Blob objects:
+ - [URL.createObjectURL](https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL)
+ or
+ [FileReader.readAsDataURL](https://developer.mozilla.org/en-US/docs/Web/API/FileReader/readAsDataURL)
+- Parsing meta data:
+ - [FileReader.readAsArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/API/FileReader/readAsArrayBuffer)
+ - [Blob.slice](https://developer.mozilla.org/en-US/docs/Web/API/Blob/slice)
+ - [DataView](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView)
+ (no [BigInt](https://developer.mozilla.org/en-US/docs/Glossary/BigInt)
+ support required)
+- Parsing meta data from images loaded via URL:
+ - [fetch Response.blob](https://developer.mozilla.org/en-US/docs/Web/API/Body/blob)
+ or
+ [XMLHttpRequest.responseType blob](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseType#blob)
+- Promise based API:
+ - [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
+
+This includes (but is not limited to) the following browsers:
+
+- Chrome 32+
+- Firefox 29+
+- Safari 8+
+- Mobile Chrome 42+ (Android)
+- Mobile Firefox 50+ (Android)
+- Mobile Safari 8+ (iOS)
+- Edge 74+
+- Edge Legacy 12+
+- Internet Explorer 10+ `*`
+
+`*` Internet Explorer [requires](#requirements) a polyfill for the `Promise`
+based API.
+
+Loading an image from a URL and applying transformations (scaling, cropping and
+rotating - except `orientation:true`, which requires reading meta data) is
+supported by all browsers which implement the
+[HTMLCanvasElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement)
+interface.
+
+Loading an image from a URL and scaling it in size is supported by all browsers
+which implement the
+[img](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img) element and
+has been tested successfully with browser engines as old as Internet Explorer 5
+(via
+[IE11's emulation mode]()).
+
+The `loadImage()` function applies options using
+[progressive enhancement](https://en.wikipedia.org/wiki/Progressive_enhancement)
+and falls back to a configuration that is supported by the browser, e.g. if the
+`canvas` element is not supported, an equivalent `img` element is returned.
## API
-The **loadImage()** function accepts a
-[File](https://developer.mozilla.org/en/DOM/File) or
-[Blob](https://developer.mozilla.org/en/DOM/Blob) object or a simple image URL
-(e.g. `'https://example.org/image.png'`) as first argument.
-
-If a [File](https://developer.mozilla.org/en/DOM/File) or
-[Blob](https://developer.mozilla.org/en/DOM/Blob) is passed as parameter, it
-returns a HTML **img** element if the browser supports the
-[URL](https://developer.mozilla.org/en/DOM/window.URL) API or a
-[FileReader](https://developer.mozilla.org/en/DOM/FileReader) object if
-supported, or **false**.
-It always returns a HTML
-[img](https://developer.mozilla.org/en/docs/HTML/Element/Img) element when
-passing an image URL:
+
+### Callback
+
+#### Function signature
+
+The `loadImage()` function accepts a
+[File](https://developer.mozilla.org/en-US/docs/Web/API/File) or
+[Blob](https://developer.mozilla.org/en-US/docs/Web/API/Blob) object or an image
+URL as first argument.
+
+If a [File](https://developer.mozilla.org/en-US/docs/Web/API/File) or
+[Blob](https://developer.mozilla.org/en-US/docs/Web/API/Blob) is passed as
+parameter, it returns an HTML `img` element if the browser supports the
+[URL](https://developer.mozilla.org/en-US/docs/Web/API/URL) API, alternatively a
+[FileReader](https://developer.mozilla.org/en-US/docs/Web/API/FileReader) object
+if the `FileReader` API is supported, or `false`.
+
+It always returns an HTML
+[img](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Img) element
+when passing an image URL:
```js
-document.getElementById('file-input').onchange = function (e) {
- var loadingImage = loadImage(
- e.target.files[0],
- function (img) {
- document.body.appendChild(img);
- },
- {maxWidth: 600}
- );
- if (!loadingImage) {
- // Alternative code ...
- }
-};
+var loadingImage = loadImage(
+ 'https://example.org/image.png',
+ function (img) {
+ document.body.appendChild(img)
+ },
+ { maxWidth: 600 }
+)
```
-The **img** element or
-[FileReader](https://developer.mozilla.org/en/DOM/FileReader) object returned by
-the **loadImage()** function allows to abort the loading process by setting the
-**onload** and **onerror** event handlers to null:
+#### Cancel image loading
+
+Some browsers (e.g. Chrome) will cancel the image loading process if the `src`
+property of an `img` element is changed.
+To avoid unnecessary requests, we can use the
+[data URL](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs)
+of a 1x1 pixel transparent GIF image as `src` target to cancel the original
+image download.
+
+To disable callback handling, we can also unset the image event handlers and for
+maximum browser compatibility, cancel the file reading process if the returned
+object is a
+[FileReader](https://developer.mozilla.org/en-US/docs/Web/API/FileReader)
+instance:
```js
-document.getElementById('file-input').onchange = function (e) {
- var loadingImage = loadImage(
- e.target.files[0],
- function (img) {
- document.body.appendChild(img);
- },
- {maxWidth: 600}
- );
- loadingImage.onload = loadingImage.onerror = null;
-};
+var loadingImage = loadImage(
+ 'https://example.org/image.png',
+ function (img) {
+ document.body.appendChild(img)
+ },
+ { maxWidth: 600 }
+)
+
+if (loadingImage) {
+ // Unset event handling for the loading image:
+ loadingImage.onload = loadingImage.onerror = null
+
+ // Cancel image loading process:
+ if (loadingImage.abort) {
+ // FileReader instance, stop the file reading process:
+ loadingImage.abort()
+ } else {
+ // HTMLImageElement element, cancel the original image request by changing
+ // the target source to the data URL of a 1x1 pixel transparent image GIF:
+ loadingImage.src =
+ 'data:image/gif;base64,' +
+ 'R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'
+ }
+}
```
-The second argument must be a **callback** function, which is called when the
-image has been loaded or an error occurred while loading the image. The callback
-function is passed one argument, which is either a HTML **img** element, a
-[canvas](https://developer.mozilla.org/en/HTML/Canvas) element, or an
-[Event](https://developer.mozilla.org/en/DOM/event) object of type **error**:
+**Please note:**
+The `img` element (or `FileReader` instance) for the loading image is only
+returned when using the callback style API and not available with the
+[Promise](#promise) based API.
+
+#### Callback arguments
+
+For the callback style API, the second argument to `loadImage()` must be a
+`callback` function, which is called when the image has been loaded or an error
+occurred while loading the image.
+
+The callback function is passed two arguments:
+
+1. An HTML [img](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img)
+ element or
+ [canvas](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API)
+ element, or an
+ [Event](https://developer.mozilla.org/en-US/docs/Web/API/Event) object of
+ type `error`.
+2. An object with the original image dimensions as properties and potentially
+ additional [metadata](#metadata-parsing).
```js
-var imageUrl = "https://example.org/image.png";
loadImage(
- imageUrl,
- function (img) {
- if(img.type === "error") {
- console.log("Error loading image " + imageUrl);
- } else {
- document.body.appendChild(img);
- }
- },
- {maxWidth: 600}
-);
+ fileOrBlobOrUrl,
+ function (img, data) {
+ document.body.appendChild(img)
+ console.log('Original image width: ', data.originalWidth)
+ console.log('Original image height: ', data.originalHeight)
+ },
+ { maxWidth: 600, meta: true }
+)
+```
+
+**Please note:**
+The original image dimensions reflect the natural width and height of the loaded
+image before applying any transformation.
+For consistent values across browsers, [metadata](#metadata-parsing) parsing has
+to be enabled via `meta:true`, so `loadImage` can detect automatic image
+orientation and normalize the dimensions.
+
+#### Error handling
+
+Example code implementing error handling:
+
+```js
+loadImage(
+ fileOrBlobOrUrl,
+ function (img, data) {
+ if (img.type === 'error') {
+ console.error('Error loading image file')
+ } else {
+ document.body.appendChild(img)
+ }
+ },
+ { maxWidth: 600 }
+)
```
+### Promise
+
+If the `loadImage()` function is called without a `callback` function as second
+argument and the
+[Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
+API is available, it returns a `Promise` object:
+
+```js
+loadImage(fileOrBlobOrUrl, { maxWidth: 600, meta: true })
+ .then(function (data) {
+ document.body.appendChild(data.image)
+ console.log('Original image width: ', data.originalWidth)
+ console.log('Original image height: ', data.originalHeight)
+ })
+ .catch(function (err) {
+ // Handling image loading errors
+ console.log(err)
+ })
+```
+
+The `Promise` resolves with an object with the following properties:
+
+- `image`: An HTML
+ [img](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img) or
+ [canvas](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API) element.
+- `originalWidth`: The original width of the image.
+- `originalHeight`: The original height of the image.
+
+Please also read the note about original image dimensions normalization in the
+[callback arguments](#callback-arguments) section.
+
+If [metadata](#metadata-parsing) has been parsed, additional properties might be
+present on the object.
+
+If image loading fails, the `Promise` rejects with an
+[Event](https://developer.mozilla.org/en-US/docs/Web/API/Event) object of type
+`error`.
+
## Options
-The optional third argument to **loadImage()** is a map of options:
-
-* **maxWidth**: Defines the maximum width of the img/canvas element.
-* **maxHeight**: Defines the maximum height of the img/canvas element.
-* **minWidth**: Defines the minimum width of the img/canvas element.
-* **minHeight**: Defines the minimum height of the img/canvas element.
-* **sourceWidth**: The width of the sub-rectangle of the source image to draw
-into the destination canvas.
+
+The optional options argument to `loadImage()` allows to configure the image
+loading.
+
+It can be used the following way with the callback style:
+
+```js
+loadImage(
+ fileOrBlobOrUrl,
+ function (img) {
+ document.body.appendChild(img)
+ },
+ {
+ maxWidth: 600,
+ maxHeight: 300,
+ minWidth: 100,
+ minHeight: 50,
+ canvas: true
+ }
+)
+```
+
+Or the following way with the `Promise` based API:
+
+```js
+loadImage(fileOrBlobOrUrl, {
+ maxWidth: 600,
+ maxHeight: 300,
+ minWidth: 100,
+ minHeight: 50,
+ canvas: true
+}).then(function (data) {
+ document.body.appendChild(data.image)
+})
+```
+
+All settings are optional. By default, the image is returned as HTML `img`
+element without any image size restrictions.
+
+### maxWidth
+
+Defines the maximum width of the `img`/`canvas` element.
+
+### maxHeight
+
+Defines the maximum height of the `img`/`canvas` element.
+
+### minWidth
+
+Defines the minimum width of the `img`/`canvas` element.
+
+### minHeight
+
+Defines the minimum height of the `img`/`canvas` element.
+
+### sourceWidth
+
+The width of the sub-rectangle of the source image to draw into the destination
+canvas.
Defaults to the source image width and requires `canvas: true`.
-* **sourceHeight**: The height of the sub-rectangle of the source image to draw
-into the destination canvas.
+
+### sourceHeight
+
+The height of the sub-rectangle of the source image to draw into the destination
+canvas.
Defaults to the source image height and requires `canvas: true`.
-* **top**: The top margin of the sub-rectangle of the source image.
+
+### top
+
+The top margin of the sub-rectangle of the source image.
Defaults to `0` and requires `canvas: true`.
-* **right**: The right margin of the sub-rectangle of the source image.
+
+### right
+
+The right margin of the sub-rectangle of the source image.
Defaults to `0` and requires `canvas: true`.
-* **bottom**: The bottom margin of the sub-rectangle of the source image.
+
+### bottom
+
+The bottom margin of the sub-rectangle of the source image.
Defaults to `0` and requires `canvas: true`.
-* **left**: The left margin of the sub-rectangle of the source image.
+
+### left
+
+The left margin of the sub-rectangle of the source image.
Defaults to `0` and requires `canvas: true`.
-* **contain**: Scales the image up/down to contain it in the max dimensions if
-set to `true`.
+
+### contain
+
+Scales the image up/down to contain it in the max dimensions if set to `true`.
This emulates the CSS feature
-[background-image: contain](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Scaling_background_images#contain).
-* **cover**: Scales the image up/down to cover the max dimensions with the image
-dimensions if set to `true`.
+[background-image: contain](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Backgrounds_and_Borders/Resizing_background_images#contain).
+
+### cover
+
+Scales the image up/down to cover the max dimensions with the image dimensions
+if set to `true`.
This emulates the CSS feature
-[background-image: cover](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Scaling_background_images#cover).
-* **aspectRatio**: Crops the image to the given aspect ratio (e.g. `16/9`).
+[background-image: cover](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Backgrounds_and_Borders/Resizing_background_images#cover).
+
+### aspectRatio
+
+Crops the image to the given aspect ratio (e.g. `16/9`).
Setting the `aspectRatio` also enables the `crop` option.
-* **pixelRatio**: Defines the ratio of the canvas pixels to the physical image
-pixels on the screen.
-Should be set to `window.devicePixelRatio` unless the scaled image is not
-rendered on screen.
+
+### pixelRatio
+
+Defines the ratio of the canvas pixels to the physical image pixels on the
+screen.
+Should be set to
+[window.devicePixelRatio](https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio)
+unless the scaled image is not rendered on screen.
Defaults to `1` and requires `canvas: true`.
-* **downsamplingRatio**: Defines the ratio in which the image is downsampled.
-By default, images are downsampled in one step. With a ratio of `0.5`, each step
-scales the image to half the size, before reaching the target dimensions.
+
+### downsamplingRatio
+
+Defines the ratio in which the image is downsampled (scaled down in steps).
+By default, images are downsampled in one step.
+With a ratio of `0.5`, each step scales the image to half the size, before
+reaching the target dimensions.
Requires `canvas: true`.
-* **crop**: Crops the image to the maxWidth/maxHeight constraints if set to
-`true`.
+
+### imageSmoothingEnabled
+
+If set to `false`,
+[disables image smoothing](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/imageSmoothingEnabled).
+Defaults to `true` and requires `canvas: true`.
+
+### imageSmoothingQuality
+
+Sets the
+[quality of image smoothing](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/imageSmoothingQuality).
+Possible values: `'low'`, `'medium'`, `'high'`
+Defaults to `'low'` and requires `canvas: true`.
+
+### crop
+
+Crops the image to the `maxWidth`/`maxHeight` constraints if set to `true`.
Enabling the `crop` option also enables the `canvas` option.
-* **orientation**: Transform the canvas according to the specified Exif
-orientation, which can be an `integer` in the range of `1` to `8` or the boolean
-value `true`.
-When set to `true`, it will set the orientation value based on the EXIF data of
-the image, which will be parsed automatically if the exif library is available.
-Setting the `orientation` also enables the `canvas` option.
-Setting `orientation` to `true` also enables the `meta` option.
-* **meta**: Automatically parses the image meta data if set to `true`.
-The meta data is passed to the callback as second argument.
+
+### orientation
+
+Transform the canvas according to the specified Exif orientation, which can be
+an `integer` in the range of `1` to `8` or the boolean value `true`.
+
+When set to `true`, it will set the orientation value based on the Exif data of
+the image, which will be parsed automatically if the Exif extension is
+available.
+
+Exif orientation values to correctly display the letter F:
+
+```
+ 1 2
+ ██████ ██████
+ ██ ██
+ ████ ████
+ ██ ██
+ ██ ██
+
+ 3 4
+ ██ ██
+ ██ ██
+ ████ ████
+ ██ ██
+ ██████ ██████
+
+ 5 6
+██████████ ██
+██ ██ ██ ██
+██ ██████████
+
+ 7 8
+ ██ ██████████
+ ██ ██ ██ ██
+██████████ ██
+```
+
+Setting `orientation` to `true` enables the `canvas` and `meta` options, unless
+the browser supports automatic image orientation (see
+[browser support for image-orientation](https://caniuse.com/#feat=css-image-orientation)).
+
+Setting `orientation` to `1` enables the `canvas` and `meta` options if the
+browser does support automatic image orientation (to allow reset of the
+orientation).
+
+Setting `orientation` to an integer in the range of `2` to `8` always enables
+the `canvas` option and also enables the `meta` option if the browser supports
+automatic image orientation (again to allow reset).
+
+### meta
+
+Automatically parses the image metadata if set to `true`.
+
+If metadata has been found, the data object passed as second argument to the
+callback function has additional properties (see
+[metadata parsing](#metadata-parsing)).
+
If the file is given as URL and the browser supports the
-[fetch API](https://developer.mozilla.org/en/docs/Web/API/Fetch_API), fetches
-the file as Blob to be able to parse the meta data.
-* **canvas**: Returns the image as
-[canvas](https://developer.mozilla.org/en/HTML/Canvas) element if set to `true`.
-* **crossOrigin**: Sets the crossOrigin property on the img element for loading
-[CORS enabled images](https://developer.mozilla.org/en-US/docs/HTML/CORS_Enabled_Image).
-* **noRevoke**: By default, the
-[created object URL](https://developer.mozilla.org/en/DOM/window.URL.createObjectURL)
+[fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) or the
+XHR
+[responseType](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseType)
+`blob`, fetches the file as `Blob` to be able to parse the metadata.
+
+### canvas
+
+Returns the image as
+[canvas](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API) element if
+set to `true`.
+
+### crossOrigin
+
+Sets the `crossOrigin` property on the `img` element for loading
+[CORS enabled images](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image).
+
+### noRevoke
+
+By default, the
+[created object URL](https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL)
is revoked after the image has been loaded, except when this option is set to
`true`.
-They can be used the following way:
+## Metadata parsing
+
+If the Load Image Meta extension is included, it is possible to parse image meta
+data automatically with the `meta` option:
```js
loadImage(
- fileOrBlobOrUrl,
- function (img) {
- document.body.appendChild(img);
- },
- {
- maxWidth: 600,
- maxHeight: 300,
- minWidth: 100,
- minHeight: 50,
- canvas: true
- }
-);
+ fileOrBlobOrUrl,
+ function (img, data) {
+ console.log('Original image head: ', data.imageHead)
+ console.log('Exif data: ', data.exif) // requires exif extension
+ console.log('IPTC data: ', data.iptc) // requires iptc extension
+ },
+ { meta: true }
+)
```
-All settings are optional. By default, the image is returned as HTML **img**
-element without any image size restrictions.
-
-## Meta data parsing
-If the Load Image Meta extension is included, it is also possible to parse image
-meta data.
-The extension provides the method **loadImage.parseMetaData**, which can be used
-the following way:
+Or alternatively via `loadImage.parseMetaData`, which can be used with an
+available `File` or `Blob` object as first argument:
```js
loadImage.parseMetaData(
- fileOrBlob,
- function (data) {
- if (!data.imageHead) {
- return;
- }
- // Combine data.imageHead with the image body of a resized file
- // to create scaled images with the original image meta data, e.g.:
- var blob = new Blob([
- data.imageHead,
- // Resized images always have a head size of 20 bytes,
- // including the JPEG marker and a minimal JFIF header:
- loadImage.blobSlice.call(resizedImage, 20)
- ], {type: resizedImage.type});
- },
- {
- maxMetaDataSize: 262144,
- disableImageHead: false
+ fileOrBlob,
+ function (data) {
+ console.log('Original image head: ', data.imageHead)
+ console.log('Exif data: ', data.exif) // requires exif extension
+ console.log('IPTC data: ', data.iptc) // requires iptc extension
+ },
+ {
+ maxMetaDataSize: 262144
+ }
+)
+```
+
+Or using the
+[Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
+based API:
+
+```js
+loadImage
+ .parseMetaData(fileOrBlob, {
+ maxMetaDataSize: 262144
+ })
+ .then(function (data) {
+ console.log('Original image head: ', data.imageHead)
+ console.log('Exif data: ', data.exif) // requires exif extension
+ console.log('IPTC data: ', data.iptc) // requires iptc extension
+ })
+```
+
+The Metadata extension adds additional options used for the `parseMetaData`
+method:
+
+- `maxMetaDataSize`: Maximum number of bytes of metadata to parse.
+- `disableImageHead`: Disable parsing the original image head.
+- `disableMetaDataParsers`: Disable parsing metadata (image head only)
+
+### Image head
+
+Resized JPEG images can be combined with their original image head via
+`loadImage.replaceHead`, which requires the resized image as `Blob` object as
+first argument and an `ArrayBuffer` image head as second argument.
+
+With callback style, the third argument must be a `callback` function, which is
+called with the new `Blob` object:
+
+```js
+loadImage(
+ fileOrBlobOrUrl,
+ function (img, data) {
+ if (data.imageHead) {
+ img.toBlob(function (blob) {
+ loadImage.replaceHead(blob, data.imageHead, function (newBlob) {
+ // do something with the new Blob object
+ })
+ }, 'image/jpeg')
}
-);
+ },
+ { meta: true, canvas: true, maxWidth: 800 }
+)
```
-The third argument is an options object which defines the maximum number of
-bytes to parse for the image meta data, allows to disable the imageHead creation
-and is also passed along to segment parsers registered via loadImage extensions,
-e.g. the Exif parser.
+Or using the
+[Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
+based API like this:
-**Note:**
-Blob objects of resized images can be created via
-[canvas.toBlob()](https://github.com/blueimp/JavaScript-Canvas-to-Blob).
+```js
+loadImage(fileOrBlobOrUrl, { meta: true, canvas: true, maxWidth: 800 })
+ .then(function (data) {
+ if (!data.imageHead) throw new Error('Could not parse image metadata')
+ return new Promise(function (resolve) {
+ data.image.toBlob(function (blob) {
+ data.blob = blob
+ resolve(data)
+ }, 'image/jpeg')
+ })
+ })
+ .then(function (data) {
+ return loadImage.replaceHead(data.blob, data.imageHead)
+ })
+ .then(function (blob) {
+ // do something with the new Blob object
+ })
+ .catch(function (err) {
+ console.error(err)
+ })
+```
+
+**Please note:**
+`Blob` objects of resized images can be created via
+[HTMLCanvasElement.toBlob](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob).
+[blueimp-canvas-to-blob](https://github.com/blueimp/JavaScript-Canvas-to-Blob)
+provides a polyfill for browsers without native `canvas.toBlob()` support.
### Exif parser
+
If you include the Load Image Exif Parser extension, the argument passed to the
-callback for **parseMetaData** will contain the additional property **exif** if
-Exif data could be found in the given image.
-The **exif** object stores the parsed Exif tags:
+callback for `parseMetaData` will contain the following additional properties if
+Exif data could be found in the given image:
+
+- `exif`: The parsed Exif tags
+- `exifOffsets`: The parsed Exif tag offsets
+- `exifTiffOffset`: TIFF header offset (used for offset pointers)
+- `exifLittleEndian`: little endian order if true, big endian if false
+
+The `exif` object stores the parsed Exif tags:
```js
-var orientation = data.exif[0x0112];
+var orientation = data.exif[0x0112] // Orientation
```
-It also provides an **exif.get()** method to retrieve the tag value via the
-tag's mapped name:
+The `exif` and `exifOffsets` objects also provide a `get()` method to retrieve
+the tag value/offset via the tag's mapped name:
```js
-var orientation = data.exif.get('Orientation');
+var orientation = data.exif.get('Orientation')
+var orientationOffset = data.exifOffsets.get('Orientation')
```
-By default, the only available mapped names are **Orientation** and
-**Thumbnail**.
+By default, only the following names are mapped:
+
+- `Orientation`
+- `Thumbnail` (see [Exif Thumbnail](#exif-thumbnail))
+- `Exif` (see [Exif IFD](#exif-ifd))
+- `GPSInfo` (see [GPSInfo IFD](#gpsinfo-ifd))
+- `Interoperability` (see [Interoperability IFD](#interoperability-ifd))
+
If you also include the Load Image Exif Map library, additional tag mappings
-become available, as well as two additional methods, **exif.getText()** and
-**exif.getAll()**:
+become available, as well as three additional methods:
+
+- `exif.getText()`
+- `exif.getName()`
+- `exif.getAll()`
+
+```js
+var orientationText = data.exif.getText('Orientation') // e.g. "Rotate 90° CW"
+
+var name = data.exif.getName(0x0112) // "Orientation"
+
+// A map of all parsed tags with their mapped names/text as keys/values:
+var allTags = data.exif.getAll()
+```
+
+#### Exif Thumbnail
+
+Example code displaying a thumbnail image embedded into the Exif metadata:
+
+```js
+loadImage(
+ fileOrBlobOrUrl,
+ function (img, data) {
+ var exif = data.exif
+ var thumbnail = exif && exif.get('Thumbnail')
+ var blob = thumbnail && thumbnail.get('Blob')
+ if (blob) {
+ loadImage(
+ blob,
+ function (thumbImage) {
+ document.body.appendChild(thumbImage)
+ },
+ { orientation: exif.get('Orientation') }
+ )
+ }
+ },
+ { meta: true }
+)
+```
+
+#### Exif IFD
+
+Example code displaying data from the Exif IFD (Image File Directory) that
+contains Exif specified TIFF tags:
+
+```js
+loadImage(
+ fileOrBlobOrUrl,
+ function (img, data) {
+ var exifIFD = data.exif && data.exif.get('Exif')
+ if (exifIFD) {
+ // Map of all Exif IFD tags with their mapped names/text as keys/values:
+ console.log(exifIFD.getAll())
+ // A specific Exif IFD tag value:
+ console.log(exifIFD.get('UserComment'))
+ }
+ },
+ { meta: true }
+)
+```
+
+#### GPSInfo IFD
+
+Example code displaying data from the Exif IFD (Image File Directory) that
+contains [GPS](https://en.wikipedia.org/wiki/Global_Positioning_System) info:
+
+```js
+loadImage(
+ fileOrBlobOrUrl,
+ function (img, data) {
+ var gpsInfo = data.exif && data.exif.get('GPSInfo')
+ if (gpsInfo) {
+ // Map of all GPSInfo tags with their mapped names/text as keys/values:
+ console.log(gpsInfo.getAll())
+ // A specific GPSInfo tag value:
+ console.log(gpsInfo.get('GPSLatitude'))
+ }
+ },
+ { meta: true }
+)
+```
+
+#### Interoperability IFD
+
+Example code displaying data from the Exif IFD (Image File Directory) that
+contains Interoperability data:
+
+```js
+loadImage(
+ fileOrBlobOrUrl,
+ function (img, data) {
+ var interoperabilityData = data.exif && data.exif.get('Interoperability')
+ if (interoperabilityData) {
+ // The InteroperabilityIndex tag value:
+ console.log(interoperabilityData.get('InteroperabilityIndex'))
+ }
+ },
+ { meta: true }
+)
+```
+
+#### Exif parser options
+
+The Exif parser adds additional options:
+
+- `disableExif`: Disables Exif parsing when `true`.
+- `disableExifOffsets`: Disables storing Exif tag offsets when `true`.
+- `includeExifTags`: A map of Exif tags to include for parsing (includes all but
+ the excluded tags by default).
+- `excludeExifTags`: A map of Exif tags to exclude from parsing (defaults to
+ exclude `Exif` `MakerNote`).
+
+An example parsing only Orientation, Thumbnail and ExifVersion tags:
+
+```js
+loadImage.parseMetaData(
+ fileOrBlob,
+ function (data) {
+ console.log('Exif data: ', data.exif)
+ },
+ {
+ includeExifTags: {
+ 0x0112: true, // Orientation
+ ifd1: {
+ 0x0201: true, // JPEGInterchangeFormat (Thumbnail data offset)
+ 0x0202: true // JPEGInterchangeFormatLength (Thumbnail data length)
+ },
+ 0x8769: {
+ // ExifIFDPointer
+ 0x9000: true // ExifVersion
+ }
+ }
+ }
+)
+```
+
+An example excluding `Exif` `MakerNote` and `GPSInfo`:
+
+```js
+loadImage.parseMetaData(
+ fileOrBlob,
+ function (data) {
+ console.log('Exif data: ', data.exif)
+ },
+ {
+ excludeExifTags: {
+ 0x8769: {
+ // ExifIFDPointer
+ 0x927c: true // MakerNote
+ },
+ 0x8825: true // GPSInfoIFDPointer
+ }
+ }
+)
+```
+
+### Exif writer
+
+The Exif parser extension also includes a minimal writer that allows to override
+the Exif `Orientation` value in the parsed `imageHead` `ArrayBuffer`:
+
+```js
+loadImage(
+ fileOrBlobOrUrl,
+ function (img, data) {
+ if (data.imageHead && data.exif) {
+ // Reset Exif Orientation data:
+ loadImage.writeExifData(data.imageHead, data, 'Orientation', 1)
+ img.toBlob(function (blob) {
+ loadImage.replaceHead(blob, data.imageHead, function (newBlob) {
+ // do something with newBlob
+ })
+ }, 'image/jpeg')
+ }
+ },
+ { meta: true, orientation: true, canvas: true, maxWidth: 800 }
+)
+```
+
+**Please note:**
+The Exif writer relies on the Exif tag offsets being available as
+`data.exifOffsets` property, which requires that Exif data has been parsed from
+the image.
+The Exif writer can only change existing values, not add new tags, e.g. it
+cannot add an Exif `Orientation` tag for an image that does not have one.
+
+### IPTC parser
+
+If you include the Load Image IPTC Parser extension, the argument passed to the
+callback for `parseMetaData` will contain the following additional properties if
+IPTC data could be found in the given image:
+
+- `iptc`: The parsed IPTC tags
+- `iptcOffsets`: The parsed IPTC tag offsets
+
+The `iptc` object stores the parsed IPTC tags:
```js
-var flashText = data.exif.getText('Flash'); // e.g.: 'Flash fired, auto mode',
+var objectname = data.iptc[5]
+```
+
+The `iptc` and `iptcOffsets` objects also provide a `get()` method to retrieve
+the tag value/offset via the tag's mapped name:
-// A map of all parsed tags with their mapped names as keys and their text values:
-var allTags = data.exif.getAll();
+```js
+var objectname = data.iptc.get('ObjectName')
```
-The Exif parser also adds additional options for the parseMetaData method, to
-disable certain aspects of the parser:
+By default, only the following names are mapped:
+
+- `ObjectName`
+
+If you also include the Load Image IPTC Map library, additional tag mappings
+become available, as well as three additional methods:
+
+- `iptc.getText()`
+- `iptc.getName()`
+- `iptc.getAll()`
-* **disableExif**: Disables Exif parsing.
-* **disableExifThumbnail**: Disables parsing of the Exif Thumbnail.
-* **disableExifSub**: Disables parsing of the Exif Sub IFD.
-* **disableExifGps**: Disables parsing of the Exif GPS Info IFD.
+```js
+var keywords = data.iptc.getText('Keywords') // e.g.: ['Weather','Sky']
+
+var name = data.iptc.getName(5) // ObjectName
+
+// A map of all parsed tags with their mapped names/text as keys/values:
+var allTags = data.iptc.getAll()
+```
+
+#### IPTC parser options
+
+The IPTC parser adds additional options:
+
+- `disableIptc`: Disables IPTC parsing when true.
+- `disableIptcOffsets`: Disables storing IPTC tag offsets when `true`.
+- `includeIptcTags`: A map of IPTC tags to include for parsing (includes all but
+ the excluded tags by default).
+- `excludeIptcTags`: A map of IPTC tags to exclude from parsing (defaults to
+ exclude `ObjectPreviewData`).
+
+An example parsing only the `ObjectName` tag:
+
+```js
+loadImage.parseMetaData(
+ fileOrBlob,
+ function (data) {
+ console.log('IPTC data: ', data.iptc)
+ },
+ {
+ includeIptcTags: {
+ 5: true // ObjectName
+ }
+ }
+)
+```
+
+An example excluding `ApplicationRecordVersion` and `ObjectPreviewData`:
+
+```js
+loadImage.parseMetaData(
+ fileOrBlob,
+ function (data) {
+ console.log('IPTC data: ', data.iptc)
+ },
+ {
+ excludeIptcTags: {
+ 0: true, // ApplicationRecordVersion
+ 202: true // ObjectPreviewData
+ }
+ }
+)
+```
## License
-The JavaScript Load Image script is released under the
+
+The JavaScript Load Image library is released under the
[MIT license](https://opensource.org/licenses/MIT).
## Credits
-* Image meta data handling implementation based on the help and contribution of
-Achim Stöhr.
-* Exif tags mapping based on Jacob Seidelin's
-[exif-js](https://github.com/jseidelin/exif-js).
+- Original image metadata handling implemented with the help and contribution of
+ Achim Stöhr.
+- Original Exif tags mapping based on Jacob Seidelin's
+ [exif-js](https://github.com/exif-js/exif-js) library.
+- Original IPTC parser implementation by
+ [Dave Bevan](https://github.com/bevand10).
diff --git a/bin/sync-vendor-libs.sh b/bin/sync-vendor-libs.sh
new file mode 100755
index 0000000..bff8eb3
--- /dev/null
+++ b/bin/sync-vendor-libs.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+cd "$(dirname "$0")/.."
+cp node_modules/blueimp-canvas-to-blob/js/canvas-to-blob.js js/vendor/
+cp node_modules/jquery/dist/jquery.js js/vendor/
+cp node_modules/promise-polyfill/dist/polyfill.js js/vendor/promise-polyfill.js
+cp node_modules/chai/chai.js test/vendor/
+cp node_modules/mocha/mocha.js test/vendor/
+cp node_modules/mocha/mocha.css test/vendor/
diff --git a/css/demo.css b/css/demo.css
index 121bd16..97e4ec5 100644
--- a/css/demo.css
+++ b/css/demo.css
@@ -10,27 +10,25 @@
*/
body {
- max-width: 750px;
+ max-width: 990px;
margin: 0 auto;
padding: 1em;
- font-family: 'Lucida Grande', 'Lucida Sans Unicode', Arial, sans-serif;
- font-size: 1em;
- line-height: 1.4em;
- background: #222;
- color: #fff;
+ font-family: system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue',
+ Arial, sans-serif;
-webkit-text-size-adjust: 100%;
- -ms-text-size-adjust: 100%;
+ line-height: 1.4;
+ background: #212121;
+ color: #dedede;
}
a {
- color: orange;
+ color: #61afef;
text-decoration: none;
}
-img {
- border: 0;
- vertical-align: middle;
+a:visited {
+ color: #56b6c2;
}
-h1 {
- line-height: 1em;
+a:hover {
+ color: #98c379;
}
table {
width: 100%;
@@ -38,37 +36,202 @@ table {
table-layout: fixed;
border-collapse: collapse;
}
-tr {
- background: #fff;
- color: #222;
+figure {
+ margin: 0;
+ padding: 0.75em;
+ border-radius: 5px;
+ display: inline-block;
+}
+table,
+figure {
+ margin-bottom: 1.25em;
+}
+tr,
+figure {
+ background: #363636;
}
tr:nth-child(odd) {
- background: #eee;
- color: #222;
+ background: #414141;
}
-td {
- padding: 10px;
+td,
+th {
+ padding: 0.5em 0.75em;
+ text-align: left;
}
-.result,
-.thumbnail {
- padding: 20px;
- background: #fff;
- color: #222;
- text-align: center;
+img,
+canvas {
+ max-width: 100%;
+ border: 0;
+ vertical-align: middle;
}
-.jcrop-holder {
- margin: 0 auto;
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ margin-top: 1.5em;
+ margin-bottom: 0.5em;
+}
+h1 {
+ margin-top: 0.5em;
+}
+label {
+ display: inline-block;
+ margin-bottom: 0.25em;
+}
+button,
+select,
+input,
+textarea {
+ -webkit-appearance: none;
+ box-sizing: border-box;
+ margin: 0;
+ padding: 0.5em 0.75em;
+ font-family: inherit;
+ font-size: 100%;
+ line-height: 1.4;
+ background: #414141;
+ color: #dedede;
+ border: 1px solid #363636;
+ border-radius: 5px;
+ box-shadow: 0 0 4px rgba(0, 0, 0, 0.07);
+}
+input,
+textarea {
+ width: 100%;
+ box-shadow: inset 0 0 4px rgba(0, 0, 0, 0.07);
+}
+textarea {
+ display: block;
+ overflow: auto;
+}
+button {
+ background: #3c76a7;
+ background: linear-gradient(180deg, #3c76a7, #225c8d);
+ border-color: #225c8d;
+ color: #fff;
+}
+button[type='submit'] {
+ background: #6fa349;
+ background: linear-gradient(180deg, #6fa349, #568a30);
+ border-color: #568a30;
+}
+button[type='reset'] {
+ background: #d79435;
+ background: linear-gradient(180deg, #d79435, #be7b1c);
+ border-color: #be7b1c;
+}
+select {
+ display: block;
+ padding-right: 2.25em;
+ background: #3c76a7;
+ background: url('data:image/svg+xml;charset=utf8,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 4 5"%3E%3Cpath fill="%23fff" d="M2 0L0 2h4zm0 5L0 3h4z"/%3E%3C/svg%3E')
+ no-repeat right 0.75em center/0.75em,
+ linear-gradient(180deg, #3c76a7, #225c8d);
+ border-color: #225c8d;
+ color: #fff;
+}
+button:active,
+select:active {
+ box-shadow: inset 0 0 8px rgba(0, 0, 0, 0.5);
+}
+select::-ms-expand {
+ display: none;
+}
+option {
+ color: #212121;
+}
+input[type='checkbox'] {
+ -webkit-appearance: checkbox;
+ width: auto;
+ padding: initial;
+ box-shadow: none;
+}
+input[type='file'] {
+ max-width: 100%;
+ padding: 0;
+ background: none;
+ border: 0;
+ border-radius: 0;
+ box-shadow: none;
+}
+
+input[type='file']::-webkit-file-upload-button {
+ -webkit-appearance: none;
+ box-sizing: border-box;
+ padding: 0.5em 0.75em;
+ font-family: inherit;
+ font-size: 100%;
+ line-height: 1.4;
+ background: linear-gradient(180deg, #3c76a7, #225c8d);
+ border: 1px solid #225c8d;
+ color: #fff;
+ border-radius: 5px;
+ box-shadow: 0 0 4px rgba(0, 0, 0, 0.07);
+}
+input[type='file']::-webkit-file-upload-button:active {
+ box-shadow: inset 0 0 8px rgba(0, 0, 0, 0.5);
+}
+input[type='file']::-ms-browse {
+ box-sizing: border-box;
+ padding: 0.5em 0.75em;
+ font-family: inherit;
+ font-size: 100%;
+ line-height: 1.4;
+ background: linear-gradient(180deg, #3c76a7, #225c8d);
+ border: 1px solid #225c8d;
+ color: #fff;
+ border-radius: 5px;
+ box-shadow: 0 0 4px rgba(0, 0, 0, 0.07);
+}
+input[type='file']::-ms-browse:active {
+ box-shadow: inset 0 0 8px rgba(0, 0, 0, 0.5);
+}
+
+@media (prefers-color-scheme: light) {
+ body {
+ background: #ececec;
+ color: #212121;
+ }
+ a {
+ color: #225c8d;
+ }
+ a:visited {
+ color: #378f9a;
+ }
+ a:hover {
+ color: #6fa349;
+ }
+ figure,
+ tr {
+ background: #fff;
+ color: #212121;
+ }
+ tr:nth-child(odd) {
+ background: #f6f6f6;
+ }
+ input,
+ textarea {
+ background: #fff;
+ border-color: #d1d1d1;
+ color: #212121;
+ }
+}
+
+#result {
+ display: block;
}
-@media (min-width: 481px) {
- .navigation {
+@media (min-width: 540px) {
+ #navigation {
list-style: none;
padding: 0;
}
- .navigation li {
+ #navigation li {
display: inline-block;
}
- .navigation li:not(:first-child):before {
- content: '| ';
+ #navigation li:not(:first-child)::before {
+ content: ' | ';
}
}
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..6bba835
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,15 @@
+version: '3.7'
+services:
+ nginx:
+ image: nginx:alpine
+ ports:
+ - 127.0.0.1:80:80
+ volumes:
+ - .:/usr/share/nginx/html:ro
+ mocha:
+ image: blueimp/mocha-chrome
+ command: http://nginx/test
+ environment:
+ - WAIT_FOR_HOSTS=nginx:80
+ depends_on:
+ - nginx
diff --git a/index.html b/index.html
index 1208134..05d5ac0 100644
--- a/index.html
+++ b/index.html
@@ -1,71 +1,158 @@
-
+
-
-
-
-JavaScript Load Image
-
-
-
-
-
-
-
-JavaScript Load Image Demo
-JavaScript Load Image is a library to load images provided as File or Blob objects or via URL.
-It returns an optionally scaled and/or cropped HTML img or canvas element.
-It also provides a method to parse image meta data to extract Exif tags and thumbnails and to restore the complete image header after resizing.
-
-
-Select an image file
-
-Or drag & drop an image file onto this webpage.
-
-Result
-
- Edit
- Crop
-
-
-
This demo works only in browsers with support for the URL or FileReader API.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+ JavaScript Load Image
+
+
+
+
+
+
+
+
+ JavaScript Load Image Demo
+
+ JavaScript Load Image
+ is a library to load images provided as
+ File
+ or
+ Blob
+ objects or via URL.
+ It returns an optionally scaled ,
+ cropped or rotated HTML
+ img
+ or
+ canvas
+ element.
+
+
+ It also provides methods to parse image metadata to extract
+ IPTC and
+ Exif tags as well as
+ embedded thumbnail images, to overwrite the Exif Orientation value and to
+ restore the complete image header after resizing.
+
+
+ File input
+
+ Select an image file:
+
+
+
+ Or enter an image URL into the following field:
+
+
+ Or drag & drop an image file onto this webpage.
+ Options
+
+ Orientation:
+
+ undefined: Browser default
+ true: Automatic
+ 1: Original
+ 2: Horizontal flip
+ 3: Rotate 180° CCW
+ 4: Vertical flip
+ 5: Vertical flip + Rotate 90° CW
+ 6: Rotate 90° CW
+ 7: Horizontal flip + Rotate 90° CW
+ 8: Rotate 90° CCW
+
+
+
+
+ Image smoothing
+
+ Result
+
+ Edit
+ Crop
+ Cancel
+
+
+
+ Loading images from File objects requires support for the
+ URL
+ or
+ FileReader
+ API.
+
+
+
+
Image metadata
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/js/demo/demo.js b/js/demo/demo.js
index 0110501..a95cf55 100644
--- a/js/demo/demo.js
+++ b/js/demo/demo.js
@@ -9,165 +9,309 @@
* https://opensource.org/licenses/MIT
*/
-/* global loadImage, HTMLCanvasElement, $ */
+/* global loadImage, $ */
$(function () {
'use strict'
- var result = $('#result')
- var exifNode = $('#exif')
+ var resultNode = $('#result')
+ var metaNode = $('#meta')
var thumbNode = $('#thumbnail')
var actionsNode = $('#actions')
- var currentFile
+ var orientationNode = $('#orientation')
+ var imageSmoothingNode = $('#image-smoothing')
+ var fileInputNode = $('#file-input')
+ var urlNode = $('#url')
+ var editNode = $('#edit')
+ var cropNode = $('#crop')
+ var cancelNode = $('#cancel')
var coordinates
+ var jcropAPI
- function displayExifData (exif) {
- var thumbnail = exif.get('Thumbnail')
- var tags = exif.getAll()
- var table = exifNode.find('table').empty()
+ /**
+ * Displays tag data
+ *
+ * @param {*} node jQuery node
+ * @param {object} tags Tags map
+ * @param {string} title Tags title
+ */
+ function displayTagData(node, tags, title) {
+ var table = $('')
var row = $(' ')
var cell = $(' ')
+ var headerCell = $(' ')
var prop
- if (thumbnail) {
- thumbNode.empty()
- loadImage(thumbnail, function (img) {
- thumbNode.append(img).show()
- }, {orientation: exif.get('Orientation')})
- }
+ table.append(row.clone().append(headerCell.clone().text(title)))
for (prop in tags) {
- if (tags.hasOwnProperty(prop)) {
+ if (Object.prototype.hasOwnProperty.call(tags, prop)) {
+ if (typeof tags[prop] === 'object') {
+ displayTagData(node, tags[prop], prop)
+ continue
+ }
table.append(
- row.clone()
+ row
+ .clone()
.append(cell.clone().text(prop))
.append(cell.clone().text(tags[prop]))
)
}
}
- exifNode.show()
+ node.append(table).show()
}
- function updateResults (img, data) {
- var fileName = currentFile.name
- var href = img.src
- var dataURLStart
- var content
- if (!(img.src || img instanceof HTMLCanvasElement)) {
- content = $('Loading image file failed ')
- } else {
- if (!href) {
- href = img.toDataURL(currentFile.type + 'REMOVEME')
- // Check if file type is supported for the dataURL export:
- dataURLStart = 'data:' + currentFile.type
- if (href.slice(0, dataURLStart.length) !== dataURLStart) {
- fileName = fileName.replace(/\.\w+$/, '.png')
- }
- }
- content = $('').append(img)
- .attr('download', fileName)
- .attr('href', href)
+ /**
+ * Displays the thumbnal image
+ *
+ * @param {*} node jQuery node
+ * @param {string} thumbnail Thumbnail URL
+ * @param {object} [options] Options object
+ */
+ function displayThumbnailImage(node, thumbnail, options) {
+ if (thumbnail) {
+ var link = $(' ')
+ .attr('href', loadImage.createObjectURL(thumbnail))
+ .attr('download', 'thumbnail.jpg')
+ .appendTo(node)
+ loadImage(
+ thumbnail,
+ function (img) {
+ link.append(img)
+ node.show()
+ },
+ options
+ )
}
- result.children().replaceWith(content)
- if (img.getContext) {
- actionsNode.show()
+ }
+
+ /**
+ * Displays metadata
+ *
+ * @param {object} [data] Metadata object
+ */
+ function displayMetaData(data) {
+ if (!data) return
+ metaNode.data(data)
+ var exif = data.exif
+ var iptc = data.iptc
+ if (exif) {
+ var thumbnail = exif.get('Thumbnail')
+ if (thumbnail) {
+ displayThumbnailImage(thumbNode, thumbnail.get('Blob'), {
+ orientation: exif.get('Orientation')
+ })
+ }
+ displayTagData(metaNode, exif.getAll(), 'TIFF')
}
- if (data && data.exif) {
- displayExifData(data.exif)
+ if (iptc) {
+ displayTagData(metaNode, iptc.getAll(), 'IPTC')
}
}
- function displayImage (file, options) {
- currentFile = file
- if (!loadImage(
- file,
- updateResults,
- options
- )) {
- result.children().replaceWith(
- $('' +
- 'Your browser does not support the URL or FileReader API.' +
- ' ')
- )
+ /**
+ * Removes meta data from the page
+ */
+ function removeMetaData() {
+ metaNode.hide().removeData().find('table').remove()
+ thumbNode.hide().empty()
+ }
+
+ /**
+ * Updates the results view
+ *
+ * @param {*} img Image or canvas element
+ * @param {object} [data] Metadata object
+ * @param {boolean} [keepMetaData] Keep meta data if true
+ */
+ function updateResults(img, data, keepMetaData) {
+ var isCanvas = window.HTMLCanvasElement && img instanceof HTMLCanvasElement
+ if (!keepMetaData) {
+ removeMetaData()
+ if (data) {
+ displayMetaData(data)
+ }
+ if (isCanvas) {
+ actionsNode.show()
+ } else {
+ actionsNode.hide()
+ }
+ }
+ if (!(isCanvas || img.src)) {
+ resultNode
+ .children()
+ .replaceWith($('Loading image file failed '))
+ return
+ }
+ var content = $(' ').append(img)
+ resultNode.children().replaceWith(content)
+ if (data.imageHead) {
+ if (data.exif) {
+ // Reset Exif Orientation data:
+ loadImage.writeExifData(data.imageHead, data, 'Orientation', 1)
+ }
+ img.toBlob(function (blob) {
+ if (!blob) return
+ loadImage.replaceHead(blob, data.imageHead, function (newBlob) {
+ content
+ .attr('href', loadImage.createObjectURL(newBlob))
+ .attr('download', 'image.jpg')
+ })
+ }, 'image/jpeg')
}
}
- function dropChangeHandler (e) {
- e.preventDefault()
- e = e.originalEvent
- var target = e.dataTransfer || e.target
- var file = target && target.files && target.files[0]
+ /**
+ * Displays the image
+ *
+ * @param {File|Blob|string} file File or Blob object or image URL
+ */
+ function displayImage(file) {
var options = {
- maxWidth: result.width(),
+ maxWidth: resultNode.width(),
canvas: true,
pixelRatio: window.devicePixelRatio,
downsamplingRatio: 0.5,
- orientation: true
+ orientation: Number(orientationNode.val()) || true,
+ imageSmoothingEnabled: imageSmoothingNode.is(':checked'),
+ meta: true
}
+ if (!loadImage(file, updateResults, options)) {
+ removeMetaData()
+ resultNode
+ .children()
+ .replaceWith(
+ $(
+ '' +
+ 'Your browser does not support the URL or FileReader API.' +
+ ' '
+ )
+ )
+ }
+ }
+
+ /**
+ * Handles drop and file selection change events
+ *
+ * @param {event} event Drop or file selection change event
+ */
+ function fileChangeHandler(event) {
+ event.preventDefault()
+ var originalEvent = event.originalEvent
+ var target = originalEvent.dataTransfer || originalEvent.target
+ var file = target && target.files && target.files[0]
if (!file) {
return
}
- exifNode.hide()
- thumbNode.hide()
- displayImage(file, options)
+ displayImage(file)
}
- // Hide URL/FileReader API requirement message in capable browsers:
- if (window.createObjectURL || window.URL || window.webkitURL ||
- window.FileReader) {
- result.children().hide()
+ /**
+ * Handles URL change events
+ */
+ function urlChangeHandler() {
+ var url = $(this).val()
+ if (url) displayImage(url)
+ }
+
+ // Show the URL/FileReader API requirement message if not supported:
+ if (
+ window.createObjectURL ||
+ window.URL ||
+ window.webkitURL ||
+ window.FileReader
+ ) {
+ resultNode.children().hide()
+ } else {
+ resultNode.children().show()
}
$(document)
.on('dragover', function (e) {
e.preventDefault()
- e = e.originalEvent
- e.dataTransfer.dropEffect = 'copy'
+ if (event.dataTransfer) event.dataTransfer.dropEffect = 'copy'
})
- .on('drop', dropChangeHandler)
-
- $('#file-input')
- .on('change', dropChangeHandler)
-
- $('#edit')
- .on('click', function (event) {
- event.preventDefault()
- var imgNode = result.find('img, canvas')
- var img = imgNode[0]
- var pixelRatio = window.devicePixelRatio || 1
- imgNode.Jcrop({
- setSelect: [
- 40,
- 40,
- (img.width / pixelRatio) - 40,
- (img.height / pixelRatio) - 40
- ],
- onSelect: function (coords) {
- coordinates = coords
+ .on('drop', fileChangeHandler)
+
+ fileInputNode.on('change', fileChangeHandler)
+
+ urlNode.on('change paste input', urlChangeHandler)
+
+ orientationNode.on('change', function () {
+ var img = resultNode.find('img, canvas')[0]
+ if (img) {
+ updateResults(
+ loadImage.scale(img, {
+ maxWidth: resultNode.width() * (window.devicePixelRatio || 1),
+ pixelRatio: window.devicePixelRatio,
+ orientation: Number(orientationNode.val()) || true,
+ imageSmoothingEnabled: imageSmoothingNode.is(':checked')
+ }),
+ metaNode.data(),
+ true
+ )
+ }
+ })
+
+ editNode.on('click', function (event) {
+ event.preventDefault()
+ var imgNode = resultNode.find('img, canvas')
+ var img = imgNode[0]
+ var pixelRatio = window.devicePixelRatio || 1
+ var margin = img.width / pixelRatio >= 140 ? 40 : 0
+ imgNode
+ // eslint-disable-next-line new-cap
+ .Jcrop(
+ {
+ setSelect: [
+ margin,
+ margin,
+ img.width / pixelRatio - margin,
+ img.height / pixelRatio - margin
+ ],
+ onSelect: function (coords) {
+ coordinates = coords
+ },
+ onRelease: function () {
+ coordinates = null
+ }
},
- onRelease: function () {
- coordinates = null
+ function () {
+ jcropAPI = this
}
- }).parent().on('click', function (event) {
+ )
+ .parent()
+ .on('click', function (event) {
event.preventDefault()
})
- })
+ })
- $('#crop')
- .on('click', function (event) {
- event.preventDefault()
- var img = result.find('img, canvas')[0]
- var pixelRatio = window.devicePixelRatio || 1
- if (img && coordinates) {
- updateResults(loadImage.scale(img, {
+ cropNode.on('click', function (event) {
+ event.preventDefault()
+ var img = resultNode.find('img, canvas')[0]
+ var pixelRatio = window.devicePixelRatio || 1
+ if (img && coordinates) {
+ updateResults(
+ loadImage.scale(img, {
left: coordinates.x * pixelRatio,
top: coordinates.y * pixelRatio,
sourceWidth: coordinates.w * pixelRatio,
sourceHeight: coordinates.h * pixelRatio,
- minWidth: result.width(),
- maxWidth: result.width(),
+ maxWidth: resultNode.width() * pixelRatio,
+ contain: true,
pixelRatio: pixelRatio,
- downsamplingRatio: 0.5
- }))
- coordinates = null
- }
- })
+ imageSmoothingEnabled: imageSmoothingNode.is(':checked')
+ }),
+ metaNode.data(),
+ true
+ )
+ coordinates = null
+ }
+ })
+
+ cancelNode.on('click', function (event) {
+ event.preventDefault()
+ if (jcropAPI) {
+ jcropAPI.release()
+ jcropAPI.disable()
+ }
+ })
})
diff --git a/js/index.js b/js/index.js
index 6cb50d0..38e1794 100644
--- a/js/index.js
+++ b/js/index.js
@@ -1,3 +1,5 @@
+/* global module, require */
+
module.exports = require('./load-image')
require('./load-image-scale')
@@ -5,4 +7,6 @@ require('./load-image-meta')
require('./load-image-fetch')
require('./load-image-exif')
require('./load-image-exif-map')
+require('./load-image-iptc')
+require('./load-image-iptc-map')
require('./load-image-orientation')
diff --git a/js/load-image-exif-map.js b/js/load-image-exif-map.js
index 27e1556..d9ce3a8 100644
--- a/js/load-image-exif-map.js
+++ b/js/load-image-exif-map.js
@@ -12,7 +12,7 @@
* https://opensource.org/licenses/MIT
*/
-/* global define */
+/* global define, module, require */
;(function (factory) {
'use strict'
@@ -28,15 +28,14 @@
})(function (loadImage) {
'use strict'
- loadImage.ExifMap.prototype.tags = {
+ var ExifMapProto = loadImage.ExifMap.prototype
+
+ ExifMapProto.tags = {
// =================
// TIFF tags (IFD0):
// =================
0x0100: 'ImageWidth',
0x0101: 'ImageHeight',
- 0x8769: 'ExifIFDPointer',
- 0x8825: 'GPSInfoIFDPointer',
- 0xa005: 'InteroperabilityIFDPointer',
0x0102: 'BitsPerSample',
0x0103: 'Compression',
0x0106: 'PhotometricInterpretation',
@@ -65,116 +64,126 @@
0x0131: 'Software',
0x013b: 'Artist',
0x8298: 'Copyright',
- // ==================
- // Exif Sub IFD tags:
- // ==================
- 0x9000: 'ExifVersion', // EXIF version
- 0xa000: 'FlashpixVersion', // Flashpix format version
- 0xa001: 'ColorSpace', // Color space information tag
- 0xa002: 'PixelXDimension', // Valid width of meaningful image
- 0xa003: 'PixelYDimension', // Valid height of meaningful image
- 0xa500: 'Gamma',
- 0x9101: 'ComponentsConfiguration', // Information about channels
- 0x9102: 'CompressedBitsPerPixel', // Compressed bits per pixel
- 0x927c: 'MakerNote', // Any desired information written by the manufacturer
- 0x9286: 'UserComment', // Comments by user
- 0xa004: 'RelatedSoundFile', // Name of related sound file
- 0x9003: 'DateTimeOriginal', // Date and time when the original image was generated
- 0x9004: 'DateTimeDigitized', // Date and time when the image was stored digitally
- 0x9290: 'SubSecTime', // Fractions of seconds for DateTime
- 0x9291: 'SubSecTimeOriginal', // Fractions of seconds for DateTimeOriginal
- 0x9292: 'SubSecTimeDigitized', // Fractions of seconds for DateTimeDigitized
- 0x829a: 'ExposureTime', // Exposure time (in seconds)
- 0x829d: 'FNumber',
- 0x8822: 'ExposureProgram', // Exposure program
- 0x8824: 'SpectralSensitivity', // Spectral sensitivity
- 0x8827: 'PhotographicSensitivity', // EXIF 2.3, ISOSpeedRatings in EXIF 2.2
- 0x8828: 'OECF', // Optoelectric conversion factor
- 0x8830: 'SensitivityType',
- 0x8831: 'StandardOutputSensitivity',
- 0x8832: 'RecommendedExposureIndex',
- 0x8833: 'ISOSpeed',
- 0x8834: 'ISOSpeedLatitudeyyy',
- 0x8835: 'ISOSpeedLatitudezzz',
- 0x9201: 'ShutterSpeedValue', // Shutter speed
- 0x9202: 'ApertureValue', // Lens aperture
- 0x9203: 'BrightnessValue', // Value of brightness
- 0x9204: 'ExposureBias', // Exposure bias
- 0x9205: 'MaxApertureValue', // Smallest F number of lens
- 0x9206: 'SubjectDistance', // Distance to subject in meters
- 0x9207: 'MeteringMode', // Metering mode
- 0x9208: 'LightSource', // Kind of light source
- 0x9209: 'Flash', // Flash status
- 0x9214: 'SubjectArea', // Location and area of main subject
- 0x920a: 'FocalLength', // Focal length of the lens in mm
- 0xa20b: 'FlashEnergy', // Strobe energy in BCPS
- 0xa20c: 'SpatialFrequencyResponse',
- 0xa20e: 'FocalPlaneXResolution', // Number of pixels in width direction per FPRUnit
- 0xa20f: 'FocalPlaneYResolution', // Number of pixels in height direction per FPRUnit
- 0xa210: 'FocalPlaneResolutionUnit', // Unit for measuring the focal plane resolution
- 0xa214: 'SubjectLocation', // Location of subject in image
- 0xa215: 'ExposureIndex', // Exposure index selected on camera
- 0xa217: 'SensingMethod', // Image sensor type
- 0xa300: 'FileSource', // Image source (3 == DSC)
- 0xa301: 'SceneType', // Scene type (1 == directly photographed)
- 0xa302: 'CFAPattern', // Color filter array geometric pattern
- 0xa401: 'CustomRendered', // Special processing
- 0xa402: 'ExposureMode', // Exposure mode
- 0xa403: 'WhiteBalance', // 1 = auto white balance, 2 = manual
- 0xa404: 'DigitalZoomRatio', // Digital zoom ratio
- 0xa405: 'FocalLengthIn35mmFilm',
- 0xa406: 'SceneCaptureType', // Type of scene
- 0xa407: 'GainControl', // Degree of overall image gain adjustment
- 0xa408: 'Contrast', // Direction of contrast processing applied by camera
- 0xa409: 'Saturation', // Direction of saturation processing applied by camera
- 0xa40a: 'Sharpness', // Direction of sharpness processing applied by camera
- 0xa40b: 'DeviceSettingDescription',
- 0xa40c: 'SubjectDistanceRange', // Distance to subject
- 0xa420: 'ImageUniqueID', // Identifier assigned uniquely to each image
- 0xa430: 'CameraOwnerName',
- 0xa431: 'BodySerialNumber',
- 0xa432: 'LensSpecification',
- 0xa433: 'LensMake',
- 0xa434: 'LensModel',
- 0xa435: 'LensSerialNumber',
- // ==============
- // GPS Info tags:
- // ==============
- 0x0000: 'GPSVersionID',
- 0x0001: 'GPSLatitudeRef',
- 0x0002: 'GPSLatitude',
- 0x0003: 'GPSLongitudeRef',
- 0x0004: 'GPSLongitude',
- 0x0005: 'GPSAltitudeRef',
- 0x0006: 'GPSAltitude',
- 0x0007: 'GPSTimeStamp',
- 0x0008: 'GPSSatellites',
- 0x0009: 'GPSStatus',
- 0x000a: 'GPSMeasureMode',
- 0x000b: 'GPSDOP',
- 0x000c: 'GPSSpeedRef',
- 0x000d: 'GPSSpeed',
- 0x000e: 'GPSTrackRef',
- 0x000f: 'GPSTrack',
- 0x0010: 'GPSImgDirectionRef',
- 0x0011: 'GPSImgDirection',
- 0x0012: 'GPSMapDatum',
- 0x0013: 'GPSDestLatitudeRef',
- 0x0014: 'GPSDestLatitude',
- 0x0015: 'GPSDestLongitudeRef',
- 0x0016: 'GPSDestLongitude',
- 0x0017: 'GPSDestBearingRef',
- 0x0018: 'GPSDestBearing',
- 0x0019: 'GPSDestDistanceRef',
- 0x001a: 'GPSDestDistance',
- 0x001b: 'GPSProcessingMethod',
- 0x001c: 'GPSAreaInformation',
- 0x001d: 'GPSDateStamp',
- 0x001e: 'GPSDifferential',
- 0x001f: 'GPSHPositioningError'
+ 0x8769: {
+ // ExifIFDPointer
+ 0x9000: 'ExifVersion', // EXIF version
+ 0xa000: 'FlashpixVersion', // Flashpix format version
+ 0xa001: 'ColorSpace', // Color space information tag
+ 0xa002: 'PixelXDimension', // Valid width of meaningful image
+ 0xa003: 'PixelYDimension', // Valid height of meaningful image
+ 0xa500: 'Gamma',
+ 0x9101: 'ComponentsConfiguration', // Information about channels
+ 0x9102: 'CompressedBitsPerPixel', // Compressed bits per pixel
+ 0x927c: 'MakerNote', // Any desired information written by the manufacturer
+ 0x9286: 'UserComment', // Comments by user
+ 0xa004: 'RelatedSoundFile', // Name of related sound file
+ 0x9003: 'DateTimeOriginal', // Date and time when the original image was generated
+ 0x9004: 'DateTimeDigitized', // Date and time when the image was stored digitally
+ 0x9010: 'OffsetTime', // Time zone when the image file was last changed
+ 0x9011: 'OffsetTimeOriginal', // Time zone when the image was stored digitally
+ 0x9012: 'OffsetTimeDigitized', // Time zone when the image was stored digitally
+ 0x9290: 'SubSecTime', // Fractions of seconds for DateTime
+ 0x9291: 'SubSecTimeOriginal', // Fractions of seconds for DateTimeOriginal
+ 0x9292: 'SubSecTimeDigitized', // Fractions of seconds for DateTimeDigitized
+ 0x829a: 'ExposureTime', // Exposure time (in seconds)
+ 0x829d: 'FNumber',
+ 0x8822: 'ExposureProgram', // Exposure program
+ 0x8824: 'SpectralSensitivity', // Spectral sensitivity
+ 0x8827: 'PhotographicSensitivity', // EXIF 2.3, ISOSpeedRatings in EXIF 2.2
+ 0x8828: 'OECF', // Optoelectric conversion factor
+ 0x8830: 'SensitivityType',
+ 0x8831: 'StandardOutputSensitivity',
+ 0x8832: 'RecommendedExposureIndex',
+ 0x8833: 'ISOSpeed',
+ 0x8834: 'ISOSpeedLatitudeyyy',
+ 0x8835: 'ISOSpeedLatitudezzz',
+ 0x9201: 'ShutterSpeedValue', // Shutter speed
+ 0x9202: 'ApertureValue', // Lens aperture
+ 0x9203: 'BrightnessValue', // Value of brightness
+ 0x9204: 'ExposureBias', // Exposure bias
+ 0x9205: 'MaxApertureValue', // Smallest F number of lens
+ 0x9206: 'SubjectDistance', // Distance to subject in meters
+ 0x9207: 'MeteringMode', // Metering mode
+ 0x9208: 'LightSource', // Kind of light source
+ 0x9209: 'Flash', // Flash status
+ 0x9214: 'SubjectArea', // Location and area of main subject
+ 0x920a: 'FocalLength', // Focal length of the lens in mm
+ 0xa20b: 'FlashEnergy', // Strobe energy in BCPS
+ 0xa20c: 'SpatialFrequencyResponse',
+ 0xa20e: 'FocalPlaneXResolution', // Number of pixels in width direction per FPRUnit
+ 0xa20f: 'FocalPlaneYResolution', // Number of pixels in height direction per FPRUnit
+ 0xa210: 'FocalPlaneResolutionUnit', // Unit for measuring the focal plane resolution
+ 0xa214: 'SubjectLocation', // Location of subject in image
+ 0xa215: 'ExposureIndex', // Exposure index selected on camera
+ 0xa217: 'SensingMethod', // Image sensor type
+ 0xa300: 'FileSource', // Image source (3 == DSC)
+ 0xa301: 'SceneType', // Scene type (1 == directly photographed)
+ 0xa302: 'CFAPattern', // Color filter array geometric pattern
+ 0xa401: 'CustomRendered', // Special processing
+ 0xa402: 'ExposureMode', // Exposure mode
+ 0xa403: 'WhiteBalance', // 1 = auto white balance, 2 = manual
+ 0xa404: 'DigitalZoomRatio', // Digital zoom ratio
+ 0xa405: 'FocalLengthIn35mmFilm',
+ 0xa406: 'SceneCaptureType', // Type of scene
+ 0xa407: 'GainControl', // Degree of overall image gain adjustment
+ 0xa408: 'Contrast', // Direction of contrast processing applied by camera
+ 0xa409: 'Saturation', // Direction of saturation processing applied by camera
+ 0xa40a: 'Sharpness', // Direction of sharpness processing applied by camera
+ 0xa40b: 'DeviceSettingDescription',
+ 0xa40c: 'SubjectDistanceRange', // Distance to subject
+ 0xa420: 'ImageUniqueID', // Identifier assigned uniquely to each image
+ 0xa430: 'CameraOwnerName',
+ 0xa431: 'BodySerialNumber',
+ 0xa432: 'LensSpecification',
+ 0xa433: 'LensMake',
+ 0xa434: 'LensModel',
+ 0xa435: 'LensSerialNumber'
+ },
+ 0x8825: {
+ // GPSInfoIFDPointer
+ 0x0000: 'GPSVersionID',
+ 0x0001: 'GPSLatitudeRef',
+ 0x0002: 'GPSLatitude',
+ 0x0003: 'GPSLongitudeRef',
+ 0x0004: 'GPSLongitude',
+ 0x0005: 'GPSAltitudeRef',
+ 0x0006: 'GPSAltitude',
+ 0x0007: 'GPSTimeStamp',
+ 0x0008: 'GPSSatellites',
+ 0x0009: 'GPSStatus',
+ 0x000a: 'GPSMeasureMode',
+ 0x000b: 'GPSDOP',
+ 0x000c: 'GPSSpeedRef',
+ 0x000d: 'GPSSpeed',
+ 0x000e: 'GPSTrackRef',
+ 0x000f: 'GPSTrack',
+ 0x0010: 'GPSImgDirectionRef',
+ 0x0011: 'GPSImgDirection',
+ 0x0012: 'GPSMapDatum',
+ 0x0013: 'GPSDestLatitudeRef',
+ 0x0014: 'GPSDestLatitude',
+ 0x0015: 'GPSDestLongitudeRef',
+ 0x0016: 'GPSDestLongitude',
+ 0x0017: 'GPSDestBearingRef',
+ 0x0018: 'GPSDestBearing',
+ 0x0019: 'GPSDestDistanceRef',
+ 0x001a: 'GPSDestDistance',
+ 0x001b: 'GPSProcessingMethod',
+ 0x001c: 'GPSAreaInformation',
+ 0x001d: 'GPSDateStamp',
+ 0x001e: 'GPSDifferential',
+ 0x001f: 'GPSHPositioningError'
+ },
+ 0xa005: {
+ // InteroperabilityIFDPointer
+ 0x0001: 'InteroperabilityIndex'
+ }
}
- loadImage.ExifMap.prototype.stringValues = {
+ // IFD1 directory can contain any IFD0 tags:
+ ExifMapProto.tags.ifd1 = ExifMapProto.tags
+
+ ExifMapProto.stringValues = {
ExposureProgram: {
0: 'Undefined',
1: 'Manual',
@@ -237,11 +246,15 @@
0x0045: 'Flash fired, red-eye reduction mode, return light not detected',
0x0047: 'Flash fired, red-eye reduction mode, return light detected',
0x0049: 'Flash fired, compulsory flash mode, red-eye reduction mode',
- 0x004d: 'Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected',
- 0x004f: 'Flash fired, compulsory flash mode, red-eye reduction mode, return light detected',
+ 0x004d:
+ 'Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected',
+ 0x004f:
+ 'Flash fired, compulsory flash mode, red-eye reduction mode, return light detected',
0x0059: 'Flash fired, auto mode, red-eye reduction mode',
- 0x005d: 'Flash fired, auto mode, return light not detected, red-eye reduction mode',
- 0x005f: 'Flash fired, auto mode, return light detected, red-eye reduction mode'
+ 0x005d:
+ 'Flash fired, auto mode, return light not detected, red-eye reduction mode',
+ 0x005f:
+ 'Flash fired, auto mode, return light detected, red-eye reduction mode'
},
SensingMethod: {
1: 'Undefined',
@@ -310,20 +323,20 @@
6: 'B'
},
Orientation: {
- 1: 'top-left',
- 2: 'top-right',
- 3: 'bottom-right',
- 4: 'bottom-left',
- 5: 'left-top',
- 6: 'right-top',
- 7: 'right-bottom',
- 8: 'left-bottom'
+ 1: 'Original',
+ 2: 'Horizontal flip',
+ 3: 'Rotate 180° CCW',
+ 4: 'Vertical flip',
+ 5: 'Vertical flip + Rotate 90° CW',
+ 6: 'Rotate 90° CW',
+ 7: 'Horizontal flip + Rotate 90° CW',
+ 8: 'Rotate 90° CCW'
}
}
- loadImage.ExifMap.prototype.getText = function (id) {
- var value = this.get(id)
- switch (id) {
+ ExifMapProto.getText = function (name) {
+ var value = this.get(name)
+ switch (name) {
case 'LightSource':
case 'Flash':
case 'MeteringMode':
@@ -340,7 +353,7 @@
case 'SubjectDistanceRange':
case 'FileSource':
case 'Orientation':
- return this.stringValues[id][value]
+ return this.stringValues[name][value]
case 'ExifVersion':
case 'FlashpixVersion':
if (!value) return
@@ -348,10 +361,10 @@
case 'ComponentsConfiguration':
if (!value) return
return (
- this.stringValues[id][value[0]] +
- this.stringValues[id][value[1]] +
- this.stringValues[id][value[2]] +
- this.stringValues[id][value[3]]
+ this.stringValues[name][value[0]] +
+ this.stringValues[name][value[1]] +
+ this.stringValues[name][value[2]] +
+ this.stringValues[name][value[3]]
)
case 'GPSVersionID':
if (!value) return
@@ -359,30 +372,53 @@
}
return String(value)
}
- ;(function (exifMapPrototype) {
- var tags = exifMapPrototype.tags
- var map = exifMapPrototype.map
- var prop
- // Map the tag names to tags:
- for (prop in tags) {
- if (tags.hasOwnProperty(prop)) {
- map[tags[prop]] = prop
- }
- }
- })(loadImage.ExifMap.prototype)
- loadImage.ExifMap.prototype.getAll = function () {
+ ExifMapProto.getAll = function () {
var map = {}
var prop
- var id
+ var obj
+ var name
for (prop in this) {
- if (this.hasOwnProperty(prop)) {
- id = this.tags[prop]
- if (id) {
- map[id] = this.getText(id)
+ if (Object.prototype.hasOwnProperty.call(this, prop)) {
+ obj = this[prop]
+ if (obj && obj.getAll) {
+ map[this.ifds[prop].name] = obj.getAll()
+ } else {
+ name = this.tags[prop]
+ if (name) map[name] = this.getText(name)
}
}
}
return map
}
+
+ ExifMapProto.getName = function (tagCode) {
+ var name = this.tags[tagCode]
+ if (typeof name === 'object') return this.ifds[tagCode].name
+ return name
+ }
+
+ // Extend the map of tag names to tag codes:
+ ;(function () {
+ var tags = ExifMapProto.tags
+ var prop
+ var ifd
+ var subTags
+ // Map the tag names to tags:
+ for (prop in tags) {
+ if (Object.prototype.hasOwnProperty.call(tags, prop)) {
+ ifd = ExifMapProto.ifds[prop]
+ if (ifd) {
+ subTags = tags[prop]
+ for (prop in subTags) {
+ if (Object.prototype.hasOwnProperty.call(subTags, prop)) {
+ ifd.map[subTags[prop]] = Number(prop)
+ }
+ }
+ } else {
+ ExifMapProto.map[tags[prop]] = Number(prop)
+ }
+ }
+ }
+ })()
})
diff --git a/js/load-image-exif.js b/js/load-image-exif.js
index ee467a4..7428eef 100644
--- a/js/load-image-exif.js
+++ b/js/load-image-exif.js
@@ -9,7 +9,9 @@
* https://opensource.org/licenses/MIT
*/
-/* global define, Blob */
+/* global define, module, require, DataView */
+
+/* eslint-disable no-console */
;(function (factory) {
'use strict'
@@ -25,29 +27,73 @@
})(function (loadImage) {
'use strict'
- loadImage.ExifMap = function () {
- return this
+ /**
+ * Exif tag map
+ *
+ * @name ExifMap
+ * @class
+ * @param {number|string} tagCode IFD tag code
+ */
+ function ExifMap(tagCode) {
+ if (tagCode) {
+ Object.defineProperty(this, 'map', {
+ value: this.ifds[tagCode].map
+ })
+ Object.defineProperty(this, 'tags', {
+ value: (this.tags && this.tags[tagCode]) || {}
+ })
+ }
+ }
+
+ ExifMap.prototype.map = {
+ Orientation: 0x0112,
+ Thumbnail: 'ifd1',
+ Blob: 0x0201, // Alias for JPEGInterchangeFormat
+ Exif: 0x8769,
+ GPSInfo: 0x8825,
+ Interoperability: 0xa005
}
- loadImage.ExifMap.prototype.map = {
- Orientation: 0x0112
+ ExifMap.prototype.ifds = {
+ ifd1: { name: 'Thumbnail', map: ExifMap.prototype.map },
+ 0x8769: { name: 'Exif', map: {} },
+ 0x8825: { name: 'GPSInfo', map: {} },
+ 0xa005: { name: 'Interoperability', map: {} }
}
- loadImage.ExifMap.prototype.get = function (id) {
+ /**
+ * Retrieves exif tag value
+ *
+ * @param {number|string} id Exif tag code or name
+ * @returns {object} Exif tag value
+ */
+ ExifMap.prototype.get = function (id) {
return this[id] || this[this.map[id]]
}
- loadImage.getExifThumbnail = function (dataView, offset, length) {
- if (!length || offset + length > dataView.byteLength) {
+ /**
+ * Returns the Exif Thumbnail data as Blob.
+ *
+ * @param {DataView} dataView Data view interface
+ * @param {number} offset Thumbnail data offset
+ * @param {number} length Thumbnail data length
+ * @returns {undefined|Blob} Returns the Thumbnail Blob or undefined
+ */
+ function getExifThumbnail(dataView, offset, length) {
+ if (!length) return
+ if (offset + length > dataView.byteLength) {
console.log('Invalid Exif data: Invalid thumbnail data.')
return
}
- return loadImage.createObjectURL(
- new Blob([dataView.buffer.slice(offset, offset + length)])
+ return new Blob(
+ [loadImage.bufferSlice.call(dataView.buffer, offset, offset + length)],
+ {
+ type: 'image/jpeg'
+ }
)
}
- loadImage.exifTagTypes = {
+ var ExifTagTypes = {
// byte, 8-bit unsigned int:
1: {
getValue: function (dataView, dataOffset) {
@@ -106,9 +152,20 @@
}
}
// undefined, 8-bit byte, value depending on field:
- loadImage.exifTagTypes[7] = loadImage.exifTagTypes[1]
+ ExifTagTypes[7] = ExifTagTypes[1]
- loadImage.getExifValue = function (
+ /**
+ * Returns Exif tag value.
+ *
+ * @param {DataView} dataView Data view interface
+ * @param {number} tiffOffset TIFF offset
+ * @param {number} offset Tag offset
+ * @param {number} type Tag type
+ * @param {number} length Tag length
+ * @param {boolean} littleEndian Little endian encoding
+ * @returns {object} Tag value
+ */
+ function getExifValue(
dataView,
tiffOffset,
offset,
@@ -116,7 +173,7 @@
length,
littleEndian
) {
- var tagType = loadImage.exifTagTypes[type]
+ var tagType = ExifTagTypes[type]
var tagSize
var dataOffset
var values
@@ -165,32 +222,45 @@
return values
}
- loadImage.parseExifTag = function (
- dataView,
- tiffOffset,
- offset,
- littleEndian,
- data
- ) {
- var tag = dataView.getUint16(offset, littleEndian)
- data.exif[tag] = loadImage.getExifValue(
- dataView,
- tiffOffset,
- offset,
- dataView.getUint16(offset + 2, littleEndian), // tag type
- dataView.getUint32(offset + 4, littleEndian), // tag length
- littleEndian
+ /**
+ * Determines if the given tag should be included.
+ *
+ * @param {object} includeTags Map of tags to include
+ * @param {object} excludeTags Map of tags to exclude
+ * @param {number|string} tagCode Tag code to check
+ * @returns {boolean} True if the tag should be included
+ */
+ function shouldIncludeTag(includeTags, excludeTags, tagCode) {
+ return (
+ (!includeTags || includeTags[tagCode]) &&
+ (!excludeTags || excludeTags[tagCode] !== true)
)
}
- loadImage.parseExifTags = function (
+ /**
+ * Parses Exif tags.
+ *
+ * @param {DataView} dataView Data view interface
+ * @param {number} tiffOffset TIFF offset
+ * @param {number} dirOffset Directory offset
+ * @param {boolean} littleEndian Little endian encoding
+ * @param {ExifMap} tags Map to store parsed exif tags
+ * @param {ExifMap} tagOffsets Map to store parsed exif tag offsets
+ * @param {object} includeTags Map of tags to include
+ * @param {object} excludeTags Map of tags to exclude
+ * @returns {number} Next directory offset
+ */
+ function parseExifTags(
dataView,
tiffOffset,
dirOffset,
littleEndian,
- data
+ tags,
+ tagOffsets,
+ includeTags,
+ excludeTags
) {
- var tagsNumber, dirEndOffset, i
+ var tagsNumber, dirEndOffset, i, tagOffset, tagNumber, tagValue
if (dirOffset + 6 > dataView.byteLength) {
console.log('Invalid Exif data: Invalid directory offset.')
return
@@ -202,26 +272,80 @@
return
}
for (i = 0; i < tagsNumber; i += 1) {
- this.parseExifTag(
+ tagOffset = dirOffset + 2 + 12 * i
+ tagNumber = dataView.getUint16(tagOffset, littleEndian)
+ if (!shouldIncludeTag(includeTags, excludeTags, tagNumber)) continue
+ tagValue = getExifValue(
dataView,
tiffOffset,
- dirOffset + 2 + 12 * i, // tag offset
- littleEndian,
- data
+ tagOffset,
+ dataView.getUint16(tagOffset + 2, littleEndian), // tag type
+ dataView.getUint32(tagOffset + 4, littleEndian), // tag length
+ littleEndian
)
+ tags[tagNumber] = tagValue
+ if (tagOffsets) {
+ tagOffsets[tagNumber] = tagOffset
+ }
}
// Return the offset to the next directory:
return dataView.getUint32(dirEndOffset, littleEndian)
}
+ /**
+ * Parses tags in a given IFD (Image File Directory).
+ *
+ * @param {object} data Data object to store exif tags and offsets
+ * @param {number|string} tagCode IFD tag code
+ * @param {DataView} dataView Data view interface
+ * @param {number} tiffOffset TIFF offset
+ * @param {boolean} littleEndian Little endian encoding
+ * @param {object} includeTags Map of tags to include
+ * @param {object} excludeTags Map of tags to exclude
+ */
+ function parseExifIFD(
+ data,
+ tagCode,
+ dataView,
+ tiffOffset,
+ littleEndian,
+ includeTags,
+ excludeTags
+ ) {
+ var dirOffset = data.exif[tagCode]
+ if (dirOffset) {
+ data.exif[tagCode] = new ExifMap(tagCode)
+ if (data.exifOffsets) {
+ data.exifOffsets[tagCode] = new ExifMap(tagCode)
+ }
+ parseExifTags(
+ dataView,
+ tiffOffset,
+ tiffOffset + dirOffset,
+ littleEndian,
+ data.exif[tagCode],
+ data.exifOffsets && data.exifOffsets[tagCode],
+ includeTags && includeTags[tagCode],
+ excludeTags && excludeTags[tagCode]
+ )
+ }
+ }
+
loadImage.parseExifData = function (dataView, offset, length, data, options) {
if (options.disableExif) {
return
}
+ var includeTags = options.includeExifTags
+ var excludeTags = options.excludeExifTags || {
+ 0x8769: {
+ // ExifIFDPointer
+ 0x927c: true // MakerNote
+ }
+ }
var tiffOffset = offset + 10
var littleEndian
var dirOffset
- var thumbnailData
+ var thumbnailIFD
// Check for the ASCII code for "Exif" (0x45786966):
if (dataView.getUint32(offset + 4) !== 0x45786966) {
// No Exif data, might be XMP data instead
@@ -256,65 +380,81 @@
// Retrieve the directory offset bytes, usually 0x00000008 or 8 decimal:
dirOffset = dataView.getUint32(tiffOffset + 4, littleEndian)
// Create the exif object to store the tags:
- data.exif = new loadImage.ExifMap()
- // Parse the tags of the main image directory and retrieve the
- // offset to the next directory, usually the thumbnail directory:
- dirOffset = loadImage.parseExifTags(
+ data.exif = new ExifMap()
+ if (!options.disableExifOffsets) {
+ data.exifOffsets = new ExifMap()
+ data.exifTiffOffset = tiffOffset
+ data.exifLittleEndian = littleEndian
+ }
+ // Parse the tags of the main image directory (IFD0) and retrieve the
+ // offset to the next directory (IFD1), usually the thumbnail directory:
+ dirOffset = parseExifTags(
dataView,
tiffOffset,
tiffOffset + dirOffset,
littleEndian,
- data
+ data.exif,
+ data.exifOffsets,
+ includeTags,
+ excludeTags
)
- if (dirOffset && !options.disableExifThumbnail) {
- thumbnailData = { exif: {} }
- dirOffset = loadImage.parseExifTags(
- dataView,
- tiffOffset,
- tiffOffset + dirOffset,
- littleEndian,
- thumbnailData
- )
- // Check for JPEG Thumbnail offset:
- if (thumbnailData.exif[0x0201]) {
- data.exif.Thumbnail = loadImage.getExifThumbnail(
- dataView,
- tiffOffset + thumbnailData.exif[0x0201],
- thumbnailData.exif[0x0202] // Thumbnail data length
- )
+ if (dirOffset && shouldIncludeTag(includeTags, excludeTags, 'ifd1')) {
+ data.exif.ifd1 = dirOffset
+ if (data.exifOffsets) {
+ data.exifOffsets.ifd1 = tiffOffset + dirOffset
}
}
- // Check for Exif Sub IFD Pointer:
- if (data.exif[0x8769] && !options.disableExifSub) {
- loadImage.parseExifTags(
+ Object.keys(data.exif.ifds).forEach(function (tagCode) {
+ parseExifIFD(
+ data,
+ tagCode,
dataView,
tiffOffset,
- tiffOffset + data.exif[0x8769], // directory offset
littleEndian,
- data
+ includeTags,
+ excludeTags
)
- }
- // Check for GPS Info IFD Pointer:
- if (data.exif[0x8825] && !options.disableExifGps) {
- loadImage.parseExifTags(
+ })
+ thumbnailIFD = data.exif.ifd1
+ // Check for JPEG Thumbnail offset and data length:
+ if (thumbnailIFD && thumbnailIFD[0x0201]) {
+ thumbnailIFD[0x0201] = getExifThumbnail(
dataView,
- tiffOffset,
- tiffOffset + data.exif[0x8825], // directory offset
- littleEndian,
- data
+ tiffOffset + thumbnailIFD[0x0201],
+ thumbnailIFD[0x0202] // Thumbnail data length
)
}
}
- // Registers the Exif parser for the APP1 JPEG meta data segment:
+ // Registers the Exif parser for the APP1 JPEG metadata segment:
loadImage.metaDataParsers.jpeg[0xffe1].push(loadImage.parseExifData)
+ loadImage.exifWriters = {
+ // Orientation writer:
+ 0x0112: function (buffer, data, value) {
+ var orientationOffset = data.exifOffsets[0x0112]
+ if (!orientationOffset) return buffer
+ var view = new DataView(buffer, orientationOffset + 8, 2)
+ view.setUint16(0, value, data.exifLittleEndian)
+ return buffer
+ }
+ }
+
+ loadImage.writeExifData = function (buffer, data, id, value) {
+ return loadImage.exifWriters[data.exif.map[id]](buffer, data, value)
+ }
+
+ loadImage.ExifMap = ExifMap
+
// Adds the following properties to the parseMetaData callback data:
- // * exif: The exif tags, parsed by the parseExifData method
+ // - exif: The parsed Exif tags
+ // - exifOffsets: The parsed Exif tag offsets
+ // - exifTiffOffset: TIFF header offset (used for offset pointers)
+ // - exifLittleEndian: little endian order if true, big endian if false
// Adds the following options to the parseMetaData method:
- // * disableExif: Disables Exif parsing.
- // * disableExifThumbnail: Disables parsing of the Exif Thumbnail.
- // * disableExifSub: Disables parsing of the Exif Sub IFD.
- // * disableExifGps: Disables parsing of the Exif GPS Info IFD.
+ // - disableExif: Disables Exif parsing when true.
+ // - disableExifOffsets: Disables storing Exif tag offsets when true.
+ // - includeExifTags: A map of Exif tags to include for parsing.
+ // - excludeExifTags: A map of Exif tags to exclude from parsing.
})
diff --git a/js/load-image-fetch.js b/js/load-image-fetch.js
index 8d8f0a0..af65797 100644
--- a/js/load-image-fetch.js
+++ b/js/load-image-fetch.js
@@ -9,15 +9,15 @@
* https://opensource.org/licenses/MIT
*/
-/* global define, fetch, Request */
+/* global define, module, require, Promise */
;(function (factory) {
'use strict'
if (typeof define === 'function' && define.amd) {
// Register as an anonymous AMD module:
- define(['./load-image', './load-image-meta'], factory)
+ define(['./load-image'], factory)
} else if (typeof module === 'object' && module.exports) {
- factory(require('./load-image'), require('./load-image-meta'))
+ factory(require('./load-image'))
} else {
// Browser globals:
factory(window.loadImage)
@@ -25,21 +25,82 @@
})(function (loadImage) {
'use strict'
- if (typeof fetch !== 'undefined' && typeof Request !== 'undefined') {
+ var global = loadImage.global
+
+ if (
+ global.fetch &&
+ global.Request &&
+ global.Response &&
+ global.Response.prototype.blob
+ ) {
loadImage.fetchBlob = function (url, callback, options) {
- if (loadImage.hasMetaOption(options)) {
- return fetch(new Request(url, options))
- .then(function (response) {
- return response.blob()
- })
- .then(callback)
- .catch(function (err) {
- console.log(err)
- callback()
+ /**
+ * Fetch response handler.
+ *
+ * @param {Response} response Fetch response
+ * @returns {Blob} Fetched Blob.
+ */
+ function responseHandler(response) {
+ return response.blob()
+ }
+ if (global.Promise && typeof callback !== 'function') {
+ return fetch(new Request(url, callback)).then(responseHandler)
+ }
+ fetch(new Request(url, options))
+ .then(responseHandler)
+ .then(callback)
+ [
+ // Avoid parsing error in IE<9, where catch is a reserved word.
+ // eslint-disable-next-line dot-notation
+ 'catch'
+ ](function (err) {
+ callback(null, err)
+ })
+ }
+ } else if (
+ global.XMLHttpRequest &&
+ // https://xhr.spec.whatwg.org/#the-responsetype-attribute
+ new XMLHttpRequest().responseType === ''
+ ) {
+ loadImage.fetchBlob = function (url, callback, options) {
+ /**
+ * Promise executor
+ *
+ * @param {Function} resolve Resolution function
+ * @param {Function} reject Rejection function
+ */
+ function executor(resolve, reject) {
+ options = options || {} // eslint-disable-line no-param-reassign
+ var req = new XMLHttpRequest()
+ req.open(options.method || 'GET', url)
+ if (options.headers) {
+ Object.keys(options.headers).forEach(function (key) {
+ req.setRequestHeader(key, options.headers[key])
})
- } else {
- callback()
+ }
+ req.withCredentials = options.credentials === 'include'
+ req.responseType = 'blob'
+ req.onload = function () {
+ resolve(req.response)
+ }
+ req.onerror =
+ req.onabort =
+ req.ontimeout =
+ function (err) {
+ if (resolve === reject) {
+ // Not using Promises
+ reject(null, err)
+ } else {
+ reject(err)
+ }
+ }
+ req.send(options.body)
+ }
+ if (global.Promise && typeof callback !== 'function') {
+ options = callback // eslint-disable-line no-param-reassign
+ return new Promise(executor)
}
+ return executor(callback, callback)
}
}
})
diff --git a/js/load-image-iptc-map.js b/js/load-image-iptc-map.js
new file mode 100644
index 0000000..165d17d
--- /dev/null
+++ b/js/load-image-iptc-map.js
@@ -0,0 +1,169 @@
+/*
+ * JavaScript Load Image IPTC Map
+ * https://github.com/blueimp/JavaScript-Load-Image
+ *
+ * Copyright 2013, Sebastian Tschan
+ * Copyright 2018, Dave Bevan
+ *
+ * IPTC tags mapping based on
+ * https://iptc.org/standards/photo-metadata
+ * https://exiftool.org/TagNames/IPTC.html
+ *
+ * Licensed under the MIT license:
+ * https://opensource.org/licenses/MIT
+ */
+
+/* global define, module, require */
+
+;(function (factory) {
+ 'use strict'
+ if (typeof define === 'function' && define.amd) {
+ // Register as an anonymous AMD module:
+ define(['./load-image', './load-image-iptc'], factory)
+ } else if (typeof module === 'object' && module.exports) {
+ factory(require('./load-image'), require('./load-image-iptc'))
+ } else {
+ // Browser globals:
+ factory(window.loadImage)
+ }
+})(function (loadImage) {
+ 'use strict'
+
+ var IptcMapProto = loadImage.IptcMap.prototype
+
+ IptcMapProto.tags = {
+ 0: 'ApplicationRecordVersion',
+ 3: 'ObjectTypeReference',
+ 4: 'ObjectAttributeReference',
+ 5: 'ObjectName',
+ 7: 'EditStatus',
+ 8: 'EditorialUpdate',
+ 10: 'Urgency',
+ 12: 'SubjectReference',
+ 15: 'Category',
+ 20: 'SupplementalCategories',
+ 22: 'FixtureIdentifier',
+ 25: 'Keywords',
+ 26: 'ContentLocationCode',
+ 27: 'ContentLocationName',
+ 30: 'ReleaseDate',
+ 35: 'ReleaseTime',
+ 37: 'ExpirationDate',
+ 38: 'ExpirationTime',
+ 40: 'SpecialInstructions',
+ 42: 'ActionAdvised',
+ 45: 'ReferenceService',
+ 47: 'ReferenceDate',
+ 50: 'ReferenceNumber',
+ 55: 'DateCreated',
+ 60: 'TimeCreated',
+ 62: 'DigitalCreationDate',
+ 63: 'DigitalCreationTime',
+ 65: 'OriginatingProgram',
+ 70: 'ProgramVersion',
+ 75: 'ObjectCycle',
+ 80: 'Byline',
+ 85: 'BylineTitle',
+ 90: 'City',
+ 92: 'Sublocation',
+ 95: 'State',
+ 100: 'CountryCode',
+ 101: 'Country',
+ 103: 'OriginalTransmissionReference',
+ 105: 'Headline',
+ 110: 'Credit',
+ 115: 'Source',
+ 116: 'CopyrightNotice',
+ 118: 'Contact',
+ 120: 'Caption',
+ 121: 'LocalCaption',
+ 122: 'Writer',
+ 125: 'RasterizedCaption',
+ 130: 'ImageType',
+ 131: 'ImageOrientation',
+ 135: 'LanguageIdentifier',
+ 150: 'AudioType',
+ 151: 'AudioSamplingRate',
+ 152: 'AudioSamplingResolution',
+ 153: 'AudioDuration',
+ 154: 'AudioOutcue',
+ 184: 'JobID',
+ 185: 'MasterDocumentID',
+ 186: 'ShortDocumentID',
+ 187: 'UniqueDocumentID',
+ 188: 'OwnerID',
+ 200: 'ObjectPreviewFileFormat',
+ 201: 'ObjectPreviewFileVersion',
+ 202: 'ObjectPreviewData',
+ 221: 'Prefs',
+ 225: 'ClassifyState',
+ 228: 'SimilarityIndex',
+ 230: 'DocumentNotes',
+ 231: 'DocumentHistory',
+ 232: 'ExifCameraInfo',
+ 255: 'CatalogSets'
+ }
+
+ IptcMapProto.stringValues = {
+ 10: {
+ 0: '0 (reserved)',
+ 1: '1 (most urgent)',
+ 2: '2',
+ 3: '3',
+ 4: '4',
+ 5: '5 (normal urgency)',
+ 6: '6',
+ 7: '7',
+ 8: '8 (least urgent)',
+ 9: '9 (user-defined priority)'
+ },
+ 75: {
+ a: 'Morning',
+ b: 'Both Morning and Evening',
+ p: 'Evening'
+ },
+ 131: {
+ L: 'Landscape',
+ P: 'Portrait',
+ S: 'Square'
+ }
+ }
+
+ IptcMapProto.getText = function (id) {
+ var value = this.get(id)
+ var tagCode = this.map[id]
+ var stringValue = this.stringValues[tagCode]
+ if (stringValue) return stringValue[value]
+ return String(value)
+ }
+
+ IptcMapProto.getAll = function () {
+ var map = {}
+ var prop
+ var name
+ for (prop in this) {
+ if (Object.prototype.hasOwnProperty.call(this, prop)) {
+ name = this.tags[prop]
+ if (name) map[name] = this.getText(name)
+ }
+ }
+ return map
+ }
+
+ IptcMapProto.getName = function (tagCode) {
+ return this.tags[tagCode]
+ }
+
+ // Extend the map of tag names to tag codes:
+ ;(function () {
+ var tags = IptcMapProto.tags
+ var map = IptcMapProto.map || {}
+ var prop
+ // Map the tag names to tags:
+ for (prop in tags) {
+ if (Object.prototype.hasOwnProperty.call(tags, prop)) {
+ map[tags[prop]] = Number(prop)
+ }
+ }
+ })()
+})
diff --git a/js/load-image-iptc.js b/js/load-image-iptc.js
new file mode 100644
index 0000000..04eb796
--- /dev/null
+++ b/js/load-image-iptc.js
@@ -0,0 +1,239 @@
+/*
+ * JavaScript Load Image IPTC Parser
+ * https://github.com/blueimp/JavaScript-Load-Image
+ *
+ * Copyright 2013, Sebastian Tschan
+ * Copyright 2018, Dave Bevan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * https://opensource.org/licenses/MIT
+ */
+
+/* global define, module, require, DataView */
+
+;(function (factory) {
+ 'use strict'
+ if (typeof define === 'function' && define.amd) {
+ // Register as an anonymous AMD module:
+ define(['./load-image', './load-image-meta'], factory)
+ } else if (typeof module === 'object' && module.exports) {
+ factory(require('./load-image'), require('./load-image-meta'))
+ } else {
+ // Browser globals:
+ factory(window.loadImage)
+ }
+})(function (loadImage) {
+ 'use strict'
+
+ /**
+ * IPTC tag map
+ *
+ * @name IptcMap
+ * @class
+ */
+ function IptcMap() {}
+
+ IptcMap.prototype.map = {
+ ObjectName: 5
+ }
+
+ IptcMap.prototype.types = {
+ 0: 'Uint16', // ApplicationRecordVersion
+ 200: 'Uint16', // ObjectPreviewFileFormat
+ 201: 'Uint16', // ObjectPreviewFileVersion
+ 202: 'binary' // ObjectPreviewData
+ }
+
+ /**
+ * Retrieves IPTC tag value
+ *
+ * @param {number|string} id IPTC tag code or name
+ * @returns {object} IPTC tag value
+ */
+ IptcMap.prototype.get = function (id) {
+ return this[id] || this[this.map[id]]
+ }
+
+ /**
+ * Retrieves string for the given DataView and range
+ *
+ * @param {DataView} dataView Data view interface
+ * @param {number} offset Offset start
+ * @param {number} length Offset length
+ * @returns {string} String value
+ */
+ function getStringValue(dataView, offset, length) {
+ var outstr = ''
+ var end = offset + length
+ for (var n = offset; n < end; n += 1) {
+ outstr += String.fromCharCode(dataView.getUint8(n))
+ }
+ return outstr
+ }
+
+ /**
+ * Retrieves tag value for the given DataView and range
+ *
+ * @param {number} tagCode tag code
+ * @param {IptcMap} map IPTC tag map
+ * @param {DataView} dataView Data view interface
+ * @param {number} offset Range start
+ * @param {number} length Range length
+ * @returns {object} Tag value
+ */
+ function getTagValue(tagCode, map, dataView, offset, length) {
+ if (map.types[tagCode] === 'binary') {
+ return new Blob([dataView.buffer.slice(offset, offset + length)])
+ }
+ if (map.types[tagCode] === 'Uint16') {
+ return dataView.getUint16(offset)
+ }
+ return getStringValue(dataView, offset, length)
+ }
+
+ /**
+ * Combines IPTC value with existing ones.
+ *
+ * @param {object} value Existing IPTC field value
+ * @param {object} newValue New IPTC field value
+ * @returns {object} Resulting IPTC field value
+ */
+ function combineTagValues(value, newValue) {
+ if (value === undefined) return newValue
+ if (value instanceof Array) {
+ value.push(newValue)
+ return value
+ }
+ return [value, newValue]
+ }
+
+ /**
+ * Parses IPTC tags.
+ *
+ * @param {DataView} dataView Data view interface
+ * @param {number} segmentOffset Segment offset
+ * @param {number} segmentLength Segment length
+ * @param {object} data Data export object
+ * @param {object} includeTags Map of tags to include
+ * @param {object} excludeTags Map of tags to exclude
+ */
+ function parseIptcTags(
+ dataView,
+ segmentOffset,
+ segmentLength,
+ data,
+ includeTags,
+ excludeTags
+ ) {
+ var value, tagSize, tagCode
+ var segmentEnd = segmentOffset + segmentLength
+ var offset = segmentOffset
+ while (offset < segmentEnd) {
+ if (
+ dataView.getUint8(offset) === 0x1c && // tag marker
+ dataView.getUint8(offset + 1) === 0x02 // record number, only handles v2
+ ) {
+ tagCode = dataView.getUint8(offset + 2)
+ if (
+ (!includeTags || includeTags[tagCode]) &&
+ (!excludeTags || !excludeTags[tagCode])
+ ) {
+ tagSize = dataView.getInt16(offset + 3)
+ value = getTagValue(tagCode, data.iptc, dataView, offset + 5, tagSize)
+ data.iptc[tagCode] = combineTagValues(data.iptc[tagCode], value)
+ if (data.iptcOffsets) {
+ data.iptcOffsets[tagCode] = offset
+ }
+ }
+ }
+ offset += 1
+ }
+ }
+
+ /**
+ * Tests if field segment starts at offset.
+ *
+ * @param {DataView} dataView Data view interface
+ * @param {number} offset Segment offset
+ * @returns {boolean} True if '8BIM' exists at offset
+ */
+ function isSegmentStart(dataView, offset) {
+ return (
+ dataView.getUint32(offset) === 0x3842494d && // Photoshop segment start
+ dataView.getUint16(offset + 4) === 0x0404 // IPTC segment start
+ )
+ }
+
+ /**
+ * Returns header length.
+ *
+ * @param {DataView} dataView Data view interface
+ * @param {number} offset Segment offset
+ * @returns {number} Header length
+ */
+ function getHeaderLength(dataView, offset) {
+ var length = dataView.getUint8(offset + 7)
+ if (length % 2 !== 0) length += 1
+ // Check for pre photoshop 6 format
+ if (length === 0) {
+ // Always 4
+ length = 4
+ }
+ return length
+ }
+
+ loadImage.parseIptcData = function (dataView, offset, length, data, options) {
+ if (options.disableIptc) {
+ return
+ }
+ var markerLength = offset + length
+ while (offset + 8 < markerLength) {
+ if (isSegmentStart(dataView, offset)) {
+ var headerLength = getHeaderLength(dataView, offset)
+ var segmentOffset = offset + 8 + headerLength
+ if (segmentOffset > markerLength) {
+ // eslint-disable-next-line no-console
+ console.log('Invalid IPTC data: Invalid segment offset.')
+ break
+ }
+ var segmentLength = dataView.getUint16(offset + 6 + headerLength)
+ if (offset + segmentLength > markerLength) {
+ // eslint-disable-next-line no-console
+ console.log('Invalid IPTC data: Invalid segment size.')
+ break
+ }
+ // Create the iptc object to store the tags:
+ data.iptc = new IptcMap()
+ if (!options.disableIptcOffsets) {
+ data.iptcOffsets = new IptcMap()
+ }
+ parseIptcTags(
+ dataView,
+ segmentOffset,
+ segmentLength,
+ data,
+ options.includeIptcTags,
+ options.excludeIptcTags || { 202: true } // ObjectPreviewData
+ )
+ return
+ }
+ // eslint-disable-next-line no-param-reassign
+ offset += 1
+ }
+ }
+
+ // Registers this IPTC parser for the APP13 JPEG metadata segment:
+ loadImage.metaDataParsers.jpeg[0xffed].push(loadImage.parseIptcData)
+
+ loadImage.IptcMap = IptcMap
+
+ // Adds the following properties to the parseMetaData callback data:
+ // - iptc: The iptc tags, parsed by the parseIptcData method
+
+ // Adds the following options to the parseMetaData method:
+ // - disableIptc: Disables IPTC parsing when true.
+ // - disableIptcOffsets: Disables storing IPTC tag offsets when true.
+ // - includeIptcTags: A map of IPTC tags to include for parsing.
+ // - excludeIptcTags: A map of IPTC tags to exclude from parsing.
+})
diff --git a/js/load-image-meta.js b/js/load-image-meta.js
index da93bd4..357b9b3 100644
--- a/js/load-image-meta.js
+++ b/js/load-image-meta.js
@@ -5,7 +5,7 @@
* Copyright 2013, Sebastian Tschan
* https://blueimp.net
*
- * Image meta data handling implementation
+ * Image metadata handling implementation
* based on the help and contribution of
* Achim Stöhr.
*
@@ -13,7 +13,7 @@
* https://opensource.org/licenses/MIT
*/
-/* global define, Blob */
+/* global define, module, require, Promise, DataView, Uint8Array, ArrayBuffer */
;(function (factory) {
'use strict'
@@ -29,74 +29,99 @@
})(function (loadImage) {
'use strict'
- var hasblobSlice =
- typeof Blob !== 'undefined' &&
+ var global = loadImage.global
+ var originalTransform = loadImage.transform
+
+ var blobSlice =
+ global.Blob &&
(Blob.prototype.slice ||
Blob.prototype.webkitSlice ||
Blob.prototype.mozSlice)
- loadImage.blobSlice =
- hasblobSlice &&
- function () {
- var slice = this.slice || this.webkitSlice || this.mozSlice
- return slice.apply(this, arguments)
+ var bufferSlice =
+ (global.ArrayBuffer && ArrayBuffer.prototype.slice) ||
+ function (begin, end) {
+ // Polyfill for IE10, which does not support ArrayBuffer.slice
+ // eslint-disable-next-line no-param-reassign
+ end = end || this.byteLength - begin
+ var arr1 = new Uint8Array(this, begin, end)
+ var arr2 = new Uint8Array(end)
+ arr2.set(arr1)
+ return arr2.buffer
}
- loadImage.metaDataParsers = {
+ var metaDataParsers = {
jpeg: {
- 0xffe1: [] // APP1 marker
+ 0xffe1: [], // APP1 marker
+ 0xffed: [] // APP13 marker
}
}
- // Parses image meta data and calls the callback with an object argument
- // with the following properties:
- // * imageHead: The complete image head as ArrayBuffer (Uint8Array for IE10)
- // The options arguments accepts an object and supports the following properties:
- // * maxMetaDataSize: Defines the maximum number of bytes to parse.
- // * disableImageHead: Disables creating the imageHead property.
- loadImage.parseMetaData = function (file, callback, options, data) {
- options = options || {}
- data = data || {}
+ /**
+ * Parses image metadata and calls the callback with an object argument
+ * with the following property:
+ * - imageHead: The complete image head as ArrayBuffer
+ * The options argument accepts an object and supports the following
+ * properties:
+ * - maxMetaDataSize: Defines the maximum number of bytes to parse.
+ * - disableImageHead: Disables creating the imageHead property.
+ *
+ * @param {Blob} file Blob object
+ * @param {Function} [callback] Callback function
+ * @param {object} [options] Parsing options
+ * @param {object} [data] Result data object
+ * @returns {Promise|undefined} Returns Promise if no callback given.
+ */
+ function parseMetaData(file, callback, options, data) {
var that = this
- // 256 KiB should contain all EXIF/ICC/IPTC segments:
- var maxMetaDataSize = options.maxMetaDataSize || 262144
- var noMetaData = !(
- typeof DataView !== 'undefined' &&
- file &&
- file.size >= 12 &&
- file.type === 'image/jpeg' &&
- loadImage.blobSlice
- )
- if (
- noMetaData ||
- !loadImage.readFile(
- loadImage.blobSlice.call(file, 0, maxMetaDataSize),
- function (e) {
- if (e.target.error) {
- // FileReader error
- console.log(e.target.error)
- callback(data)
- return
- }
- // Note on endianness:
- // Since the marker and length bytes in JPEG files are always
- // stored in big endian order, we can leave the endian parameter
- // of the DataView methods undefined, defaulting to big endian.
- var buffer = e.target.result
- var dataView = new DataView(buffer)
- var offset = 2
- var maxOffset = dataView.byteLength - 4
- var headLength = offset
- var markerBytes
- var markerLength
- var parsers
- var i
- // Check for the JPEG marker (0xffd8):
- if (dataView.getUint16(0) === 0xffd8) {
+ /**
+ * Promise executor
+ *
+ * @param {Function} resolve Resolution function
+ * @param {Function} reject Rejection function
+ * @returns {undefined} Undefined
+ */
+ function executor(resolve, reject) {
+ if (
+ !(
+ global.DataView &&
+ blobSlice &&
+ file &&
+ file.size >= 12 &&
+ file.type === 'image/jpeg'
+ )
+ ) {
+ // Nothing to parse
+ return resolve(data)
+ }
+ // 256 KiB should contain all EXIF/ICC/IPTC segments:
+ var maxMetaDataSize = options.maxMetaDataSize || 262144
+ if (
+ !loadImage.readFile(
+ blobSlice.call(file, 0, maxMetaDataSize),
+ function (buffer) {
+ // Note on endianness:
+ // Since the marker and length bytes in JPEG files are always
+ // stored in big endian order, we can leave the endian parameter
+ // of the DataView methods undefined, defaulting to big endian.
+ var dataView = new DataView(buffer)
+ // Check for the JPEG marker (0xffd8):
+ if (dataView.getUint16(0) !== 0xffd8) {
+ return reject(
+ new Error('Invalid JPEG file: Missing JPEG marker.')
+ )
+ }
+ var offset = 2
+ var maxOffset = dataView.byteLength - 4
+ var headLength = offset
+ var markerBytes
+ var markerLength
+ var parsers
+ var i
while (offset < maxOffset) {
markerBytes = dataView.getUint16(offset)
// Search for APPn (0xffeN) and COM (0xfffe) markers,
- // which contain application-specific meta-data like
+ // which contain application-specific metadata like
// Exif, ICC and IPTC data and text comments:
if (
(markerBytes >= 0xffe0 && markerBytes <= 0xffef) ||
@@ -108,11 +133,12 @@
// but not the marker bytes, so we add 2:
markerLength = dataView.getUint16(offset + 2) + 2
if (offset + markerLength > dataView.byteLength) {
- console.log('Invalid meta data: Invalid segment size.')
+ // eslint-disable-next-line no-console
+ console.log('Invalid JPEG metadata: Invalid segment size.')
break
}
- parsers = loadImage.metaDataParsers.jpeg[markerBytes]
- if (parsers) {
+ parsers = metaDataParsers.jpeg[markerBytes]
+ if (parsers && !options.disableMetaDataParsers) {
for (i = 0; i < parsers.length; i += 1) {
parsers[i].call(
that,
@@ -128,45 +154,94 @@
headLength = offset
} else {
// Not an APPn or COM marker, probably safe to
- // assume that this is the end of the meta data
+ // assume that this is the end of the metadata
break
}
}
// Meta length must be longer than JPEG marker (2)
// plus APPn marker (2), followed by length bytes (2):
if (!options.disableImageHead && headLength > 6) {
- if (buffer.slice) {
- data.imageHead = buffer.slice(0, headLength)
- } else {
- // Workaround for IE10, which does not yet
- // support ArrayBuffer.slice:
- data.imageHead = new Uint8Array(buffer).subarray(0, headLength)
- }
+ data.imageHead = bufferSlice.call(buffer, 0, headLength)
}
- } else {
- console.log('Invalid JPEG file: Missing JPEG marker.')
- }
- callback(data)
- },
- 'readAsArrayBuffer'
- )
- ) {
- callback(data)
+ resolve(data)
+ },
+ reject,
+ 'readAsArrayBuffer'
+ )
+ ) {
+ // No support for the FileReader interface, nothing to parse
+ resolve(data)
+ }
+ }
+ options = options || {} // eslint-disable-line no-param-reassign
+ if (global.Promise && typeof callback !== 'function') {
+ options = callback || {} // eslint-disable-line no-param-reassign
+ data = options // eslint-disable-line no-param-reassign
+ return new Promise(executor)
}
+ data = data || {} // eslint-disable-line no-param-reassign
+ return executor(callback, callback)
}
- // Determines if meta data should be loaded automatically:
- loadImage.hasMetaOption = function (options) {
- return options && options.meta
+ /**
+ * Replaces the head of a JPEG Blob
+ *
+ * @param {Blob} blob Blob object
+ * @param {ArrayBuffer} oldHead Old JPEG head
+ * @param {ArrayBuffer} newHead New JPEG head
+ * @returns {Blob} Combined Blob
+ */
+ function replaceJPEGHead(blob, oldHead, newHead) {
+ if (!blob || !oldHead || !newHead) return null
+ return new Blob([newHead, blobSlice.call(blob, oldHead.byteLength)], {
+ type: 'image/jpeg'
+ })
+ }
+
+ /**
+ * Replaces the image head of a JPEG blob with the given one.
+ * Returns a Promise or calls the callback with the new Blob.
+ *
+ * @param {Blob} blob Blob object
+ * @param {ArrayBuffer} head New JPEG head
+ * @param {Function} [callback] Callback function
+ * @returns {Promise|undefined} Combined Blob
+ */
+ function replaceHead(blob, head, callback) {
+ var options = { maxMetaDataSize: 1024, disableMetaDataParsers: true }
+ if (!callback && global.Promise) {
+ return parseMetaData(blob, options).then(function (data) {
+ return replaceJPEGHead(blob, data.imageHead, head)
+ })
+ }
+ parseMetaData(
+ blob,
+ function (data) {
+ callback(replaceJPEGHead(blob, data.imageHead, head))
+ },
+ options
+ )
}
- var originalTransform = loadImage.transform
loadImage.transform = function (img, options, callback, file, data) {
- if (loadImage.hasMetaOption(options)) {
- loadImage.parseMetaData(
+ if (loadImage.requiresMetaData(options)) {
+ data = data || {} // eslint-disable-line no-param-reassign
+ parseMetaData(
file,
- function (data) {
- originalTransform.call(loadImage, img, options, callback, file, data)
+ function (result) {
+ if (result !== data) {
+ // eslint-disable-next-line no-console
+ if (global.console) console.log(result)
+ result = data // eslint-disable-line no-param-reassign
+ }
+ originalTransform.call(
+ loadImage,
+ img,
+ options,
+ callback,
+ file,
+ result
+ )
},
options,
data
@@ -175,4 +250,10 @@
originalTransform.apply(loadImage, arguments)
}
}
+
+ loadImage.blobSlice = blobSlice
+ loadImage.bufferSlice = bufferSlice
+ loadImage.replaceHead = replaceHead
+ loadImage.parseMetaData = parseMetaData
+ loadImage.metaDataParsers = metaDataParsers
})
diff --git a/js/load-image-orientation.js b/js/load-image-orientation.js
index f14922f..c3d53c4 100644
--- a/js/load-image-orientation.js
+++ b/js/load-image-orientation.js
@@ -9,7 +9,36 @@
* https://opensource.org/licenses/MIT
*/
-/* global define */
+/*
+Exif orientation values to correctly display the letter F:
+
+ 1 2
+ ██████ ██████
+ ██ ██
+ ████ ████
+ ██ ██
+ ██ ██
+
+ 3 4
+ ██ ██
+ ██ ██
+ ████ ████
+ ██ ██
+ ██████ ██████
+
+ 5 6
+██████████ ██
+██ ██ ██ ██
+██ ██████████
+
+ 7 8
+ ██ ██████████
+ ██ ██ ██ ██
+██████████ ██
+
+*/
+
+/* global define, module, require */
;(function (factory) {
'use strict'
@@ -29,160 +58,424 @@
})(function (loadImage) {
'use strict'
- var originalHasCanvasOption = loadImage.hasCanvasOption
- var originalHasMetaOption = loadImage.hasMetaOption
+ var originalTransform = loadImage.transform
+ var originalRequiresCanvas = loadImage.requiresCanvas
+ var originalRequiresMetaData = loadImage.requiresMetaData
var originalTransformCoordinates = loadImage.transformCoordinates
var originalGetTransformedOptions = loadImage.getTransformedOptions
+ ;(function ($) {
+ // Guard for non-browser environments (e.g. server-side rendering):
+ if (!$.global.document) return
+ // black+white 3x2 JPEG, with the following meta information set:
+ // - EXIF Orientation: 6 (Rotated 90° CCW)
+ // Image data layout (B=black, F=white):
+ // BFF
+ // BBB
+ var testImageURL =
+ 'data:image/jpeg;base64,/9j/4QAiRXhpZgAATU0AKgAAAAgAAQESAAMAAAABAAYAAAA' +
+ 'AAAD/2wCEAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBA' +
+ 'QEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE' +
+ 'BAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAf/AABEIAAIAAwMBEQACEQEDEQH/x' +
+ 'ABRAAEAAAAAAAAAAAAAAAAAAAAKEAEBAQADAQEAAAAAAAAAAAAGBQQDCAkCBwEBAAAAAAA' +
+ 'AAAAAAAAAAAAAABEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8AG8T9NfSMEVMhQ' +
+ 'voP3fFiRZ+MTHDifa/95OFSZU5OzRzxkyejv8ciEfhSceSXGjS8eSdLnZc2HDm4M3BxcXw' +
+ 'H/9k='
+ var img = document.createElement('img')
+ img.onload = function () {
+ // Check if the browser supports automatic image orientation:
+ $.orientation = img.width === 2 && img.height === 3
+ if ($.orientation) {
+ var canvas = $.createCanvas(1, 1, true)
+ var ctx = canvas.getContext('2d')
+ ctx.drawImage(img, 1, 1, 1, 1, 0, 0, 1, 1)
+ // Check if the source image coordinates (sX, sY, sWidth, sHeight) are
+ // correctly applied to the auto-orientated image, which should result
+ // in a white opaque pixel (e.g. in Safari).
+ // Browsers that show a transparent pixel (e.g. Chromium) fail to crop
+ // auto-oriented images correctly and require a workaround, e.g.
+ // drawing the complete source image to an intermediate canvas first.
+ // See https://bugs.chromium.org/p/chromium/issues/detail?id=1074354
+ $.orientationCropBug =
+ ctx.getImageData(0, 0, 1, 1).data.toString() !== '255,255,255,255'
+ }
+ }
+ img.src = testImageURL
+ })(loadImage)
+
+ /**
+ * Determines if the orientation requires a canvas element.
+ *
+ * @param {object} [options] Options object
+ * @param {boolean} [withMetaData] Is metadata required for orientation
+ * @returns {boolean} Returns true if orientation requires canvas/meta
+ */
+ function requiresCanvasOrientation(options, withMetaData) {
+ var orientation = options && options.orientation
+ return (
+ // Exif orientation for browsers without automatic image orientation:
+ (orientation === true && !loadImage.orientation) ||
+ // Orientation reset for browsers with automatic image orientation:
+ (orientation === 1 && loadImage.orientation) ||
+ // Orientation to defined value, requires meta for orientation reset only:
+ ((!withMetaData || loadImage.orientation) &&
+ orientation > 1 &&
+ orientation < 9)
+ )
+ }
+
+ /**
+ * Determines if the image requires an orientation change.
+ *
+ * @param {number} [orientation] Defined orientation value
+ * @param {number} [autoOrientation] Auto-orientation based on Exif data
+ * @returns {boolean} Returns true if an orientation change is required
+ */
+ function requiresOrientationChange(orientation, autoOrientation) {
+ return (
+ orientation !== autoOrientation &&
+ ((orientation === 1 && autoOrientation > 1 && autoOrientation < 9) ||
+ (orientation > 1 && orientation < 9))
+ )
+ }
+
+ /**
+ * Determines orientation combinations that require a rotation by 180°.
+ *
+ * The following is a list of combinations that return true:
+ *
+ * 2 (flip) => 5 (rot90,flip), 7 (rot90,flip), 6 (rot90), 8 (rot90)
+ * 4 (flip) => 5 (rot90,flip), 7 (rot90,flip), 6 (rot90), 8 (rot90)
+ *
+ * 5 (rot90,flip) => 2 (flip), 4 (flip), 6 (rot90), 8 (rot90)
+ * 7 (rot90,flip) => 2 (flip), 4 (flip), 6 (rot90), 8 (rot90)
+ *
+ * 6 (rot90) => 2 (flip), 4 (flip), 5 (rot90,flip), 7 (rot90,flip)
+ * 8 (rot90) => 2 (flip), 4 (flip), 5 (rot90,flip), 7 (rot90,flip)
+ *
+ * @param {number} [orientation] Defined orientation value
+ * @param {number} [autoOrientation] Auto-orientation based on Exif data
+ * @returns {boolean} Returns true if rotation by 180° is required
+ */
+ function requiresRot180(orientation, autoOrientation) {
+ if (autoOrientation > 1 && autoOrientation < 9) {
+ switch (orientation) {
+ case 2:
+ case 4:
+ return autoOrientation > 4
+ case 5:
+ case 7:
+ return autoOrientation % 2 === 0
+ case 6:
+ case 8:
+ return (
+ autoOrientation === 2 ||
+ autoOrientation === 4 ||
+ autoOrientation === 5 ||
+ autoOrientation === 7
+ )
+ }
+ }
+ return false
+ }
+
// Determines if the target image should be a canvas element:
- loadImage.hasCanvasOption = function (options) {
+ loadImage.requiresCanvas = function (options) {
return (
- !!options.orientation || originalHasCanvasOption.call(loadImage, options)
+ requiresCanvasOrientation(options) ||
+ originalRequiresCanvas.call(loadImage, options)
)
}
- // Determines if meta data should be loaded automatically:
- loadImage.hasMetaOption = function (options) {
+ // Determines if metadata should be loaded automatically:
+ loadImage.requiresMetaData = function (options) {
return (
- (options && options.orientation === true) ||
- originalHasMetaOption.call(loadImage, options)
+ requiresCanvasOrientation(options, true) ||
+ originalRequiresMetaData.call(loadImage, options)
)
}
- // Transform image orientation based on
- // the given EXIF orientation option:
- loadImage.transformCoordinates = function (canvas, options) {
- originalTransformCoordinates.call(loadImage, canvas, options)
- var ctx = canvas.getContext('2d')
- var width = canvas.width
- var height = canvas.height
- var styleWidth = canvas.style.width
- var styleHeight = canvas.style.height
+ loadImage.transform = function (img, options, callback, file, data) {
+ originalTransform.call(
+ loadImage,
+ img,
+ options,
+ function (img, data) {
+ if (data) {
+ var autoOrientation =
+ loadImage.orientation && data.exif && data.exif.get('Orientation')
+ if (autoOrientation > 4 && autoOrientation < 9) {
+ // Automatic image orientation switched image dimensions
+ var originalWidth = data.originalWidth
+ var originalHeight = data.originalHeight
+ data.originalWidth = originalHeight
+ data.originalHeight = originalWidth
+ }
+ }
+ callback(img, data)
+ },
+ file,
+ data
+ )
+ }
+
+ // Transforms coordinate and dimension options
+ // based on the given orientation option:
+ loadImage.getTransformedOptions = function (img, opts, data) {
+ var options = originalGetTransformedOptions.call(loadImage, img, opts)
+ var exifOrientation = data.exif && data.exif.get('Orientation')
var orientation = options.orientation
- if (!orientation || orientation > 8) {
- return
+ var autoOrientation = loadImage.orientation && exifOrientation
+ if (orientation === true) orientation = exifOrientation
+ if (!requiresOrientationChange(orientation, autoOrientation)) {
+ return options
}
- if (orientation > 4) {
- canvas.width = height
- canvas.height = width
- canvas.style.width = styleHeight
- canvas.style.height = styleWidth
+ var top = options.top
+ var right = options.right
+ var bottom = options.bottom
+ var left = options.left
+ var newOptions = {}
+ for (var i in options) {
+ if (Object.prototype.hasOwnProperty.call(options, i)) {
+ newOptions[i] = options[i]
+ }
+ }
+ newOptions.orientation = orientation
+ if (
+ (orientation > 4 && !(autoOrientation > 4)) ||
+ (orientation < 5 && autoOrientation > 4)
+ ) {
+ // Image dimensions and target dimensions are switched
+ newOptions.maxWidth = options.maxHeight
+ newOptions.maxHeight = options.maxWidth
+ newOptions.minWidth = options.minHeight
+ newOptions.minHeight = options.minWidth
+ newOptions.sourceWidth = options.sourceHeight
+ newOptions.sourceHeight = options.sourceWidth
+ }
+ if (autoOrientation > 1) {
+ // Browsers which correctly apply source image coordinates to
+ // auto-oriented images
+ switch (autoOrientation) {
+ case 2:
+ // Horizontal flip
+ right = options.left
+ left = options.right
+ break
+ case 3:
+ // 180° Rotate CCW
+ top = options.bottom
+ right = options.left
+ bottom = options.top
+ left = options.right
+ break
+ case 4:
+ // Vertical flip
+ top = options.bottom
+ bottom = options.top
+ break
+ case 5:
+ // Horizontal flip + 90° Rotate CCW
+ top = options.left
+ right = options.bottom
+ bottom = options.right
+ left = options.top
+ break
+ case 6:
+ // 90° Rotate CCW
+ top = options.left
+ right = options.top
+ bottom = options.right
+ left = options.bottom
+ break
+ case 7:
+ // Vertical flip + 90° Rotate CCW
+ top = options.right
+ right = options.top
+ bottom = options.left
+ left = options.bottom
+ break
+ case 8:
+ // 90° Rotate CW
+ top = options.right
+ right = options.bottom
+ bottom = options.left
+ left = options.top
+ break
+ }
+ // Some orientation combinations require additional rotation by 180°:
+ if (requiresRot180(orientation, autoOrientation)) {
+ var tmpTop = top
+ var tmpRight = right
+ top = bottom
+ right = left
+ bottom = tmpTop
+ left = tmpRight
+ }
}
+ newOptions.top = top
+ newOptions.right = right
+ newOptions.bottom = bottom
+ newOptions.left = left
+ // Account for defined browser orientation:
switch (orientation) {
case 2:
- // horizontal flip
- ctx.translate(width, 0)
- ctx.scale(-1, 1)
+ // Horizontal flip
+ newOptions.right = left
+ newOptions.left = right
break
case 3:
- // 180° rotate left
- ctx.translate(width, height)
- ctx.rotate(Math.PI)
+ // 180° Rotate CCW
+ newOptions.top = bottom
+ newOptions.right = left
+ newOptions.bottom = top
+ newOptions.left = right
break
case 4:
- // vertical flip
- ctx.translate(0, height)
- ctx.scale(1, -1)
+ // Vertical flip
+ newOptions.top = bottom
+ newOptions.bottom = top
break
case 5:
- // vertical flip + 90 rotate right
- ctx.rotate(0.5 * Math.PI)
- ctx.scale(1, -1)
+ // Vertical flip + 90° Rotate CW
+ newOptions.top = left
+ newOptions.right = bottom
+ newOptions.bottom = right
+ newOptions.left = top
break
case 6:
- // 90° rotate right
- ctx.rotate(0.5 * Math.PI)
- ctx.translate(0, -height)
+ // 90° Rotate CW
+ newOptions.top = right
+ newOptions.right = bottom
+ newOptions.bottom = left
+ newOptions.left = top
break
case 7:
- // horizontal flip + 90 rotate right
- ctx.rotate(0.5 * Math.PI)
- ctx.translate(width, -height)
- ctx.scale(-1, 1)
+ // Horizontal flip + 90° Rotate CW
+ newOptions.top = right
+ newOptions.right = top
+ newOptions.bottom = left
+ newOptions.left = bottom
break
case 8:
- // 90° rotate left
- ctx.rotate(-0.5 * Math.PI)
- ctx.translate(-width, 0)
+ // 90° Rotate CCW
+ newOptions.top = left
+ newOptions.right = top
+ newOptions.bottom = right
+ newOptions.left = bottom
break
}
+ return newOptions
}
- // Transforms coordinate and dimension options
- // based on the given orientation option:
- loadImage.getTransformedOptions = function (img, opts, data) {
- var options = originalGetTransformedOptions.call(loadImage, img, opts)
+ // Transform image orientation based on the given EXIF orientation option:
+ loadImage.transformCoordinates = function (canvas, options, data) {
+ originalTransformCoordinates.call(loadImage, canvas, options, data)
var orientation = options.orientation
- var newOptions
- var i
- if (orientation === true && data && data.exif) {
- orientation = data.exif.get('Orientation')
+ var autoOrientation =
+ loadImage.orientation && data.exif && data.exif.get('Orientation')
+ if (!requiresOrientationChange(orientation, autoOrientation)) {
+ return
}
- if (!orientation || orientation > 8 || orientation === 1) {
- return options
+ var ctx = canvas.getContext('2d')
+ var width = canvas.width
+ var height = canvas.height
+ var sourceWidth = width
+ var sourceHeight = height
+ if (
+ (orientation > 4 && !(autoOrientation > 4)) ||
+ (orientation < 5 && autoOrientation > 4)
+ ) {
+ // Image dimensions and target dimensions are switched
+ canvas.width = height
+ canvas.height = width
}
- newOptions = {}
- for (i in options) {
- if (options.hasOwnProperty(i)) {
- newOptions[i] = options[i]
- }
+ if (orientation > 4) {
+ // Destination and source dimensions are switched
+ sourceWidth = height
+ sourceHeight = width
}
- newOptions.orientation = orientation
- switch (orientation) {
+ // Reset automatic browser orientation:
+ switch (autoOrientation) {
case 2:
- // horizontal flip
- newOptions.left = options.right
- newOptions.right = options.left
+ // Horizontal flip
+ ctx.translate(sourceWidth, 0)
+ ctx.scale(-1, 1)
break
case 3:
- // 180° rotate left
- newOptions.left = options.right
- newOptions.top = options.bottom
- newOptions.right = options.left
- newOptions.bottom = options.top
+ // 180° Rotate CCW
+ ctx.translate(sourceWidth, sourceHeight)
+ ctx.rotate(Math.PI)
break
case 4:
- // vertical flip
- newOptions.top = options.bottom
- newOptions.bottom = options.top
+ // Vertical flip
+ ctx.translate(0, sourceHeight)
+ ctx.scale(1, -1)
break
case 5:
- // vertical flip + 90 rotate right
- newOptions.left = options.top
- newOptions.top = options.left
- newOptions.right = options.bottom
- newOptions.bottom = options.right
+ // Horizontal flip + 90° Rotate CCW
+ ctx.rotate(-0.5 * Math.PI)
+ ctx.scale(-1, 1)
break
case 6:
- // 90° rotate right
- newOptions.left = options.top
- newOptions.top = options.right
- newOptions.right = options.bottom
- newOptions.bottom = options.left
+ // 90° Rotate CCW
+ ctx.rotate(-0.5 * Math.PI)
+ ctx.translate(-sourceWidth, 0)
break
case 7:
- // horizontal flip + 90 rotate right
- newOptions.left = options.bottom
- newOptions.top = options.right
- newOptions.right = options.top
- newOptions.bottom = options.left
+ // Vertical flip + 90° Rotate CCW
+ ctx.rotate(-0.5 * Math.PI)
+ ctx.translate(-sourceWidth, sourceHeight)
+ ctx.scale(1, -1)
break
case 8:
- // 90° rotate left
- newOptions.left = options.bottom
- newOptions.top = options.left
- newOptions.right = options.top
- newOptions.bottom = options.right
+ // 90° Rotate CW
+ ctx.rotate(0.5 * Math.PI)
+ ctx.translate(0, -sourceHeight)
break
}
- if (newOptions.orientation > 4) {
- newOptions.maxWidth = options.maxHeight
- newOptions.maxHeight = options.maxWidth
- newOptions.minWidth = options.minHeight
- newOptions.minHeight = options.minWidth
- newOptions.sourceWidth = options.sourceHeight
- newOptions.sourceHeight = options.sourceWidth
+ // Some orientation combinations require additional rotation by 180°:
+ if (requiresRot180(orientation, autoOrientation)) {
+ ctx.translate(sourceWidth, sourceHeight)
+ ctx.rotate(Math.PI)
+ }
+ switch (orientation) {
+ case 2:
+ // Horizontal flip
+ ctx.translate(width, 0)
+ ctx.scale(-1, 1)
+ break
+ case 3:
+ // 180° Rotate CCW
+ ctx.translate(width, height)
+ ctx.rotate(Math.PI)
+ break
+ case 4:
+ // Vertical flip
+ ctx.translate(0, height)
+ ctx.scale(1, -1)
+ break
+ case 5:
+ // Vertical flip + 90° Rotate CW
+ ctx.rotate(0.5 * Math.PI)
+ ctx.scale(1, -1)
+ break
+ case 6:
+ // 90° Rotate CW
+ ctx.rotate(0.5 * Math.PI)
+ ctx.translate(0, -height)
+ break
+ case 7:
+ // Horizontal flip + 90° Rotate CW
+ ctx.rotate(0.5 * Math.PI)
+ ctx.translate(width, -height)
+ ctx.scale(-1, 1)
+ break
+ case 8:
+ // 90° Rotate CCW
+ ctx.rotate(-0.5 * Math.PI)
+ ctx.translate(-width, 0)
+ break
}
- return newOptions
}
})
diff --git a/js/load-image-scale.js b/js/load-image-scale.js
index aca6134..c5e381e 100644
--- a/js/load-image-scale.js
+++ b/js/load-image-scale.js
@@ -9,7 +9,7 @@
* https://opensource.org/licenses/MIT
*/
-/* global define */
+/* global define, module, require */
;(function (factory) {
'use strict'
@@ -27,6 +27,16 @@
var originalTransform = loadImage.transform
+ loadImage.createCanvas = function (width, height, offscreen) {
+ if (offscreen && loadImage.global.OffscreenCanvas) {
+ return new OffscreenCanvas(width, height)
+ }
+ var canvas = document.createElement('canvas')
+ canvas.width = width
+ canvas.height = height
+ return canvas
+ }
+
loadImage.transform = function (img, options, callback, file, data) {
originalTransform.call(
loadImage,
@@ -40,12 +50,12 @@
// Transform image coordinates, allows to override e.g.
// the canvas orientation based on the orientation option,
- // gets canvas, options passed as arguments:
+ // gets canvas, options and data passed as arguments:
loadImage.transformCoordinates = function () {}
// Returns transformed options, allows to override e.g.
// maxWidth, maxHeight and crop options based on the aspectRatio.
- // gets img, options passed as arguments:
+ // gets img, options, data passed as arguments:
loadImage.getTransformedOptions = function (img, options) {
var aspectRatio = options.aspectRatio
var newOptions
@@ -57,7 +67,7 @@
}
newOptions = {}
for (i in options) {
- if (options.hasOwnProperty(i)) {
+ if (Object.prototype.hasOwnProperty.call(options, i)) {
newOptions[i] = options[i]
}
}
@@ -75,50 +85,54 @@
}
// Canvas render method, allows to implement a different rendering algorithm:
- loadImage.renderImageToCanvas = function (
- canvas,
+ loadImage.drawImage = function (
img,
+ canvas,
sourceX,
sourceY,
sourceWidth,
sourceHeight,
- destX,
- destY,
destWidth,
- destHeight
+ destHeight,
+ options
) {
- canvas
- .getContext('2d')
- .drawImage(
- img,
- sourceX,
- sourceY,
- sourceWidth,
- sourceHeight,
- destX,
- destY,
- destWidth,
- destHeight
- )
- return canvas
+ var ctx = canvas.getContext('2d')
+ if (options.imageSmoothingEnabled === false) {
+ ctx.msImageSmoothingEnabled = false
+ ctx.imageSmoothingEnabled = false
+ } else if (options.imageSmoothingQuality) {
+ ctx.imageSmoothingQuality = options.imageSmoothingQuality
+ }
+ ctx.drawImage(
+ img,
+ sourceX,
+ sourceY,
+ sourceWidth,
+ sourceHeight,
+ 0,
+ 0,
+ destWidth,
+ destHeight
+ )
+ return ctx
}
// Determines if the target image should be a canvas element:
- loadImage.hasCanvasOption = function (options) {
+ loadImage.requiresCanvas = function (options) {
return options.canvas || options.crop || !!options.aspectRatio
}
// Scales and/or crops the given image (img or canvas HTML element)
- // using the given options.
- // Returns a canvas object if the browser supports canvas
- // and the hasCanvasOption method returns true or a canvas
- // object is passed as image, else the scaled image:
+ // using the given options:
loadImage.scale = function (img, options, data) {
+ // eslint-disable-next-line no-param-reassign
options = options || {}
- var canvas = document.createElement('canvas')
+ // eslint-disable-next-line no-param-reassign
+ data = data || {}
var useCanvas =
img.getContext ||
- (loadImage.hasCanvasOption(options) && canvas.getContext)
+ (loadImage.requiresCanvas(options) &&
+ !!loadImage.global.HTMLCanvasElement)
var width = img.naturalWidth || img.width
var height = img.naturalHeight || img.height
var destWidth = width
@@ -134,7 +148,11 @@
var pixelRatio
var downsamplingRatio
var tmp
- function scaleUp () {
+ var canvas
+ /**
+ * Scales up image dimensions
+ */
+ function scaleUp() {
var scale = Math.max(
(minWidth || destWidth) / destWidth,
(minHeight || destHeight) / destHeight
@@ -144,7 +162,10 @@
destHeight *= scale
}
}
- function scaleDown () {
+ /**
+ * Scales down image dimensions
+ */
+ function scaleDown() {
var scale = Math.min(
(maxWidth || destWidth) / destWidth,
(maxHeight || destHeight) / destHeight
@@ -155,6 +176,7 @@
}
}
if (useCanvas) {
+ // eslint-disable-next-line no-param-reassign
options = loadImage.getTransformedOptions(img, options, data)
sourceX = options.left || 0
sourceY = options.top || 0
@@ -186,12 +208,12 @@
destHeight = maxHeight
tmp = sourceWidth / sourceHeight - maxWidth / maxHeight
if (tmp < 0) {
- sourceHeight = maxHeight * sourceWidth / maxWidth
+ sourceHeight = (maxHeight * sourceWidth) / maxWidth
if (options.top === undefined && options.bottom === undefined) {
sourceY = (height - sourceHeight) / 2
}
} else if (tmp > 0) {
- sourceWidth = maxWidth * sourceHeight / maxHeight
+ sourceWidth = (maxWidth * sourceHeight) / maxHeight
if (options.left === undefined && options.right === undefined) {
sourceX = (width - sourceWidth) / 2
}
@@ -211,12 +233,40 @@
}
if (useCanvas) {
pixelRatio = options.pixelRatio
- if (pixelRatio > 1) {
- canvas.style.width = destWidth + 'px'
- canvas.style.height = destHeight + 'px'
+ if (
+ pixelRatio > 1 &&
+ // Check if the image has not yet had the device pixel ratio applied:
+ !(
+ img.style.width &&
+ Math.floor(parseFloat(img.style.width, 10)) ===
+ Math.floor(width / pixelRatio)
+ )
+ ) {
destWidth *= pixelRatio
destHeight *= pixelRatio
- canvas.getContext('2d').scale(pixelRatio, pixelRatio)
+ }
+ // Check if workaround for Chromium orientation crop bug is required:
+ // https://bugs.chromium.org/p/chromium/issues/detail?id=1074354
+ if (
+ loadImage.orientationCropBug &&
+ !img.getContext &&
+ (sourceX || sourceY || sourceWidth !== width || sourceHeight !== height)
+ ) {
+ // Write the complete source image to an intermediate canvas first:
+ tmp = img
+ // eslint-disable-next-line no-param-reassign
+ img = loadImage.createCanvas(width, height, true)
+ loadImage.drawImage(
+ tmp,
+ img,
+ 0,
+ 0,
+ width,
+ height,
+ width,
+ height,
+ options
+ )
}
downsamplingRatio = options.downsamplingRatio
if (
@@ -226,56 +276,49 @@
destHeight < sourceHeight
) {
while (sourceWidth * downsamplingRatio > destWidth) {
- canvas.width = sourceWidth * downsamplingRatio
- canvas.height = sourceHeight * downsamplingRatio
- loadImage.renderImageToCanvas(
- canvas,
+ canvas = loadImage.createCanvas(
+ sourceWidth * downsamplingRatio,
+ sourceHeight * downsamplingRatio,
+ true
+ )
+ loadImage.drawImage(
img,
+ canvas,
sourceX,
sourceY,
sourceWidth,
sourceHeight,
- 0,
- 0,
canvas.width,
- canvas.height
+ canvas.height,
+ options
)
sourceX = 0
sourceY = 0
sourceWidth = canvas.width
sourceHeight = canvas.height
- img = document.createElement('canvas')
- img.width = sourceWidth
- img.height = sourceHeight
- loadImage.renderImageToCanvas(
- img,
- canvas,
- 0,
- 0,
- sourceWidth,
- sourceHeight,
- 0,
- 0,
- sourceWidth,
- sourceHeight
- )
+ // eslint-disable-next-line no-param-reassign
+ img = canvas
}
}
- canvas.width = destWidth
- canvas.height = destHeight
- loadImage.transformCoordinates(canvas, options)
- return loadImage.renderImageToCanvas(
- canvas,
- img,
- sourceX,
- sourceY,
- sourceWidth,
- sourceHeight,
- 0,
- 0,
- destWidth,
- destHeight
- )
+ canvas = loadImage.createCanvas(destWidth, destHeight)
+ loadImage.transformCoordinates(canvas, options, data)
+ if (pixelRatio > 1) {
+ canvas.style.width = canvas.width / pixelRatio + 'px'
+ }
+ loadImage
+ .drawImage(
+ img,
+ canvas,
+ sourceX,
+ sourceY,
+ sourceWidth,
+ sourceHeight,
+ destWidth,
+ destHeight,
+ options
+ )
+ .setTransform(1, 0, 0, 1, 0, 0) // reset to the identity matrix
+ return canvas
}
img.width = destWidth
img.height = destHeight
diff --git a/js/load-image.all.min.js b/js/load-image.all.min.js
index 6c16907..884ef46 100644
--- a/js/load-image.all.min.js
+++ b/js/load-image.all.min.js
@@ -1,2 +1,2 @@
-!function(e){"use strict";function t(e,i,a){var o,n=document.createElement("img");return n.onerror=function(o){return t.onerror(n,o,e,i,a)},n.onload=function(o){return t.onload(n,o,e,i,a)},"string"==typeof e?(t.fetchBlob(e,function(i){i?(e=i,o=t.createObjectURL(e)):(o=e,a&&a.crossOrigin&&(n.crossOrigin=a.crossOrigin)),n.src=o},a),n):t.isInstanceOf("Blob",e)||t.isInstanceOf("File",e)?(o=n._objectURL=t.createObjectURL(e))?(n.src=o,n):t.readFile(e,function(e){var t=e.target;t&&t.result?n.src=t.result:i&&i(e)}):void 0}function i(e,i){!e._objectURL||i&&i.noRevoke||(t.revokeObjectURL(e._objectURL),delete e._objectURL)}var a=e.createObjectURL&&e||e.URL&&URL.revokeObjectURL&&URL||e.webkitURL&&webkitURL;t.fetchBlob=function(e,t,i){t()},t.isInstanceOf=function(e,t){return Object.prototype.toString.call(t)==="[object "+e+"]"},t.transform=function(e,t,i,a,o){i(e,o)},t.onerror=function(e,t,a,o,n){i(e,n),o&&o.call(e,t)},t.onload=function(e,a,o,n,r){i(e,r),n&&t.transform(e,r,n,o,{})},t.createObjectURL=function(e){return!!a&&a.createObjectURL(e)},t.revokeObjectURL=function(e){return!!a&&a.revokeObjectURL(e)},t.readFile=function(t,i,a){if(e.FileReader){var o=new FileReader;if(o.onload=o.onerror=i,a=a||"readAsDataURL",o[a])return o[a](t),o}return!1},"function"==typeof define&&define.amd?define(function(){return t}):"object"==typeof module&&module.exports?module.exports=t:e.loadImage=t}("undefined"!=typeof window&&window||this),function(e){"use strict";"function"==typeof define&&define.amd?define(["./load-image"],e):e("object"==typeof module&&module.exports?require("./load-image"):window.loadImage)}(function(e){"use strict";var t=e.transform;e.transform=function(i,a,o,n,r){t.call(e,e.scale(i,a,r),a,o,n,r)},e.transformCoordinates=function(){},e.getTransformedOptions=function(e,t){var i,a,o,n,r=t.aspectRatio;if(!r)return t;i={};for(a in t)t.hasOwnProperty(a)&&(i[a]=t[a]);return i.crop=!0,o=e.naturalWidth||e.width,n=e.naturalHeight||e.height,o/n>r?(i.maxWidth=n*r,i.maxHeight=n):(i.maxWidth=o,i.maxHeight=o/r),i},e.renderImageToCanvas=function(e,t,i,a,o,n,r,s,l,d){return e.getContext("2d").drawImage(t,i,a,o,n,r,s,l,d),e},e.hasCanvasOption=function(e){return e.canvas||e.crop||!!e.aspectRatio},e.scale=function(t,i,a){function o(){var e=Math.max((l||v)/v,(d||P)/P);e>1&&(v*=e,P*=e)}function n(){var e=Math.min((r||v)/v,(s||P)/P);e<1&&(v*=e,P*=e)}i=i||{};var r,s,l,d,c,u,f,g,h,m,p,S=document.createElement("canvas"),b=t.getContext||e.hasCanvasOption(i)&&S.getContext,y=t.naturalWidth||t.width,x=t.naturalHeight||t.height,v=y,P=x;if(b&&(f=(i=e.getTransformedOptions(t,i,a)).left||0,g=i.top||0,i.sourceWidth?(c=i.sourceWidth,void 0!==i.right&&void 0===i.left&&(f=y-c-i.right)):c=y-f-(i.right||0),i.sourceHeight?(u=i.sourceHeight,void 0!==i.bottom&&void 0===i.top&&(g=x-u-i.bottom)):u=x-g-(i.bottom||0),v=c,P=u),r=i.maxWidth,s=i.maxHeight,l=i.minWidth,d=i.minHeight,b&&r&&s&&i.crop?(v=r,P=s,(p=c/u-r/s)<0?(u=s*c/r,void 0===i.top&&void 0===i.bottom&&(g=(x-u)/2)):p>0&&(c=r*u/s,void 0===i.left&&void 0===i.right&&(f=(y-c)/2))):((i.contain||i.cover)&&(l=r=r||l,d=s=s||d),i.cover?(n(),o()):(o(),n())),b){if((h=i.pixelRatio)>1&&(S.style.width=v+"px",S.style.height=P+"px",v*=h,P*=h,S.getContext("2d").scale(h,h)),(m=i.downsamplingRatio)>0&&m<1&&vv;)S.width=c*m,S.height=u*m,e.renderImageToCanvas(S,t,f,g,c,u,0,0,S.width,S.height),f=0,g=0,c=S.width,u=S.height,(t=document.createElement("canvas")).width=c,t.height=u,e.renderImageToCanvas(t,S,0,0,c,u,0,0,c,u);return S.width=v,S.height=P,e.transformCoordinates(S,i),e.renderImageToCanvas(S,t,f,g,c,u,0,0,v,P)}return t.width=v,t.height=P,t}}),function(e){"use strict";"function"==typeof define&&define.amd?define(["./load-image"],e):e("object"==typeof module&&module.exports?require("./load-image"):window.loadImage)}(function(e){"use strict";var t="undefined"!=typeof Blob&&(Blob.prototype.slice||Blob.prototype.webkitSlice||Blob.prototype.mozSlice);e.blobSlice=t&&function(){return(this.slice||this.webkitSlice||this.mozSlice).apply(this,arguments)},e.metaDataParsers={jpeg:{65505:[]}},e.parseMetaData=function(t,i,a,o){a=a||{},o=o||{};var n=this,r=a.maxMetaDataSize||262144;!!("undefined"!=typeof DataView&&t&&t.size>=12&&"image/jpeg"===t.type&&e.blobSlice)&&e.readFile(e.blobSlice.call(t,0,r),function(t){if(t.target.error)return console.log(t.target.error),void i(o);var r,s,l,d,c=t.target.result,u=new DataView(c),f=2,g=u.byteLength-4,h=f;if(65496===u.getUint16(0)){for(;f=65504&&r<=65519||65534===r);){if(s=u.getUint16(f+2)+2,f+s>u.byteLength){console.log("Invalid meta data: Invalid segment size.");break}if(l=e.metaDataParsers.jpeg[r])for(d=0;d6&&(c.slice?o.imageHead=c.slice(0,h):o.imageHead=new Uint8Array(c).subarray(0,h))}else console.log("Invalid JPEG file: Missing JPEG marker.");i(o)},"readAsArrayBuffer")||i(o)},e.hasMetaOption=function(e){return e&&e.meta};var i=e.transform;e.transform=function(t,a,o,n,r){e.hasMetaOption(a)?e.parseMetaData(n,function(r){i.call(e,t,a,o,n,r)},a,r):i.apply(e,arguments)}}),function(e){"use strict";"function"==typeof define&&define.amd?define(["./load-image","./load-image-meta"],e):"object"==typeof module&&module.exports?e(require("./load-image"),require("./load-image-meta")):e(window.loadImage)}(function(e){"use strict";"undefined"!=typeof fetch&&"undefined"!=typeof Request&&(e.fetchBlob=function(t,i,a){if(e.hasMetaOption(a))return fetch(new Request(t,a)).then(function(e){return e.blob()}).then(i).catch(function(e){console.log(e),i()});i()})}),function(e){"use strict";"function"==typeof define&&define.amd?define(["./load-image","./load-image-meta"],e):"object"==typeof module&&module.exports?e(require("./load-image"),require("./load-image-meta")):e(window.loadImage)}(function(e){"use strict";e.ExifMap=function(){return this},e.ExifMap.prototype.map={Orientation:274},e.ExifMap.prototype.get=function(e){return this[e]||this[this.map[e]]},e.getExifThumbnail=function(t,i,a){if(a&&!(i+a>t.byteLength))return e.createObjectURL(new Blob([t.buffer.slice(i,i+a)]));console.log("Invalid Exif data: Invalid thumbnail data.")},e.exifTagTypes={1:{getValue:function(e,t){return e.getUint8(t)},size:1},2:{getValue:function(e,t){return String.fromCharCode(e.getUint8(t))},size:1,ascii:!0},3:{getValue:function(e,t,i){return e.getUint16(t,i)},size:2},4:{getValue:function(e,t,i){return e.getUint32(t,i)},size:4},5:{getValue:function(e,t,i){return e.getUint32(t,i)/e.getUint32(t+4,i)},size:8},9:{getValue:function(e,t,i){return e.getInt32(t,i)},size:4},10:{getValue:function(e,t,i){return e.getInt32(t,i)/e.getInt32(t+4,i)},size:8}},e.exifTagTypes[7]=e.exifTagTypes[1],e.getExifValue=function(t,i,a,o,n,r){var s,l,d,c,u,f,g=e.exifTagTypes[o];if(g){if(s=g.size*n,!((l=s>4?i+t.getUint32(a+8,r):a+8)+s>t.byteLength)){if(1===n)return g.getValue(t,l,r);for(d=[],c=0;ce.byteLength)console.log("Invalid Exif data: Invalid directory offset.");else{if(n=e.getUint16(i,a),!((r=i+2+12*n)+4>e.byteLength)){for(s=0;st.byteLength)console.log("Invalid Exif data: Invalid segment size.");else if(0===t.getUint16(i+8)){switch(t.getUint16(d)){case 18761:r=!0;break;case 19789:r=!1;break;default:return void console.log("Invalid Exif data: Invalid byte alignment marker.")}42===t.getUint16(d+2,r)?(s=t.getUint32(d+4,r),o.exif=new e.ExifMap,(s=e.parseExifTags(t,d,d+s,r,o))&&!n.disableExifThumbnail&&(l={exif:{}},s=e.parseExifTags(t,d,d+s,r,l),l.exif[513]&&(o.exif.Thumbnail=e.getExifThumbnail(t,d+l.exif[513],l.exif[514]))),o.exif[34665]&&!n.disableExifSub&&e.parseExifTags(t,d,d+o.exif[34665],r,o),o.exif[34853]&&!n.disableExifGps&&e.parseExifTags(t,d,d+o.exif[34853],r,o)):console.log("Invalid Exif data: Missing TIFF marker.")}else console.log("Invalid Exif data: Missing byte alignment offset.")}},e.metaDataParsers.jpeg[65505].push(e.parseExifData)}),function(e){"use strict";"function"==typeof define&&define.amd?define(["./load-image","./load-image-exif"],e):"object"==typeof module&&module.exports?e(require("./load-image"),require("./load-image-exif")):e(window.loadImage)}(function(e){"use strict";e.ExifMap.prototype.tags={256:"ImageWidth",257:"ImageHeight",34665:"ExifIFDPointer",34853:"GPSInfoIFDPointer",40965:"InteroperabilityIFDPointer",258:"BitsPerSample",259:"Compression",262:"PhotometricInterpretation",274:"Orientation",277:"SamplesPerPixel",284:"PlanarConfiguration",530:"YCbCrSubSampling",531:"YCbCrPositioning",282:"XResolution",283:"YResolution",296:"ResolutionUnit",273:"StripOffsets",278:"RowsPerStrip",279:"StripByteCounts",513:"JPEGInterchangeFormat",514:"JPEGInterchangeFormatLength",301:"TransferFunction",318:"WhitePoint",319:"PrimaryChromaticities",529:"YCbCrCoefficients",532:"ReferenceBlackWhite",306:"DateTime",270:"ImageDescription",271:"Make",272:"Model",305:"Software",315:"Artist",33432:"Copyright",36864:"ExifVersion",40960:"FlashpixVersion",40961:"ColorSpace",40962:"PixelXDimension",40963:"PixelYDimension",42240:"Gamma",37121:"ComponentsConfiguration",37122:"CompressedBitsPerPixel",37500:"MakerNote",37510:"UserComment",40964:"RelatedSoundFile",36867:"DateTimeOriginal",36868:"DateTimeDigitized",37520:"SubSecTime",37521:"SubSecTimeOriginal",37522:"SubSecTimeDigitized",33434:"ExposureTime",33437:"FNumber",34850:"ExposureProgram",34852:"SpectralSensitivity",34855:"PhotographicSensitivity",34856:"OECF",34864:"SensitivityType",34865:"StandardOutputSensitivity",34866:"RecommendedExposureIndex",34867:"ISOSpeed",34868:"ISOSpeedLatitudeyyy",34869:"ISOSpeedLatitudezzz",37377:"ShutterSpeedValue",37378:"ApertureValue",37379:"BrightnessValue",37380:"ExposureBias",37381:"MaxApertureValue",37382:"SubjectDistance",37383:"MeteringMode",37384:"LightSource",37385:"Flash",37396:"SubjectArea",37386:"FocalLength",41483:"FlashEnergy",41484:"SpatialFrequencyResponse",41486:"FocalPlaneXResolution",41487:"FocalPlaneYResolution",41488:"FocalPlaneResolutionUnit",41492:"SubjectLocation",41493:"ExposureIndex",41495:"SensingMethod",41728:"FileSource",41729:"SceneType",41730:"CFAPattern",41985:"CustomRendered",41986:"ExposureMode",41987:"WhiteBalance",41988:"DigitalZoomRatio",41989:"FocalLengthIn35mmFilm",41990:"SceneCaptureType",41991:"GainControl",41992:"Contrast",41993:"Saturation",41994:"Sharpness",41995:"DeviceSettingDescription",41996:"SubjectDistanceRange",42016:"ImageUniqueID",42032:"CameraOwnerName",42033:"BodySerialNumber",42034:"LensSpecification",42035:"LensMake",42036:"LensModel",42037:"LensSerialNumber",0:"GPSVersionID",1:"GPSLatitudeRef",2:"GPSLatitude",3:"GPSLongitudeRef",4:"GPSLongitude",5:"GPSAltitudeRef",6:"GPSAltitude",7:"GPSTimeStamp",8:"GPSSatellites",9:"GPSStatus",10:"GPSMeasureMode",11:"GPSDOP",12:"GPSSpeedRef",13:"GPSSpeed",14:"GPSTrackRef",15:"GPSTrack",16:"GPSImgDirectionRef",17:"GPSImgDirection",18:"GPSMapDatum",19:"GPSDestLatitudeRef",20:"GPSDestLatitude",21:"GPSDestLongitudeRef",22:"GPSDestLongitude",23:"GPSDestBearingRef",24:"GPSDestBearing",25:"GPSDestDistanceRef",26:"GPSDestDistance",27:"GPSProcessingMethod",28:"GPSAreaInformation",29:"GPSDateStamp",30:"GPSDifferential",31:"GPSHPositioningError"},e.ExifMap.prototype.stringValues={ExposureProgram:{0:"Undefined",1:"Manual",2:"Normal program",3:"Aperture priority",4:"Shutter priority",5:"Creative program",6:"Action program",7:"Portrait mode",8:"Landscape mode"},MeteringMode:{0:"Unknown",1:"Average",2:"CenterWeightedAverage",3:"Spot",4:"MultiSpot",5:"Pattern",6:"Partial",255:"Other"},LightSource:{0:"Unknown",1:"Daylight",2:"Fluorescent",3:"Tungsten (incandescent light)",4:"Flash",9:"Fine weather",10:"Cloudy weather",11:"Shade",12:"Daylight fluorescent (D 5700 - 7100K)",13:"Day white fluorescent (N 4600 - 5400K)",14:"Cool white fluorescent (W 3900 - 4500K)",15:"White fluorescent (WW 3200 - 3700K)",17:"Standard light A",18:"Standard light B",19:"Standard light C",20:"D55",21:"D65",22:"D75",23:"D50",24:"ISO studio tungsten",255:"Other"},Flash:{0:"Flash did not fire",1:"Flash fired",5:"Strobe return light not detected",7:"Strobe return light detected",9:"Flash fired, compulsory flash mode",13:"Flash fired, compulsory flash mode, return light not detected",15:"Flash fired, compulsory flash mode, return light detected",16:"Flash did not fire, compulsory flash mode",24:"Flash did not fire, auto mode",25:"Flash fired, auto mode",29:"Flash fired, auto mode, return light not detected",31:"Flash fired, auto mode, return light detected",32:"No flash function",65:"Flash fired, red-eye reduction mode",69:"Flash fired, red-eye reduction mode, return light not detected",71:"Flash fired, red-eye reduction mode, return light detected",73:"Flash fired, compulsory flash mode, red-eye reduction mode",77:"Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected",79:"Flash fired, compulsory flash mode, red-eye reduction mode, return light detected",89:"Flash fired, auto mode, red-eye reduction mode",93:"Flash fired, auto mode, return light not detected, red-eye reduction mode",95:"Flash fired, auto mode, return light detected, red-eye reduction mode"},SensingMethod:{1:"Undefined",2:"One-chip color area sensor",3:"Two-chip color area sensor",4:"Three-chip color area sensor",5:"Color sequential area sensor",7:"Trilinear sensor",8:"Color sequential linear sensor"},SceneCaptureType:{0:"Standard",1:"Landscape",2:"Portrait",3:"Night scene"},SceneType:{1:"Directly photographed"},CustomRendered:{0:"Normal process",1:"Custom process"},WhiteBalance:{0:"Auto white balance",1:"Manual white balance"},GainControl:{0:"None",1:"Low gain up",2:"High gain up",3:"Low gain down",4:"High gain down"},Contrast:{0:"Normal",1:"Soft",2:"Hard"},Saturation:{0:"Normal",1:"Low saturation",2:"High saturation"},Sharpness:{0:"Normal",1:"Soft",2:"Hard"},SubjectDistanceRange:{0:"Unknown",1:"Macro",2:"Close view",3:"Distant view"},FileSource:{3:"DSC"},ComponentsConfiguration:{0:"",1:"Y",2:"Cb",3:"Cr",4:"R",5:"G",6:"B"},Orientation:{1:"top-left",2:"top-right",3:"bottom-right",4:"bottom-left",5:"left-top",6:"right-top",7:"right-bottom",8:"left-bottom"}},e.ExifMap.prototype.getText=function(e){var t=this.get(e);switch(e){case"LightSource":case"Flash":case"MeteringMode":case"ExposureProgram":case"SensingMethod":case"SceneCaptureType":case"SceneType":case"CustomRendered":case"WhiteBalance":case"GainControl":case"Contrast":case"Saturation":case"Sharpness":case"SubjectDistanceRange":case"FileSource":case"Orientation":return this.stringValues[e][t];case"ExifVersion":case"FlashpixVersion":if(!t)return;return String.fromCharCode(t[0],t[1],t[2],t[3]);case"ComponentsConfiguration":if(!t)return;return this.stringValues[e][t[0]]+this.stringValues[e][t[1]]+this.stringValues[e][t[2]]+this.stringValues[e][t[3]];case"GPSVersionID":if(!t)return;return t[0]+"."+t[1]+"."+t[2]+"."+t[3]}return String(t)},function(e){var t,i=e.tags,a=e.map;for(t in i)i.hasOwnProperty(t)&&(a[i[t]]=t)}(e.ExifMap.prototype),e.ExifMap.prototype.getAll=function(){var e,t,i={};for(e in this)this.hasOwnProperty(e)&&(t=this.tags[e])&&(i[t]=this.getText(t));return i}}),function(e){"use strict";"function"==typeof define&&define.amd?define(["./load-image","./load-image-scale","./load-image-meta"],e):"object"==typeof module&&module.exports?e(require("./load-image"),require("./load-image-scale"),require("./load-image-meta")):e(window.loadImage)}(function(e){"use strict";var t=e.hasCanvasOption,i=e.hasMetaOption,a=e.transformCoordinates,o=e.getTransformedOptions;e.hasCanvasOption=function(i){return!!i.orientation||t.call(e,i)},e.hasMetaOption=function(t){return t&&!0===t.orientation||i.call(e,t)},e.transformCoordinates=function(t,i){a.call(e,t,i);var o=t.getContext("2d"),n=t.width,r=t.height,s=t.style.width,l=t.style.height,d=i.orientation;if(d&&!(d>8))switch(d>4&&(t.width=r,t.height=n,t.style.width=l,t.style.height=s),d){case 2:o.translate(n,0),o.scale(-1,1);break;case 3:o.translate(n,r),o.rotate(Math.PI);break;case 4:o.translate(0,r),o.scale(1,-1);break;case 5:o.rotate(.5*Math.PI),o.scale(1,-1);break;case 6:o.rotate(.5*Math.PI),o.translate(0,-r);break;case 7:o.rotate(.5*Math.PI),o.translate(n,-r),o.scale(-1,1);break;case 8:o.rotate(-.5*Math.PI),o.translate(-n,0)}},e.getTransformedOptions=function(t,i,a){var n,r,s=o.call(e,t,i),l=s.orientation;if(!0===l&&a&&a.exif&&(l=a.exif.get("Orientation")),!l||l>8||1===l)return s;n={};for(r in s)s.hasOwnProperty(r)&&(n[r]=s[r]);switch(n.orientation=l,l){case 2:n.left=s.right,n.right=s.left;break;case 3:n.left=s.right,n.top=s.bottom,n.right=s.left,n.bottom=s.top;break;case 4:n.top=s.bottom,n.bottom=s.top;break;case 5:n.left=s.top,n.top=s.left,n.right=s.bottom,n.bottom=s.right;break;case 6:n.left=s.top,n.top=s.right,n.right=s.bottom,n.bottom=s.left;break;case 7:n.left=s.bottom,n.top=s.right,n.right=s.top,n.bottom=s.left;break;case 8:n.left=s.bottom,n.top=s.left,n.right=s.top,n.bottom=s.right}return n.orientation>4&&(n.maxWidth=s.maxHeight,n.maxHeight=s.maxWidth,n.minWidth=s.minHeight,n.minHeight=s.minWidth,n.sourceWidth=s.sourceHeight,n.sourceHeight=s.sourceWidth),n}});
+!function(c){"use strict";var t=c.URL||c.webkitURL;function f(e){return!!t&&t.createObjectURL(e)}function i(e){return!!t&&t.revokeObjectURL(e)}function u(e,t){!e||"blob:"!==e.slice(0,5)||t&&t.noRevoke||i(e)}function d(e,t,i,a){if(!c.FileReader)return!1;var n=new FileReader;n.onload=function(){t.call(n,this.result)},i&&(n.onabort=n.onerror=function(){i.call(n,this.error)});a=n[a||"readAsDataURL"];return a?(a.call(n,e),n):void 0}function g(e,t){return Object.prototype.toString.call(t)==="[object "+e+"]"}function m(s,e,l){function t(i,a){var n,r=document.createElement("img");function o(e,t){i!==a?e instanceof Error?a(e):((t=t||{}).image=e,i(t)):i&&i(e,t)}function e(e,t){t&&c.console&&console.log(t),e&&g("Blob",e)?n=f(s=e):(n=s,l&&l.crossOrigin&&(r.crossOrigin=l.crossOrigin)),r.src=n}return r.onerror=function(e){u(n,l),a&&a.call(r,e)},r.onload=function(){u(n,l);var e={originalWidth:r.naturalWidth||r.width,originalHeight:r.naturalHeight||r.height};try{m.transform(r,l,o,s,e)}catch(t){a&&a(t)}},"string"==typeof s?(m.requiresMetaData(l)?m.fetchBlob(s,e,l):e(),r):g("Blob",s)||g("File",s)?(n=f(s))?(r.src=n,r):d(s,function(e){r.src=e},a):void 0}return c.Promise&&"function"!=typeof e?(l=e,new Promise(t)):t(e,e)}m.requiresMetaData=function(e){return e&&e.meta},m.fetchBlob=function(e,t){t()},m.transform=function(e,t,i,a,n){i(e,n)},m.global=c,m.readFile=d,m.isInstanceOf=g,m.createObjectURL=f,m.revokeObjectURL=i,"function"==typeof define&&define.amd?define(function(){return m}):"object"==typeof module&&module.exports?module.exports=m:c.loadImage=m}("undefined"!=typeof window&&window||this),function(e){"use strict";"function"==typeof define&&define.amd?define(["./load-image"],e):"object"==typeof module&&module.exports?e(require("./load-image")):e(window.loadImage)}(function(E){"use strict";var r=E.transform;E.createCanvas=function(e,t,i){if(i&&E.global.OffscreenCanvas)return new OffscreenCanvas(e,t);i=document.createElement("canvas");return i.width=e,i.height=t,i},E.transform=function(e,t,i,a,n){r.call(E,E.scale(e,t,n),t,i,a,n)},E.transformCoordinates=function(){},E.getTransformedOptions=function(e,t){var i,a,n,r=t.aspectRatio;if(!r)return t;for(a in i={},t)Object.prototype.hasOwnProperty.call(t,a)&&(i[a]=t[a]);return i.crop=!0,r<(n=e.naturalWidth||e.width)/(e=e.naturalHeight||e.height)?(i.maxWidth=e*r,i.maxHeight=e):(i.maxWidth=n,i.maxHeight=n/r),i},E.drawImage=function(e,t,i,a,n,r,o,s,l){t=t.getContext("2d");return!1===l.imageSmoothingEnabled?(t.msImageSmoothingEnabled=!1,t.imageSmoothingEnabled=!1):l.imageSmoothingQuality&&(t.imageSmoothingQuality=l.imageSmoothingQuality),t.drawImage(e,i,a,n,r,0,0,o,s),t},E.requiresCanvas=function(e){return e.canvas||e.crop||!!e.aspectRatio},E.scale=function(e,t,i){t=t||{},i=i||{};var a,n,r,o,s,l,c,f,u,d,g,m=e.getContext||E.requiresCanvas(t)&&!!E.global.HTMLCanvasElement,h=e.naturalWidth||e.width,p=e.naturalHeight||e.height,A=h,b=p;function y(){var e=Math.max((r||A)/A,(o||b)/b);1t.byteLength){console.log("Invalid JPEG metadata: Invalid segment size.");break}if((n=h.jpeg[i])&&!u.disableMetaDataParsers)for(r=0;re.byteLength)console.log("Invalid Exif data: Invalid directory offset.");else{if(!((c=i+2+12*(l=e.getUint16(i,a)))+4>e.byteLength)){for(f=0;fe.byteLength)){if(1===n)return u.getValue(e,o,r);for(s=[],l=0;ll.byteLength)console.log("Invalid Exif data: Invalid segment size.");else if(0===l.getUint16(e+8)){switch(l.getUint16(g)){case 18761:f=!0;break;case 19789:f=!1;break;default:return void console.log("Invalid Exif data: Invalid byte alignment marker.")}42===l.getUint16(g+2,f)?(e=l.getUint32(g+4,f),c.exif=new m,i.disableExifOffsets||(c.exifOffsets=new m,c.exifTiffOffset=g,c.exifLittleEndian=f),(e=A(l,g,g+e,f,c.exif,c.exifOffsets,u,d))&&p(u,d,"ifd1")&&(c.exif.ifd1=e,c.exifOffsets&&(c.exifOffsets.ifd1=g+e)),Object.keys(c.exif.ifds).forEach(function(e){var t,i,a,n,r,o,s;i=e,a=l,n=g,r=f,o=u,s=d,(e=(t=c).exif[i])&&(t.exif[i]=new m(i),t.exifOffsets&&(t.exifOffsets[i]=new m(i)),A(a,n,n+e,r,t.exif[i],t.exifOffsets&&t.exifOffsets[i],o&&o[i],s&&s[i]))}),(e=c.exif.ifd1)&&e[513]&&(e[513]=function(e,t,i){if(i){if(!(t+i>e.byteLength))return new Blob([n.bufferSlice.call(e.buffer,t,t+i)],{type:"image/jpeg"});console.log("Invalid Exif data: Invalid thumbnail data.")}}(l,g+e[513],e[514]))):console.log("Invalid Exif data: Missing TIFF marker.")}else console.log("Invalid Exif data: Missing byte alignment offset.")}},n.metaDataParsers.jpeg[65505].push(n.parseExifData),n.exifWriters={274:function(e,t,i){var a=t.exifOffsets[274];return a&&new DataView(e,a+8,2).setUint16(0,i,t.exifLittleEndian),e}},n.writeExifData=function(e,t,i,a){return n.exifWriters[t.exif.map[i]](e,t,a)},n.ExifMap=m}),function(e){"use strict";"function"==typeof define&&define.amd?define(["./load-image","./load-image-exif"],e):"object"==typeof module&&module.exports?e(require("./load-image"),require("./load-image-exif")):e(window.loadImage)}(function(e){"use strict";var n=e.ExifMap.prototype;n.tags={256:"ImageWidth",257:"ImageHeight",258:"BitsPerSample",259:"Compression",262:"PhotometricInterpretation",274:"Orientation",277:"SamplesPerPixel",284:"PlanarConfiguration",530:"YCbCrSubSampling",531:"YCbCrPositioning",282:"XResolution",283:"YResolution",296:"ResolutionUnit",273:"StripOffsets",278:"RowsPerStrip",279:"StripByteCounts",513:"JPEGInterchangeFormat",514:"JPEGInterchangeFormatLength",301:"TransferFunction",318:"WhitePoint",319:"PrimaryChromaticities",529:"YCbCrCoefficients",532:"ReferenceBlackWhite",306:"DateTime",270:"ImageDescription",271:"Make",272:"Model",305:"Software",315:"Artist",33432:"Copyright",34665:{36864:"ExifVersion",40960:"FlashpixVersion",40961:"ColorSpace",40962:"PixelXDimension",40963:"PixelYDimension",42240:"Gamma",37121:"ComponentsConfiguration",37122:"CompressedBitsPerPixel",37500:"MakerNote",37510:"UserComment",40964:"RelatedSoundFile",36867:"DateTimeOriginal",36868:"DateTimeDigitized",36880:"OffsetTime",36881:"OffsetTimeOriginal",36882:"OffsetTimeDigitized",37520:"SubSecTime",37521:"SubSecTimeOriginal",37522:"SubSecTimeDigitized",33434:"ExposureTime",33437:"FNumber",34850:"ExposureProgram",34852:"SpectralSensitivity",34855:"PhotographicSensitivity",34856:"OECF",34864:"SensitivityType",34865:"StandardOutputSensitivity",34866:"RecommendedExposureIndex",34867:"ISOSpeed",34868:"ISOSpeedLatitudeyyy",34869:"ISOSpeedLatitudezzz",37377:"ShutterSpeedValue",37378:"ApertureValue",37379:"BrightnessValue",37380:"ExposureBias",37381:"MaxApertureValue",37382:"SubjectDistance",37383:"MeteringMode",37384:"LightSource",37385:"Flash",37396:"SubjectArea",37386:"FocalLength",41483:"FlashEnergy",41484:"SpatialFrequencyResponse",41486:"FocalPlaneXResolution",41487:"FocalPlaneYResolution",41488:"FocalPlaneResolutionUnit",41492:"SubjectLocation",41493:"ExposureIndex",41495:"SensingMethod",41728:"FileSource",41729:"SceneType",41730:"CFAPattern",41985:"CustomRendered",41986:"ExposureMode",41987:"WhiteBalance",41988:"DigitalZoomRatio",41989:"FocalLengthIn35mmFilm",41990:"SceneCaptureType",41991:"GainControl",41992:"Contrast",41993:"Saturation",41994:"Sharpness",41995:"DeviceSettingDescription",41996:"SubjectDistanceRange",42016:"ImageUniqueID",42032:"CameraOwnerName",42033:"BodySerialNumber",42034:"LensSpecification",42035:"LensMake",42036:"LensModel",42037:"LensSerialNumber"},34853:{0:"GPSVersionID",1:"GPSLatitudeRef",2:"GPSLatitude",3:"GPSLongitudeRef",4:"GPSLongitude",5:"GPSAltitudeRef",6:"GPSAltitude",7:"GPSTimeStamp",8:"GPSSatellites",9:"GPSStatus",10:"GPSMeasureMode",11:"GPSDOP",12:"GPSSpeedRef",13:"GPSSpeed",14:"GPSTrackRef",15:"GPSTrack",16:"GPSImgDirectionRef",17:"GPSImgDirection",18:"GPSMapDatum",19:"GPSDestLatitudeRef",20:"GPSDestLatitude",21:"GPSDestLongitudeRef",22:"GPSDestLongitude",23:"GPSDestBearingRef",24:"GPSDestBearing",25:"GPSDestDistanceRef",26:"GPSDestDistance",27:"GPSProcessingMethod",28:"GPSAreaInformation",29:"GPSDateStamp",30:"GPSDifferential",31:"GPSHPositioningError"},40965:{1:"InteroperabilityIndex"}},n.tags.ifd1=n.tags,n.stringValues={ExposureProgram:{0:"Undefined",1:"Manual",2:"Normal program",3:"Aperture priority",4:"Shutter priority",5:"Creative program",6:"Action program",7:"Portrait mode",8:"Landscape mode"},MeteringMode:{0:"Unknown",1:"Average",2:"CenterWeightedAverage",3:"Spot",4:"MultiSpot",5:"Pattern",6:"Partial",255:"Other"},LightSource:{0:"Unknown",1:"Daylight",2:"Fluorescent",3:"Tungsten (incandescent light)",4:"Flash",9:"Fine weather",10:"Cloudy weather",11:"Shade",12:"Daylight fluorescent (D 5700 - 7100K)",13:"Day white fluorescent (N 4600 - 5400K)",14:"Cool white fluorescent (W 3900 - 4500K)",15:"White fluorescent (WW 3200 - 3700K)",17:"Standard light A",18:"Standard light B",19:"Standard light C",20:"D55",21:"D65",22:"D75",23:"D50",24:"ISO studio tungsten",255:"Other"},Flash:{0:"Flash did not fire",1:"Flash fired",5:"Strobe return light not detected",7:"Strobe return light detected",9:"Flash fired, compulsory flash mode",13:"Flash fired, compulsory flash mode, return light not detected",15:"Flash fired, compulsory flash mode, return light detected",16:"Flash did not fire, compulsory flash mode",24:"Flash did not fire, auto mode",25:"Flash fired, auto mode",29:"Flash fired, auto mode, return light not detected",31:"Flash fired, auto mode, return light detected",32:"No flash function",65:"Flash fired, red-eye reduction mode",69:"Flash fired, red-eye reduction mode, return light not detected",71:"Flash fired, red-eye reduction mode, return light detected",73:"Flash fired, compulsory flash mode, red-eye reduction mode",77:"Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected",79:"Flash fired, compulsory flash mode, red-eye reduction mode, return light detected",89:"Flash fired, auto mode, red-eye reduction mode",93:"Flash fired, auto mode, return light not detected, red-eye reduction mode",95:"Flash fired, auto mode, return light detected, red-eye reduction mode"},SensingMethod:{1:"Undefined",2:"One-chip color area sensor",3:"Two-chip color area sensor",4:"Three-chip color area sensor",5:"Color sequential area sensor",7:"Trilinear sensor",8:"Color sequential linear sensor"},SceneCaptureType:{0:"Standard",1:"Landscape",2:"Portrait",3:"Night scene"},SceneType:{1:"Directly photographed"},CustomRendered:{0:"Normal process",1:"Custom process"},WhiteBalance:{0:"Auto white balance",1:"Manual white balance"},GainControl:{0:"None",1:"Low gain up",2:"High gain up",3:"Low gain down",4:"High gain down"},Contrast:{0:"Normal",1:"Soft",2:"Hard"},Saturation:{0:"Normal",1:"Low saturation",2:"High saturation"},Sharpness:{0:"Normal",1:"Soft",2:"Hard"},SubjectDistanceRange:{0:"Unknown",1:"Macro",2:"Close view",3:"Distant view"},FileSource:{3:"DSC"},ComponentsConfiguration:{0:"",1:"Y",2:"Cb",3:"Cr",4:"R",5:"G",6:"B"},Orientation:{1:"Original",2:"Horizontal flip",3:"Rotate 180° CCW",4:"Vertical flip",5:"Vertical flip + Rotate 90° CW",6:"Rotate 90° CW",7:"Horizontal flip + Rotate 90° CW",8:"Rotate 90° CCW"}},n.getText=function(e){var t=this.get(e);switch(e){case"LightSource":case"Flash":case"MeteringMode":case"ExposureProgram":case"SensingMethod":case"SceneCaptureType":case"SceneType":case"CustomRendered":case"WhiteBalance":case"GainControl":case"Contrast":case"Saturation":case"Sharpness":case"SubjectDistanceRange":case"FileSource":case"Orientation":return this.stringValues[e][t];case"ExifVersion":case"FlashpixVersion":return t?String.fromCharCode(t[0],t[1],t[2],t[3]):void 0;case"ComponentsConfiguration":return t?this.stringValues[e][t[0]]+this.stringValues[e][t[1]]+this.stringValues[e][t[2]]+this.stringValues[e][t[3]]:void 0;case"GPSVersionID":return t?t[0]+"."+t[1]+"."+t[2]+"."+t[3]:void 0}return String(t)},n.getAll=function(){var e,t,i={};for(e in this)Object.prototype.hasOwnProperty.call(this,e)&&((t=this[e])&&t.getAll?i[this.ifds[e].name]=t.getAll():(t=this.tags[e])&&(i[t]=this.getText(t)));return i},n.getName=function(e){var t=this.tags[e];return"object"==typeof t?this.ifds[e].name:t},function(){var e,t,i,a=n.tags;for(e in a)if(Object.prototype.hasOwnProperty.call(a,e))if(t=n.ifds[e])for(e in i=a[e])Object.prototype.hasOwnProperty.call(i,e)&&(t.map[i[e]]=Number(e));else n.map[a[e]]=Number(e)}()}),function(e){"use strict";"function"==typeof define&&define.amd?define(["./load-image","./load-image-meta"],e):"object"==typeof module&&module.exports?e(require("./load-image"),require("./load-image-meta")):e(window.loadImage)}(function(e){"use strict";function l(){}function u(e,t,i,a,n){return"binary"===t.types[e]?new Blob([i.buffer.slice(a,a+n)]):"Uint16"===t.types[e]?i.getUint16(a):function(e,t,i){for(var a="",n=t+i,r=t;r} Object
+ */
+ function loadImage(file, callback, options) {
+ /**
+ * Promise executor
+ *
+ * @param {Function} resolve Resolution function
+ * @param {Function} reject Rejection function
+ * @returns {HTMLImageElement|FileReader} Object
+ */
+ function executor(resolve, reject) {
+ var img = document.createElement('img')
+ var url
+ /**
+ * Callback for the fetchBlob call.
+ *
+ * @param {HTMLImageElement|HTMLCanvasElement} img Error object
+ * @param {object} data Data object
+ * @returns {undefined} Undefined
+ */
+ function resolveWrapper(img, data) {
+ if (resolve === reject) {
+ // Not using Promises
+ if (resolve) resolve(img, data)
+ return
+ } else if (img instanceof Error) {
+ reject(img)
+ return
+ }
+ data = data || {} // eslint-disable-line no-param-reassign
+ data.image = img
+ resolve(data)
+ }
+ /**
+ * Callback for the fetchBlob call.
+ *
+ * @param {Blob} blob Blob object
+ * @param {Error} err Error object
+ */
+ function fetchBlobCallback(blob, err) {
+ if (err && $.console) console.log(err) // eslint-disable-line no-console
+ if (blob && isInstanceOf('Blob', blob)) {
+ file = blob // eslint-disable-line no-param-reassign
+ url = createObjectURL(file)
+ } else {
+ url = file
+ if (options && options.crossOrigin) {
+ img.crossOrigin = options.crossOrigin
+ }
+ }
+ img.src = url
+ }
+ img.onerror = function (event) {
+ revokeHelper(url, options)
+ if (reject) reject.call(img, event)
+ }
+ img.onload = function () {
+ revokeHelper(url, options)
+ var data = {
+ originalWidth: img.naturalWidth || img.width,
+ originalHeight: img.naturalHeight || img.height
+ }
+ try {
+ loadImage.transform(img, options, resolveWrapper, file, data)
+ } catch (error) {
+ if (reject) reject(error)
+ }
+ }
+ if (typeof file === 'string') {
+ if (loadImage.requiresMetaData(options)) {
+ loadImage.fetchBlob(file, fetchBlobCallback, options)
+ } else {
+ fetchBlobCallback()
+ }
+ return img
+ } else if (isInstanceOf('Blob', file) || isInstanceOf('File', file)) {
+ url = createObjectURL(file)
+ if (url) {
+ img.src = url
+ return img
+ }
+ return readFile(
+ file,
+ function (url) {
+ img.src = url
+ },
+ reject
+ )
+ }
}
- }
-
- loadImage.onload = function (img, event, file, callback, options) {
- revokeHelper(img, options)
- if (callback) {
- loadImage.transform(img, options, callback, file, {})
+ if ($.Promise && typeof callback !== 'function') {
+ options = callback // eslint-disable-line no-param-reassign
+ return new Promise(executor)
}
+ return executor(callback, callback)
}
- loadImage.createObjectURL = function (file) {
- return urlAPI ? urlAPI.createObjectURL(file) : false
+ // Determines if metadata should be loaded automatically.
+ // Requires the load image meta extension to load metadata.
+ loadImage.requiresMetaData = function (options) {
+ return options && options.meta
}
- loadImage.revokeObjectURL = function (url) {
- return urlAPI ? urlAPI.revokeObjectURL(url) : false
+ // If the callback given to this function returns a blob, it is used as image
+ // source instead of the original url and overrides the file argument used in
+ // the onload and onerror event callbacks:
+ loadImage.fetchBlob = function (url, callback) {
+ callback()
}
- // Loads a given File object via FileReader interface,
- // invokes the callback with the event object (load or error).
- // The result can be read via event.target.result:
- loadImage.readFile = function (file, callback, method) {
- if ($.FileReader) {
- var fileReader = new FileReader()
- fileReader.onload = fileReader.onerror = callback
- method = method || 'readAsDataURL'
- if (fileReader[method]) {
- fileReader[method](file)
- return fileReader
- }
- }
- return false
+ loadImage.transform = function (img, options, callback, file, data) {
+ callback(img, data)
}
+ loadImage.global = $
+ loadImage.readFile = readFile
+ loadImage.isInstanceOf = isInstanceOf
+ loadImage.createObjectURL = createObjectURL
+ loadImage.revokeObjectURL = revokeObjectURL
+
if (typeof define === 'function' && define.amd) {
define(function () {
return loadImage
diff --git a/test/vendor/canvas-to-blob.js b/js/vendor/canvas-to-blob.js
similarity index 63%
rename from test/vendor/canvas-to-blob.js
rename to js/vendor/canvas-to-blob.js
index 83f7905..8cd717b 100644
--- a/test/vendor/canvas-to-blob.js
+++ b/js/vendor/canvas-to-blob.js
@@ -12,33 +12,43 @@
* http://stackoverflow.com/q/4998908
*/
-/* global atob, Blob, define */
+/* global define, Uint8Array, ArrayBuffer, module */
;(function (window) {
'use strict'
- var CanvasPrototype = window.HTMLCanvasElement &&
- window.HTMLCanvasElement.prototype
- var hasBlobConstructor = window.Blob && (function () {
- try {
- return Boolean(new Blob())
- } catch (e) {
- return false
- }
- }())
- var hasArrayBufferViewSupport = hasBlobConstructor && window.Uint8Array &&
+ var CanvasPrototype =
+ window.HTMLCanvasElement && window.HTMLCanvasElement.prototype
+ var hasBlobConstructor =
+ window.Blob &&
+ (function () {
+ try {
+ return Boolean(new Blob())
+ } catch (e) {
+ return false
+ }
+ })()
+ var hasArrayBufferViewSupport =
+ hasBlobConstructor &&
+ window.Uint8Array &&
(function () {
try {
return new Blob([new Uint8Array(100)]).size === 100
} catch (e) {
return false
}
- }())
- var BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder ||
- window.MozBlobBuilder || window.MSBlobBuilder
+ })()
+ var BlobBuilder =
+ window.BlobBuilder ||
+ window.WebKitBlobBuilder ||
+ window.MozBlobBuilder ||
+ window.MSBlobBuilder
var dataURIPattern = /^data:((.*?)(;charset=.*?)?)(;base64)?,/
- var dataURLtoBlob = (hasBlobConstructor || BlobBuilder) && window.atob &&
- window.ArrayBuffer && window.Uint8Array &&
+ var dataURLtoBlob =
+ (hasBlobConstructor || BlobBuilder) &&
+ window.atob &&
+ window.ArrayBuffer &&
+ window.Uint8Array &&
function (dataURI) {
var matches,
mediaType,
@@ -75,10 +85,9 @@
}
// Write the ArrayBuffer (or ArrayBufferView) to a blob:
if (hasBlobConstructor) {
- return new Blob(
- [hasArrayBufferViewSupport ? intArray : arrayBuffer],
- {type: mediaType}
- )
+ return new Blob([hasArrayBufferViewSupport ? intArray : arrayBuffer], {
+ type: mediaType
+ })
}
bb = new BlobBuilder()
bb.append(arrayBuffer)
@@ -97,11 +106,28 @@
})
}
} else if (CanvasPrototype.toDataURL && dataURLtoBlob) {
- CanvasPrototype.toBlob = function (callback, type, quality) {
- var self = this
- setTimeout(function () {
- callback(dataURLtoBlob(self.toDataURL(type, quality)))
- })
+ if (CanvasPrototype.msToBlob) {
+ CanvasPrototype.toBlob = function (callback, type, quality) {
+ var self = this
+ setTimeout(function () {
+ if (
+ ((type && type !== 'image/png') || quality) &&
+ CanvasPrototype.toDataURL &&
+ dataURLtoBlob
+ ) {
+ callback(dataURLtoBlob(self.toDataURL(type, quality)))
+ } else {
+ callback(self.msToBlob(type))
+ }
+ })
+ }
+ } else {
+ CanvasPrototype.toBlob = function (callback, type, quality) {
+ var self = this
+ setTimeout(function () {
+ callback(dataURLtoBlob(self.toDataURL(type, quality)))
+ })
+ }
}
}
}
@@ -114,4 +140,4 @@
} else {
window.dataURLtoBlob = dataURLtoBlob
}
-}(window))
+})(window)
diff --git a/js/vendor/jquery.Jcrop.js b/js/vendor/jquery.Jcrop.js
index 3e32f04..a879391 100755
--- a/js/vendor/jquery.Jcrop.js
+++ b/js/vendor/jquery.Jcrop.js
@@ -1,9 +1,9 @@
/**
- * jquery.Jcrop.js v0.9.12
+ * jquery.Jcrop.js v0.9.15
* jQuery Image Cropping Plugin - released under MIT License
* Author: Kelly Hallman
* http://github.com/tapmodo/Jcrop
- * Copyright (c) 2008-2013 Tapmodo Interactive LLC {{{
+ * Copyright (c) 2008-2018 Tapmodo Interactive LLC {{{
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -228,10 +228,10 @@
function newSelection(e) //{{{
{
if (options.disabled) {
- return false;
+ return;
}
if (!options.allowSelect) {
- return false;
+ return;
}
btndown = true;
docOffset = getPos($img);
@@ -329,7 +329,7 @@
boundy = $img.height(),
- $div = $('
').width(boundx).height(boundy).addClass(cssClass('holder')).css({
+ $div = $('
').width(boundx).height(boundy).addClass(cssClass('holder')).css({
position: 'relative',
backgroundColor: options.bgColor
}).insertAfter($origimg).append($img);
@@ -338,19 +338,19 @@
$div.addClass(options.addClass);
}
- var $img2 = $('
'),
+ var $img2 = $('
'),
- $img_holder = $('
')
+ $img_holder = $('
')
.width('100%').height('100%').css({
zIndex: 310,
position: 'absolute',
overflow: 'hidden'
}),
- $hdl_holder = $('
')
+ $hdl_holder = $('
')
.width('100%').height('100%').css('zIndex', 320),
- $sel = $('
')
+ $sel = $('
')
.css({
position: 'absolute',
zIndex: 600
@@ -737,7 +737,7 @@
// Shade Module {{{
var Shade = (function() {
var enabled = false,
- holder = $('
').css({
+ holder = $('
').css({
position: 'absolute',
zIndex: 240,
opacity: 0
@@ -779,7 +779,7 @@
});
}
function createShade() {
- return $('
').css({
+ return $('
').css({
position: 'absolute',
backgroundColor: options.shadeColor||options.bgColor
}).appendTo(holder);
@@ -863,7 +863,7 @@
// Private Methods
function insertBorder(type) //{{{
{
- var jq = $('
').css({
+ var jq = $('
').css({
position: 'absolute',
opacity: options.borderOpacity
}).addClass(cssClass(type));
@@ -873,7 +873,7 @@
//}}}
function dragDiv(ord, zi) //{{{
{
- var jq = $('
').mousedown(createDragger(ord)).css({
+ var jq = $('
').mousedown(createDragger(ord)).css({
cursor: ord + '-resize',
position: 'absolute',
zIndex: zi
@@ -1226,7 +1226,7 @@
width: '12px'
}).addClass('jcrop-keymgr'),
- $keywrap = $('
').css({
+ $keywrap = $('
').css({
position: 'absolute',
overflow: 'hidden'
}).append($keymgr);
diff --git a/js/vendor/jquery.js b/js/vendor/jquery.js
index 9b5206b..7fc60fc 100644
--- a/js/vendor/jquery.js
+++ b/js/vendor/jquery.js
@@ -1,22 +1,20 @@
/*!
- * jQuery JavaScript Library v3.3.1
- * https://jquery.com/
+ * jQuery JavaScript Library v1.12.4
+ * http://jquery.com/
*
* Includes Sizzle.js
- * https://sizzlejs.com/
+ * http://sizzlejs.com/
*
- * Copyright JS Foundation and other contributors
+ * Copyright jQuery Foundation and other contributors
* Released under the MIT license
- * https://jquery.org/license
+ * http://jquery.org/license
*
- * Date: 2018-01-20T17:24Z
+ * Date: 2016-05-20T17:17Z
*/
-( function( global, factory ) {
- "use strict";
+(function( global, factory ) {
if ( typeof module === "object" && typeof module.exports === "object" ) {
-
// For CommonJS and CommonJS-like environments where a proper `window`
// is present, execute the factory and get jQuery.
// For environments that do not have a `window` with a `document`
@@ -37,27 +35,24 @@
}
// Pass this if window is not defined yet
-} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
-
-// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1
-// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode
-// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common
-// enough that all such attempts are guarded in a try block.
-"use strict";
+}(typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
-var arr = [];
+// Support: Firefox 18+
+// Can't be in strict mode, several libs including ASP.NET trace
+// the stack via arguments.caller.callee and Firefox dies if
+// you try to trace through "use strict" call chains. (#13335)
+//"use strict";
+var deletedIds = [];
var document = window.document;
-var getProto = Object.getPrototypeOf;
+var slice = deletedIds.slice;
-var slice = arr.slice;
+var concat = deletedIds.concat;
-var concat = arr.concat;
+var push = deletedIds.push;
-var push = arr.push;
-
-var indexOf = arr.indexOf;
+var indexOf = deletedIds.indexOf;
var class2type = {};
@@ -65,71 +60,12 @@ var toString = class2type.toString;
var hasOwn = class2type.hasOwnProperty;
-var fnToString = hasOwn.toString;
-
-var ObjectFunctionString = fnToString.call( Object );
-
var support = {};
-var isFunction = function isFunction( obj ) {
-
- // Support: Chrome <=57, Firefox <=52
- // In some browsers, typeof returns "function" for HTML elements
- // (i.e., `typeof document.createElement( "object" ) === "function"`).
- // We don't want to classify *any* DOM node as a function.
- return typeof obj === "function" && typeof obj.nodeType !== "number";
- };
-
-
-var isWindow = function isWindow( obj ) {
- return obj != null && obj === obj.window;
- };
-
-
-
-
- var preservedScriptAttributes = {
- type: true,
- src: true,
- noModule: true
- };
-
- function DOMEval( code, doc, node ) {
- doc = doc || document;
-
- var i,
- script = doc.createElement( "script" );
-
- script.text = code;
- if ( node ) {
- for ( i in preservedScriptAttributes ) {
- if ( node[ i ] ) {
- script[ i ] = node[ i ];
- }
- }
- }
- doc.head.appendChild( script ).parentNode.removeChild( script );
- }
-
-
-function toType( obj ) {
- if ( obj == null ) {
- return obj + "";
- }
-
- // Support: Android <=2.3 only (functionish RegExp)
- return typeof obj === "object" || typeof obj === "function" ?
- class2type[ toString.call( obj ) ] || "object" :
- typeof obj;
-}
-/* global Symbol */
-// Defining this global in .eslintrc.json would create a danger of using the global
-// unguarded in another place, it seems safer to define global only for this module
-
var
- version = "3.3.1",
+ version = "1.12.4",
// Define a local copy of jQuery
jQuery = function( selector, context ) {
@@ -139,9 +75,18 @@ var
return new jQuery.fn.init( selector, context );
},
- // Support: Android <=4.0 only
+ // Support: Android<4.1, IE<9
// Make sure we trim BOM and NBSP
- rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;
+ rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,
+
+ // Matches dashed string for camelizing
+ rmsPrefix = /^-ms-/,
+ rdashAlpha = /-([\da-z])/gi,
+
+ // Used by jQuery.camelCase as callback to replace()
+ fcamelCase = function( all, letter ) {
+ return letter.toUpperCase();
+ };
jQuery.fn = jQuery.prototype = {
@@ -150,6 +95,9 @@ jQuery.fn = jQuery.prototype = {
constructor: jQuery,
+ // Start with an empty selector
+ selector: "",
+
// The default length of a jQuery object is 0
length: 0,
@@ -160,14 +108,13 @@ jQuery.fn = jQuery.prototype = {
// Get the Nth element in the matched element set OR
// Get the whole matched element set as a clean array
get: function( num ) {
+ return num != null ?
- // Return all the elements in a clean array
- if ( num == null ) {
- return slice.call( this );
- }
+ // Return just the one element from the set
+ ( num < 0 ? this[ num + this.length ] : this[ num ] ) :
- // Return just the one element from the set
- return num < 0 ? this[ num + this.length ] : this[ num ];
+ // Return all the elements in a clean array
+ slice.call( this );
},
// Take an array of elements and push it onto the stack
@@ -179,6 +126,7 @@ jQuery.fn = jQuery.prototype = {
// Add the old object onto the stack (as a reference)
ret.prevObject = this;
+ ret.context = this.context;
// Return the newly-formed element set
return ret;
@@ -220,12 +168,12 @@ jQuery.fn = jQuery.prototype = {
// For internal use only.
// Behaves like an Array's method, not like a jQuery method.
push: push,
- sort: arr.sort,
- splice: arr.splice
+ sort: deletedIds.sort,
+ splice: deletedIds.splice
};
jQuery.extend = jQuery.fn.extend = function() {
- var options, name, src, copy, copyIsArray, clone,
+ var src, copyIsArray, copy, name, options, clone,
target = arguments[ 0 ] || {},
i = 1,
length = arguments.length,
@@ -235,17 +183,17 @@ jQuery.extend = jQuery.fn.extend = function() {
if ( typeof target === "boolean" ) {
deep = target;
- // Skip the boolean and the target
+ // skip the boolean and the target
target = arguments[ i ] || {};
i++;
}
// Handle case when target is a string or something (possible in deep copy)
- if ( typeof target !== "object" && !isFunction( target ) ) {
+ if ( typeof target !== "object" && !jQuery.isFunction( target ) ) {
target = {};
}
- // Extend jQuery itself if only one argument is passed
+ // extend jQuery itself if only one argument is passed
if ( i === length ) {
target = this;
i--;
@@ -268,11 +216,11 @@ jQuery.extend = jQuery.fn.extend = function() {
// Recurse if we're merging plain objects or arrays
if ( deep && copy && ( jQuery.isPlainObject( copy ) ||
- ( copyIsArray = Array.isArray( copy ) ) ) ) {
+ ( copyIsArray = jQuery.isArray( copy ) ) ) ) {
if ( copyIsArray ) {
copyIsArray = false;
- clone = src && Array.isArray( src ) ? src : [];
+ clone = src && jQuery.isArray( src ) ? src : [];
} else {
clone = src && jQuery.isPlainObject( src ) ? src : {};
@@ -307,42 +255,110 @@ jQuery.extend( {
noop: function() {},
+ // See test/unit/core.js for details concerning isFunction.
+ // Since version 1.3, DOM methods and functions like alert
+ // aren't supported. They return false on IE (#2968).
+ isFunction: function( obj ) {
+ return jQuery.type( obj ) === "function";
+ },
+
+ isArray: Array.isArray || function( obj ) {
+ return jQuery.type( obj ) === "array";
+ },
+
+ isWindow: function( obj ) {
+ /* jshint eqeqeq: false */
+ return obj != null && obj == obj.window;
+ },
+
+ isNumeric: function( obj ) {
+
+ // parseFloat NaNs numeric-cast false positives (null|true|false|"")
+ // ...but misinterprets leading-number strings, particularly hex literals ("0x...")
+ // subtraction forces infinities to NaN
+ // adding 1 corrects loss of precision from parseFloat (#15100)
+ var realStringObj = obj && obj.toString();
+ return !jQuery.isArray( obj ) && ( realStringObj - parseFloat( realStringObj ) + 1 ) >= 0;
+ },
+
+ isEmptyObject: function( obj ) {
+ var name;
+ for ( name in obj ) {
+ return false;
+ }
+ return true;
+ },
+
isPlainObject: function( obj ) {
- var proto, Ctor;
+ var key;
- // Detect obvious negatives
- // Use toString instead of jQuery.type to catch host objects
- if ( !obj || toString.call( obj ) !== "[object Object]" ) {
+ // Must be an Object.
+ // Because of IE, we also have to check the presence of the constructor property.
+ // Make sure that DOM nodes and window objects don't pass through, as well
+ if ( !obj || jQuery.type( obj ) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) {
return false;
}
- proto = getProto( obj );
+ try {
- // Objects with no prototype (e.g., `Object.create( null )`) are plain
- if ( !proto ) {
- return true;
+ // Not own constructor property must be Object
+ if ( obj.constructor &&
+ !hasOwn.call( obj, "constructor" ) &&
+ !hasOwn.call( obj.constructor.prototype, "isPrototypeOf" ) ) {
+ return false;
+ }
+ } catch ( e ) {
+
+ // IE8,9 Will throw exceptions on certain host objects #9897
+ return false;
+ }
+
+ // Support: IE<9
+ // Handle iteration over inherited properties before own properties.
+ if ( !support.ownFirst ) {
+ for ( key in obj ) {
+ return hasOwn.call( obj, key );
+ }
}
- // Objects with prototype are plain iff they were constructed by a global Object function
- Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor;
- return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString;
+ // Own properties are enumerated firstly, so to speed up,
+ // if last one is own, then all properties are own.
+ for ( key in obj ) {}
+
+ return key === undefined || hasOwn.call( obj, key );
},
- isEmptyObject: function( obj ) {
+ type: function( obj ) {
+ if ( obj == null ) {
+ return obj + "";
+ }
+ return typeof obj === "object" || typeof obj === "function" ?
+ class2type[ toString.call( obj ) ] || "object" :
+ typeof obj;
+ },
- /* eslint-disable no-unused-vars */
- // See https://github.com/eslint/eslint/issues/6125
- var name;
+ // Workarounds based on findings by Jim Driscoll
+ // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context
+ globalEval: function( data ) {
+ if ( data && jQuery.trim( data ) ) {
- for ( name in obj ) {
- return false;
+ // We use execScript on Internet Explorer
+ // We use an anonymous function so that context is window
+ // rather than jQuery in Firefox
+ ( window.execScript || function( data ) {
+ window[ "eval" ].call( window, data ); // jscs:ignore requireDotNotation
+ } )( data );
}
- return true;
},
- // Evaluates a script in a global context
- globalEval: function( code ) {
- DOMEval( code );
+ // Convert dashed to camelCase; used by the css and data modules
+ // Microsoft forgot to hump their vendor prefix (#9572)
+ camelCase: function( string ) {
+ return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
+ },
+
+ nodeName: function( elem, name ) {
+ return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();
},
each: function( obj, callback ) {
@@ -366,7 +382,7 @@ jQuery.extend( {
return obj;
},
- // Support: Android <=4.0 only
+ // Support: Android<4.1, IE<9
trim: function( text ) {
return text == null ?
"" :
@@ -392,18 +408,43 @@ jQuery.extend( {
},
inArray: function( elem, arr, i ) {
- return arr == null ? -1 : indexOf.call( arr, elem, i );
+ var len;
+
+ if ( arr ) {
+ if ( indexOf ) {
+ return indexOf.call( arr, elem, i );
+ }
+
+ len = arr.length;
+ i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0;
+
+ for ( ; i < len; i++ ) {
+
+ // Skip accessing in sparse arrays
+ if ( i in arr && arr[ i ] === elem ) {
+ return i;
+ }
+ }
+ }
+
+ return -1;
},
- // Support: Android <=4.0 only, PhantomJS 1 only
- // push.apply(_, arraylike) throws on ancient WebKit
merge: function( first, second ) {
var len = +second.length,
j = 0,
i = first.length;
- for ( ; j < len; j++ ) {
- first[ i++ ] = second[ j ];
+ while ( j < len ) {
+ first[ i++ ] = second[ j++ ];
+ }
+
+ // Support: IE<9
+ // Workaround casting of .length to NaN on otherwise arraylike objects (e.g., NodeLists)
+ if ( len !== len ) {
+ while ( second[ j ] !== undefined ) {
+ first[ i++ ] = second[ j++ ];
+ }
}
first.length = i;
@@ -465,14 +506,53 @@ jQuery.extend( {
// A global GUID counter for objects
guid: 1,
+ // Bind a function to a context, optionally partially applying any
+ // arguments.
+ proxy: function( fn, context ) {
+ var args, proxy, tmp;
+
+ if ( typeof context === "string" ) {
+ tmp = fn[ context ];
+ context = fn;
+ fn = tmp;
+ }
+
+ // Quick check to determine if target is callable, in the spec
+ // this throws a TypeError, but we will just return undefined.
+ if ( !jQuery.isFunction( fn ) ) {
+ return undefined;
+ }
+
+ // Simulated bind
+ args = slice.call( arguments, 2 );
+ proxy = function() {
+ return fn.apply( context || this, args.concat( slice.call( arguments ) ) );
+ };
+
+ // Set the guid of unique handler to the same of original handler, so it can be removed
+ proxy.guid = fn.guid = fn.guid || jQuery.guid++;
+
+ return proxy;
+ },
+
+ now: function() {
+ return +( new Date() );
+ },
+
// jQuery.support is not used in Core but other projects attach their
// properties to it so it needs to exist.
support: support
} );
+// JSHint would error on this code due to the Symbol not being defined in ES5.
+// Defining this global in .jshintrc would create a danger of using the global
+// unguarded in another place, it seems safer to just disable JSHint for these
+// three lines.
+/* jshint ignore: start */
if ( typeof Symbol === "function" ) {
- jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ];
+ jQuery.fn[ Symbol.iterator ] = deletedIds[ Symbol.iterator ];
}
+/* jshint ignore: end */
// Populate the class2type map
jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ),
@@ -482,14 +562,14 @@ function( i, name ) {
function isArrayLike( obj ) {
- // Support: real iOS 8.2 only (not reproducible in simulator)
+ // Support: iOS 8.2 (not reproducible in simulator)
// `in` check used to prevent JIT error (gh-2145)
// hasOwn isn't used here due to false negatives
// regarding Nodelist length in IE
var length = !!obj && "length" in obj && obj.length,
- type = toType( obj );
+ type = jQuery.type( obj );
- if ( isFunction( obj ) || isWindow( obj ) ) {
+ if ( type === "function" || jQuery.isWindow( obj ) ) {
return false;
}
@@ -498,14 +578,14 @@ function isArrayLike( obj ) {
}
var Sizzle =
/*!
- * Sizzle CSS Selector Engine v2.3.3
- * https://sizzlejs.com/
+ * Sizzle CSS Selector Engine v2.2.1
+ * http://sizzlejs.com/
*
* Copyright jQuery Foundation and other contributors
* Released under the MIT license
* http://jquery.org/license
*
- * Date: 2016-08-08
+ * Date: 2015-10-17
*/
(function( window ) {
@@ -546,6 +626,9 @@ var i,
return 0;
},
+ // General-purpose constants
+ MAX_NEGATIVE = 1 << 31,
+
// Instance methods
hasOwn = ({}).hasOwnProperty,
arr = [],
@@ -554,7 +637,7 @@ var i,
push = arr.push,
slice = arr.slice,
// Use a stripped-down indexOf as it's faster than native
- // https://jsperf.com/thor-indexof-vs-for/5
+ // http://jsperf.com/thor-indexof-vs-for/5
indexOf = function( list, elem ) {
var i = 0,
len = list.length;
@@ -574,7 +657,7 @@ var i,
whitespace = "[\\x20\\t\\r\\n\\f]",
// http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
- identifier = "(?:\\\\.|[\\w-]|[^\0-\\xa0])+",
+ identifier = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",
// Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors
attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace +
@@ -631,9 +714,9 @@ var i,
rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,
rsibling = /[+~]/,
+ rescape = /'|\\/g,
- // CSS escapes
- // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters
+ // CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters
runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ),
funescape = function( _, escaped, escapedWhitespace ) {
var high = "0x" + escaped - 0x10000;
@@ -649,39 +732,13 @@ var i,
String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 );
},
- // CSS string/identifier serialization
- // https://drafts.csswg.org/cssom/#common-serializing-idioms
- rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,
- fcssescape = function( ch, asCodePoint ) {
- if ( asCodePoint ) {
-
- // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER
- if ( ch === "\0" ) {
- return "\uFFFD";
- }
-
- // Control characters and (dependent upon position) numbers get escaped as code points
- return ch.slice( 0, -1 ) + "\\" + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " ";
- }
-
- // Other potentially-special ASCII characters get backslash-escaped
- return "\\" + ch;
- },
-
// Used for iframes
// See setDocument()
// Removing the function wrapper causes a "Permission Denied"
// error in IE
unloadHandler = function() {
setDocument();
- },
-
- disabledAncestor = addCombinator(
- function( elem ) {
- return elem.disabled === true && ("form" in elem || "label" in elem);
- },
- { dir: "parentNode", next: "legend" }
- );
+ };
// Optimize for push.apply( _, NodeList )
try {
@@ -713,7 +770,7 @@ try {
}
function Sizzle( selector, context, results, seed ) {
- var m, i, elem, nid, match, groups, newSelector,
+ var m, i, elem, nid, nidselect, match, groups, newSelector,
newContext = context && context.ownerDocument,
// nodeType defaults to 9, since context defaults to document
@@ -806,7 +863,7 @@ function Sizzle( selector, context, results, seed ) {
// Capture the context ID, setting it first if necessary
if ( (nid = context.getAttribute( "id" )) ) {
- nid = nid.replace( rcssescape, fcssescape );
+ nid = nid.replace( rescape, "\\$&" );
} else {
context.setAttribute( "id", (nid = expando) );
}
@@ -814,8 +871,9 @@ function Sizzle( selector, context, results, seed ) {
// Prefix every selector in the list
groups = tokenize( selector );
i = groups.length;
+ nidselect = ridentifier.test( nid ) ? "#" + nid : "[id='" + nid + "']";
while ( i-- ) {
- groups[i] = "#" + nid + " " + toSelector( groups[i] );
+ groups[i] = nidselect + " " + toSelector( groups[i] );
}
newSelector = groups.join( "," );
@@ -876,22 +934,22 @@ function markFunction( fn ) {
/**
* Support testing using an element
- * @param {Function} fn Passed the created element and returns a boolean result
+ * @param {Function} fn Passed the created div and expects a boolean result
*/
function assert( fn ) {
- var el = document.createElement("fieldset");
+ var div = document.createElement("div");
try {
- return !!fn( el );
+ return !!fn( div );
} catch (e) {
return false;
} finally {
// Remove from its parent by default
- if ( el.parentNode ) {
- el.parentNode.removeChild( el );
+ if ( div.parentNode ) {
+ div.parentNode.removeChild( div );
}
// release memory in IE
- el = null;
+ div = null;
}
}
@@ -918,7 +976,8 @@ function addHandle( attrs, handler ) {
function siblingCheck( a, b ) {
var cur = b && a,
diff = cur && a.nodeType === 1 && b.nodeType === 1 &&
- a.sourceIndex - b.sourceIndex;
+ ( ~b.sourceIndex || MAX_NEGATIVE ) -
+ ( ~a.sourceIndex || MAX_NEGATIVE );
// Use IE sourceIndex if available on both nodes
if ( diff ) {
@@ -959,62 +1018,6 @@ function createButtonPseudo( type ) {
};
}
-/**
- * Returns a function to use in pseudos for :enabled/:disabled
- * @param {Boolean} disabled true for :disabled; false for :enabled
- */
-function createDisabledPseudo( disabled ) {
-
- // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable
- return function( elem ) {
-
- // Only certain elements can match :enabled or :disabled
- // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled
- // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled
- if ( "form" in elem ) {
-
- // Check for inherited disabledness on relevant non-disabled elements:
- // * listed form-associated elements in a disabled fieldset
- // https://html.spec.whatwg.org/multipage/forms.html#category-listed
- // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled
- // * option elements in a disabled optgroup
- // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled
- // All such elements have a "form" property.
- if ( elem.parentNode && elem.disabled === false ) {
-
- // Option elements defer to a parent optgroup if present
- if ( "label" in elem ) {
- if ( "label" in elem.parentNode ) {
- return elem.parentNode.disabled === disabled;
- } else {
- return elem.disabled === disabled;
- }
- }
-
- // Support: IE 6 - 11
- // Use the isDisabled shortcut property to check for disabled fieldset ancestors
- return elem.isDisabled === disabled ||
-
- // Where there is no isDisabled, check manually
- /* jshint -W018 */
- elem.isDisabled !== !disabled &&
- disabledAncestor( elem ) === disabled;
- }
-
- return elem.disabled === disabled;
-
- // Try to winnow out elements that can't be disabled before trusting the disabled property.
- // Some victims get caught in our net (label, legend, menu, track), but it shouldn't
- // even exist on them, let alone have a boolean value.
- } else if ( "label" in elem ) {
- return elem.disabled === disabled;
- }
-
- // Remaining elements are neither :enabled nor :disabled
- return false;
- };
-}
-
/**
* Returns a function to use in pseudos for positionals
* @param {Function} fn
@@ -1067,7 +1070,7 @@ isXML = Sizzle.isXML = function( elem ) {
* @returns {Object} Returns the current document
*/
setDocument = Sizzle.setDocument = function( node ) {
- var hasCompare, subWindow,
+ var hasCompare, parent,
doc = node ? node.ownerDocument || node : preferredDoc;
// Return early if doc is invalid or already selected
@@ -1082,16 +1085,14 @@ setDocument = Sizzle.setDocument = function( node ) {
// Support: IE 9-11, Edge
// Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936)
- if ( preferredDoc !== document &&
- (subWindow = document.defaultView) && subWindow.top !== subWindow ) {
-
- // Support: IE 11, Edge
- if ( subWindow.addEventListener ) {
- subWindow.addEventListener( "unload", unloadHandler, false );
+ if ( (parent = document.defaultView) && parent.top !== parent ) {
+ // Support: IE 11
+ if ( parent.addEventListener ) {
+ parent.addEventListener( "unload", unloadHandler, false );
// Support: IE 9 - 10 only
- } else if ( subWindow.attachEvent ) {
- subWindow.attachEvent( "onunload", unloadHandler );
+ } else if ( parent.attachEvent ) {
+ parent.attachEvent( "onunload", unloadHandler );
}
}
@@ -1101,18 +1102,18 @@ setDocument = Sizzle.setDocument = function( node ) {
// Support: IE<8
// Verify that getAttribute really returns attributes and not properties
// (excepting IE8 booleans)
- support.attributes = assert(function( el ) {
- el.className = "i";
- return !el.getAttribute("className");
+ support.attributes = assert(function( div ) {
+ div.className = "i";
+ return !div.getAttribute("className");
});
/* getElement(s)By*
---------------------------------------------------------------------- */
// Check if getElementsByTagName("*") returns only elements
- support.getElementsByTagName = assert(function( el ) {
- el.appendChild( document.createComment("") );
- return !el.getElementsByTagName("*").length;
+ support.getElementsByTagName = assert(function( div ) {
+ div.appendChild( document.createComment("") );
+ return !div.getElementsByTagName("*").length;
});
// Support: IE<9
@@ -1120,28 +1121,32 @@ setDocument = Sizzle.setDocument = function( node ) {
// Support: IE<10
// Check if getElementById returns elements by name
- // The broken getElementById methods don't pick up programmatically-set names,
+ // The broken getElementById methods don't pick up programatically-set names,
// so use a roundabout getElementsByName test
- support.getById = assert(function( el ) {
- docElem.appendChild( el ).id = expando;
+ support.getById = assert(function( div ) {
+ docElem.appendChild( div ).id = expando;
return !document.getElementsByName || !document.getElementsByName( expando ).length;
});
- // ID filter and find
+ // ID find and filter
if ( support.getById ) {
+ Expr.find["ID"] = function( id, context ) {
+ if ( typeof context.getElementById !== "undefined" && documentIsHTML ) {
+ var m = context.getElementById( id );
+ return m ? [ m ] : [];
+ }
+ };
Expr.filter["ID"] = function( id ) {
var attrId = id.replace( runescape, funescape );
return function( elem ) {
return elem.getAttribute("id") === attrId;
};
};
- Expr.find["ID"] = function( id, context ) {
- if ( typeof context.getElementById !== "undefined" && documentIsHTML ) {
- var elem = context.getElementById( id );
- return elem ? [ elem ] : [];
- }
- };
} else {
+ // Support: IE6/7
+ // getElementById is not reliable as a find shortcut
+ delete Expr.find["ID"];
+
Expr.filter["ID"] = function( id ) {
var attrId = id.replace( runescape, funescape );
return function( elem ) {
@@ -1150,36 +1155,6 @@ setDocument = Sizzle.setDocument = function( node ) {
return node && node.value === attrId;
};
};
-
- // Support: IE 6 - 7 only
- // getElementById is not reliable as a find shortcut
- Expr.find["ID"] = function( id, context ) {
- if ( typeof context.getElementById !== "undefined" && documentIsHTML ) {
- var node, i, elems,
- elem = context.getElementById( id );
-
- if ( elem ) {
-
- // Verify the id attribute
- node = elem.getAttributeNode("id");
- if ( node && node.value === id ) {
- return [ elem ];
- }
-
- // Fall back on getElementsByName
- elems = context.getElementsByName( id );
- i = 0;
- while ( (elem = elems[i++]) ) {
- node = elem.getAttributeNode("id");
- if ( node && node.value === id ) {
- return [ elem ];
- }
- }
- }
-
- return [];
- }
- };
}
// Tag
@@ -1233,87 +1208,77 @@ setDocument = Sizzle.setDocument = function( node ) {
// We allow this because of a bug in IE8/9 that throws an error
// whenever `document.activeElement` is accessed on an iframe
// So, we allow :focus to pass through QSA all the time to avoid the IE error
- // See https://bugs.jquery.com/ticket/13378
+ // See http://bugs.jquery.com/ticket/13378
rbuggyQSA = [];
if ( (support.qsa = rnative.test( document.querySelectorAll )) ) {
// Build QSA regex
// Regex strategy adopted from Diego Perini
- assert(function( el ) {
+ assert(function( div ) {
// Select is set to empty string on purpose
// This is to test IE's treatment of not explicitly
// setting a boolean content attribute,
// since its presence should be enough
- // https://bugs.jquery.com/ticket/12359
- docElem.appendChild( el ).innerHTML = " " +
+ // http://bugs.jquery.com/ticket/12359
+ docElem.appendChild( div ).innerHTML = " " +
"" +
" ";
// Support: IE8, Opera 11-12.16
// Nothing should be selected when empty strings follow ^= or $= or *=
// The test attribute must be unknown in Opera but "safe" for WinRT
- // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section
- if ( el.querySelectorAll("[msallowcapture^='']").length ) {
+ // http://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section
+ if ( div.querySelectorAll("[msallowcapture^='']").length ) {
rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" );
}
// Support: IE8
// Boolean attributes and "value" are not treated correctly
- if ( !el.querySelectorAll("[selected]").length ) {
+ if ( !div.querySelectorAll("[selected]").length ) {
rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" );
}
// Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+
- if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) {
+ if ( !div.querySelectorAll( "[id~=" + expando + "-]" ).length ) {
rbuggyQSA.push("~=");
}
// Webkit/Opera - :checked should return selected option elements
// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
// IE8 throws error here and will not see later tests
- if ( !el.querySelectorAll(":checked").length ) {
+ if ( !div.querySelectorAll(":checked").length ) {
rbuggyQSA.push(":checked");
}
// Support: Safari 8+, iOS 8+
// https://bugs.webkit.org/show_bug.cgi?id=136851
- // In-page `selector#id sibling-combinator selector` fails
- if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) {
+ // In-page `selector#id sibing-combinator selector` fails
+ if ( !div.querySelectorAll( "a#" + expando + "+*" ).length ) {
rbuggyQSA.push(".#.+[+~]");
}
});
- assert(function( el ) {
- el.innerHTML = " " +
- " ";
-
+ assert(function( div ) {
// Support: Windows 8 Native Apps
// The type and name attributes are restricted during .innerHTML assignment
var input = document.createElement("input");
input.setAttribute( "type", "hidden" );
- el.appendChild( input ).setAttribute( "name", "D" );
+ div.appendChild( input ).setAttribute( "name", "D" );
// Support: IE8
// Enforce case-sensitivity of name attribute
- if ( el.querySelectorAll("[name=d]").length ) {
+ if ( div.querySelectorAll("[name=d]").length ) {
rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" );
}
// FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)
// IE8 throws error here and will not see later tests
- if ( el.querySelectorAll(":enabled").length !== 2 ) {
- rbuggyQSA.push( ":enabled", ":disabled" );
- }
-
- // Support: IE9-11+
- // IE's :disabled selector does not pick up the children of disabled fieldsets
- docElem.appendChild( el ).disabled = true;
- if ( el.querySelectorAll(":disabled").length !== 2 ) {
+ if ( !div.querySelectorAll(":enabled").length ) {
rbuggyQSA.push( ":enabled", ":disabled" );
}
// Opera 10-11 does not throw on post-comma invalid pseudos
- el.querySelectorAll("*,:x");
+ div.querySelectorAll("*,:x");
rbuggyQSA.push(",.*:");
});
}
@@ -1324,14 +1289,14 @@ setDocument = Sizzle.setDocument = function( node ) {
docElem.oMatchesSelector ||
docElem.msMatchesSelector) )) ) {
- assert(function( el ) {
+ assert(function( div ) {
// Check to see if it's possible to do matchesSelector
// on a disconnected node (IE 9)
- support.disconnectedMatch = matches.call( el, "*" );
+ support.disconnectedMatch = matches.call( div, "div" );
// This should fail with an exception
// Gecko does not error, returns false instead
- matches.call( el, "[s!='']:x" );
+ matches.call( div, "[s!='']:x" );
rbuggyMatches.push( "!=", pseudos );
});
}
@@ -1533,10 +1498,6 @@ Sizzle.attr = function( elem, name ) {
null;
};
-Sizzle.escape = function( sel ) {
- return (sel + "").replace( rcssescape, fcssescape );
-};
-
Sizzle.error = function( msg ) {
throw new Error( "Syntax error, unrecognized expression: " + msg );
};
@@ -2004,8 +1965,13 @@ Expr = Sizzle.selectors = {
},
// Boolean properties
- "enabled": createDisabledPseudo( false ),
- "disabled": createDisabledPseudo( true ),
+ "enabled": function( elem ) {
+ return elem.disabled === false;
+ },
+
+ "disabled": function( elem ) {
+ return elem.disabled === true;
+ },
"checked": function( elem ) {
// In CSS3, :checked should return both checked and selected elements
@@ -2207,9 +2173,7 @@ function toSelector( tokens ) {
function addCombinator( matcher, combinator, base ) {
var dir = combinator.dir,
- skip = combinator.next,
- key = skip || dir,
- checkNonElements = base && key === "parentNode",
+ checkNonElements = base && dir === "parentNode",
doneName = done++;
return combinator.first ?
@@ -2220,7 +2184,6 @@ function addCombinator( matcher, combinator, base ) {
return matcher( elem, context, xml );
}
}
- return false;
} :
// Check against all ancestor/preceding elements
@@ -2246,16 +2209,14 @@ function addCombinator( matcher, combinator, base ) {
// Defend against cloned attroperties (jQuery gh-1709)
uniqueCache = outerCache[ elem.uniqueID ] || (outerCache[ elem.uniqueID ] = {});
- if ( skip && skip === elem.nodeName.toLowerCase() ) {
- elem = elem[ dir ] || elem;
- } else if ( (oldCache = uniqueCache[ key ]) &&
+ if ( (oldCache = uniqueCache[ dir ]) &&
oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) {
// Assign to newCache so results back-propagate to previous elements
return (newCache[ 2 ] = oldCache[ 2 ]);
} else {
// Reuse newcache so results back-propagate to previous elements
- uniqueCache[ key ] = newCache;
+ uniqueCache[ dir ] = newCache;
// A match means we're done; a fail means we have to keep checking
if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) {
@@ -2265,7 +2226,6 @@ function addCombinator( matcher, combinator, base ) {
}
}
}
- return false;
};
}
@@ -2628,7 +2588,8 @@ select = Sizzle.select = function( selector, context, results, seed ) {
// Reduce context if the leading compound selector is an ID
tokens = match[0] = match[0].slice( 0 );
if ( tokens.length > 2 && (token = tokens[0]).type === "ID" &&
- context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[1].type ] ) {
+ support.getById && context.nodeType === 9 && documentIsHTML &&
+ Expr.relative[ tokens[1].type ] ) {
context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0];
if ( !context ) {
@@ -2698,17 +2659,17 @@ setDocument();
// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27)
// Detached nodes confoundingly follow *each other*
-support.sortDetached = assert(function( el ) {
+support.sortDetached = assert(function( div1 ) {
// Should return 1, but returns 4 (following)
- return el.compareDocumentPosition( document.createElement("fieldset") ) & 1;
+ return div1.compareDocumentPosition( document.createElement("div") ) & 1;
});
// Support: IE<8
// Prevent attribute/property "interpolation"
-// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx
-if ( !assert(function( el ) {
- el.innerHTML = " ";
- return el.firstChild.getAttribute("href") === "#" ;
+// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx
+if ( !assert(function( div ) {
+ div.innerHTML = " ";
+ return div.firstChild.getAttribute("href") === "#" ;
}) ) {
addHandle( "type|href|height|width", function( elem, name, isXML ) {
if ( !isXML ) {
@@ -2719,10 +2680,10 @@ if ( !assert(function( el ) {
// Support: IE<9
// Use defaultValue in place of getAttribute("value")
-if ( !support.attributes || !assert(function( el ) {
- el.innerHTML = " ";
- el.firstChild.setAttribute( "value", "" );
- return el.firstChild.getAttribute( "value" ) === "";
+if ( !support.attributes || !assert(function( div ) {
+ div.innerHTML = " ";
+ div.firstChild.setAttribute( "value", "" );
+ return div.firstChild.getAttribute( "value" ) === "";
}) ) {
addHandle( "value", function( elem, name, isXML ) {
if ( !isXML && elem.nodeName.toLowerCase() === "input" ) {
@@ -2733,8 +2694,8 @@ if ( !support.attributes || !assert(function( el ) {
// Support: IE<9
// Use getAttributeNode to fetch booleans when getAttribute lies
-if ( !assert(function( el ) {
- return el.getAttribute("disabled") == null;
+if ( !assert(function( div ) {
+ return div.getAttribute("disabled") == null;
}) ) {
addHandle( booleans, function( elem, name, isXML ) {
var val;
@@ -2755,15 +2716,11 @@ return Sizzle;
jQuery.find = Sizzle;
jQuery.expr = Sizzle.selectors;
-
-// Deprecated
jQuery.expr[ ":" ] = jQuery.expr.pseudos;
jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort;
jQuery.text = Sizzle.getText;
jQuery.isXMLDoc = Sizzle.isXML;
jQuery.contains = Sizzle.contains;
-jQuery.escapeSelector = Sizzle.escape;
-
@@ -2798,41 +2755,40 @@ var siblings = function( n, elem ) {
var rneedsContext = jQuery.expr.match.needsContext;
+var rsingleTag = ( /^<([\w-]+)\s*\/?>(?:<\/\1>|)$/ );
-function nodeName( elem, name ) {
-
- return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();
-
-};
-var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i );
-
+var risSimple = /^.[^:#\[\.,]*$/;
// Implement the identical functionality for filter and not
function winnow( elements, qualifier, not ) {
- if ( isFunction( qualifier ) ) {
+ if ( jQuery.isFunction( qualifier ) ) {
return jQuery.grep( elements, function( elem, i ) {
+ /* jshint -W018 */
return !!qualifier.call( elem, i, elem ) !== not;
} );
+
}
- // Single element
if ( qualifier.nodeType ) {
return jQuery.grep( elements, function( elem ) {
return ( elem === qualifier ) !== not;
} );
+
}
- // Arraylike of elements (jQuery, arguments, Array)
- if ( typeof qualifier !== "string" ) {
- return jQuery.grep( elements, function( elem ) {
- return ( indexOf.call( qualifier, elem ) > -1 ) !== not;
- } );
+ if ( typeof qualifier === "string" ) {
+ if ( risSimple.test( qualifier ) ) {
+ return jQuery.filter( qualifier, elements, not );
+ }
+
+ qualifier = jQuery.filter( qualifier, elements );
}
- // Filtered directly for both simple and complex selectors
- return jQuery.filter( qualifier, elements, not );
+ return jQuery.grep( elements, function( elem ) {
+ return ( jQuery.inArray( elem, qualifier ) > -1 ) !== not;
+ } );
}
jQuery.filter = function( expr, elems, not ) {
@@ -2842,20 +2798,19 @@ jQuery.filter = function( expr, elems, not ) {
expr = ":not(" + expr + ")";
}
- if ( elems.length === 1 && elem.nodeType === 1 ) {
- return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [];
- }
-
- return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) {
- return elem.nodeType === 1;
- } ) );
+ return elems.length === 1 && elem.nodeType === 1 ?
+ jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [] :
+ jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) {
+ return elem.nodeType === 1;
+ } ) );
};
jQuery.fn.extend( {
find: function( selector ) {
- var i, ret,
- len = this.length,
- self = this;
+ var i,
+ ret = [],
+ self = this,
+ len = self.length;
if ( typeof selector !== "string" ) {
return this.pushStack( jQuery( selector ).filter( function() {
@@ -2867,13 +2822,14 @@ jQuery.fn.extend( {
} ) );
}
- ret = this.pushStack( [] );
-
for ( i = 0; i < len; i++ ) {
jQuery.find( selector, self[ i ], ret );
}
- return len > 1 ? jQuery.uniqueSort( ret ) : ret;
+ // Needed because $( selector, context ) becomes $( context ).find( selector )
+ ret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret );
+ ret.selector = this.selector ? this.selector + " " + selector : selector;
+ return ret;
},
filter: function( selector ) {
return this.pushStack( winnow( this, selector || [], false ) );
@@ -2905,8 +2861,7 @@ var rootjQuery,
// A simple way to check for HTML strings
// Prioritize #id over to avoid XSS via location.hash (#9521)
// Strict HTML recognition (#11290: must start with <)
- // Shortcut simple #id case for speed
- rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,
+ rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,
init = jQuery.fn.init = function( selector, context, root ) {
var match, elem;
@@ -2916,14 +2871,14 @@ var rootjQuery,
return this;
}
- // Method init() accepts an alternate rootjQuery
+ // init accepts an alternate rootjQuery
// so migrate can support jQuery.sub (gh-2101)
root = root || rootjQuery;
// Handle HTML strings
if ( typeof selector === "string" ) {
- if ( selector[ 0 ] === "<" &&
- selector[ selector.length - 1 ] === ">" &&
+ if ( selector.charAt( 0 ) === "<" &&
+ selector.charAt( selector.length - 1 ) === ">" &&
selector.length >= 3 ) {
// Assume that strings that start and end with <> are HTML and skip the regex check
@@ -2940,7 +2895,7 @@ var rootjQuery,
if ( match[ 1 ] ) {
context = context instanceof jQuery ? context[ 0 ] : context;
- // Option to run scripts is true for back-compat
+ // scripts is true for back-compat
// Intentionally let the error be thrown if parseHTML is not present
jQuery.merge( this, jQuery.parseHTML(
match[ 1 ],
@@ -2953,7 +2908,7 @@ var rootjQuery,
for ( match in context ) {
// Properties of context are called as methods if possible
- if ( isFunction( this[ match ] ) ) {
+ if ( jQuery.isFunction( this[ match ] ) ) {
this[ match ]( context[ match ] );
// ...and otherwise set as attributes
@@ -2969,12 +2924,23 @@ var rootjQuery,
} else {
elem = document.getElementById( match[ 2 ] );
- if ( elem ) {
+ // Check parentNode to catch when Blackberry 4.6 returns
+ // nodes that are no longer in the document #6963
+ if ( elem && elem.parentNode ) {
- // Inject the element directly into the jQuery object
- this[ 0 ] = elem;
+ // Handle the case where IE and Opera return items
+ // by name instead of ID
+ if ( elem.id !== match[ 2 ] ) {
+ return rootjQuery.find( selector );
+ }
+
+ // Otherwise, we inject the element directly into the jQuery object
this.length = 1;
+ this[ 0 ] = elem;
}
+
+ this.context = document;
+ this.selector = selector;
return this;
}
@@ -2990,20 +2956,25 @@ var rootjQuery,
// HANDLE: $(DOMElement)
} else if ( selector.nodeType ) {
- this[ 0 ] = selector;
+ this.context = this[ 0 ] = selector;
this.length = 1;
return this;
// HANDLE: $(function)
// Shortcut for document ready
- } else if ( isFunction( selector ) ) {
- return root.ready !== undefined ?
+ } else if ( jQuery.isFunction( selector ) ) {
+ return typeof root.ready !== "undefined" ?
root.ready( selector ) :
// Execute immediately if ready is not present
selector( jQuery );
}
+ if ( selector.selector !== undefined ) {
+ this.selector = selector.selector;
+ this.context = selector.context;
+ }
+
return jQuery.makeArray( selector, this );
};
@@ -3016,7 +2987,7 @@ rootjQuery = jQuery( document );
var rparentsprev = /^(?:parents|prev(?:Until|All))/,
- // Methods guaranteed to produce a unique set when starting from a unique set
+ // methods guaranteed to produce a unique set when starting from a unique set
guaranteedUnique = {
children: true,
contents: true,
@@ -3026,12 +2997,12 @@ var rparentsprev = /^(?:parents|prev(?:Until|All))/,
jQuery.fn.extend( {
has: function( target ) {
- var targets = jQuery( target, this ),
- l = targets.length;
+ var i,
+ targets = jQuery( target, this ),
+ len = targets.length;
return this.filter( function() {
- var i = 0;
- for ( ; i < l; i++ ) {
+ for ( i = 0; i < len; i++ ) {
if ( jQuery.contains( this, targets[ i ] ) ) {
return true;
}
@@ -3044,24 +3015,23 @@ jQuery.fn.extend( {
i = 0,
l = this.length,
matched = [],
- targets = typeof selectors !== "string" && jQuery( selectors );
+ pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ?
+ jQuery( selectors, context || this.context ) :
+ 0;
- // Positional selectors never match, since there's no _selection_ context
- if ( !rneedsContext.test( selectors ) ) {
- for ( ; i < l; i++ ) {
- for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) {
+ for ( ; i < l; i++ ) {
+ for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) {
- // Always skip document fragments
- if ( cur.nodeType < 11 && ( targets ?
- targets.index( cur ) > -1 :
+ // Always skip document fragments
+ if ( cur.nodeType < 11 && ( pos ?
+ pos.index( cur ) > -1 :
- // Don't pass non-elements to Sizzle
- cur.nodeType === 1 &&
- jQuery.find.matchesSelector( cur, selectors ) ) ) {
+ // Don't pass non-elements to Sizzle
+ cur.nodeType === 1 &&
+ jQuery.find.matchesSelector( cur, selectors ) ) ) {
- matched.push( cur );
- break;
- }
+ matched.push( cur );
+ break;
}
}
}
@@ -3069,7 +3039,8 @@ jQuery.fn.extend( {
return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched );
},
- // Determine the position of an element within the set
+ // Determine the position of an element within
+ // the matched set of elements
index: function( elem ) {
// No argument, return index in parent
@@ -3077,17 +3048,16 @@ jQuery.fn.extend( {
return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1;
}
- // Index in selector
+ // index in selector
if ( typeof elem === "string" ) {
- return indexOf.call( jQuery( elem ), this[ 0 ] );
+ return jQuery.inArray( this[ 0 ], jQuery( elem ) );
}
// Locate the position of the desired element
- return indexOf.call( this,
+ return jQuery.inArray(
// If it receives a jQuery object, the first element is used
- elem.jquery ? elem[ 0 ] : elem
- );
+ elem.jquery ? elem[ 0 ] : elem, this );
},
add: function( selector, context ) {
@@ -3106,7 +3076,10 @@ jQuery.fn.extend( {
} );
function sibling( cur, dir ) {
- while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {}
+ do {
+ cur = cur[ dir ];
+ } while ( cur && cur.nodeType !== 1 );
+
return cur;
}
@@ -3146,55 +3119,46 @@ jQuery.each( {
return siblings( elem.firstChild );
},
contents: function( elem ) {
- if ( nodeName( elem, "iframe" ) ) {
- return elem.contentDocument;
- }
-
- // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only
- // Treat the template element as a regular one in browsers that
- // don't support it.
- if ( nodeName( elem, "template" ) ) {
- elem = elem.content || elem;
- }
-
- return jQuery.merge( [], elem.childNodes );
+ return jQuery.nodeName( elem, "iframe" ) ?
+ elem.contentDocument || elem.contentWindow.document :
+ jQuery.merge( [], elem.childNodes );
}
}, function( name, fn ) {
jQuery.fn[ name ] = function( until, selector ) {
- var matched = jQuery.map( this, fn, until );
+ var ret = jQuery.map( this, fn, until );
if ( name.slice( -5 ) !== "Until" ) {
selector = until;
}
if ( selector && typeof selector === "string" ) {
- matched = jQuery.filter( selector, matched );
+ ret = jQuery.filter( selector, ret );
}
if ( this.length > 1 ) {
// Remove duplicates
if ( !guaranteedUnique[ name ] ) {
- jQuery.uniqueSort( matched );
+ ret = jQuery.uniqueSort( ret );
}
// Reverse order for parents* and prev-derivatives
if ( rparentsprev.test( name ) ) {
- matched.reverse();
+ ret = ret.reverse();
}
}
- return this.pushStack( matched );
+ return this.pushStack( ret );
};
} );
-var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g );
+var rnotwhite = ( /\S+/g );
// Convert String-formatted options into Object-formatted ones
function createOptions( options ) {
var object = {};
- jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) {
+ jQuery.each( options.match( rnotwhite ) || [], function( _, flag ) {
object[ flag ] = true;
} );
return object;
@@ -3255,7 +3219,7 @@ jQuery.Callbacks = function( options ) {
fire = function() {
// Enforce single-firing
- locked = locked || options.once;
+ locked = options.once;
// Execute callbacks for all pending executions,
// respecting firingIndex overrides and runtime changes
@@ -3311,11 +3275,11 @@ jQuery.Callbacks = function( options ) {
( function add( args ) {
jQuery.each( args, function( _, arg ) {
- if ( isFunction( arg ) ) {
+ if ( jQuery.isFunction( arg ) ) {
if ( !options.unique || !self.has( arg ) ) {
list.push( arg );
}
- } else if ( arg && arg.length && toType( arg ) !== "string" ) {
+ } else if ( arg && arg.length && jQuery.type( arg ) !== "string" ) {
// Inspect recursively
add( arg );
@@ -3378,9 +3342,9 @@ jQuery.Callbacks = function( options ) {
// Also disable .add unless we have memory (since it would have no effect)
// Abort any pending executions
lock: function() {
- locked = queue = [];
- if ( !memory && !firing ) {
- list = memory = "";
+ locked = true;
+ if ( !memory ) {
+ self.disable();
}
return this;
},
@@ -3417,59 +3381,15 @@ jQuery.Callbacks = function( options ) {
};
-function Identity( v ) {
- return v;
-}
-function Thrower( ex ) {
- throw ex;
-}
-
-function adoptValue( value, resolve, reject, noValue ) {
- var method;
-
- try {
-
- // Check for promise aspect first to privilege synchronous behavior
- if ( value && isFunction( ( method = value.promise ) ) ) {
- method.call( value ).done( resolve ).fail( reject );
-
- // Other thenables
- } else if ( value && isFunction( ( method = value.then ) ) ) {
- method.call( value, resolve, reject );
-
- // Other non-thenables
- } else {
-
- // Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer:
- // * false: [ value ].slice( 0 ) => resolve( value )
- // * true: [ value ].slice( 1 ) => resolve()
- resolve.apply( undefined, [ value ].slice( noValue ) );
- }
-
- // For Promises/A+, convert exceptions into rejections
- // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in
- // Deferred#then to conditionally suppress rejection.
- } catch ( value ) {
-
- // Support: Android 4.0 only
- // Strict mode functions invoked without .call/.apply get global-object context
- reject.apply( undefined, [ value ] );
- }
-}
-
jQuery.extend( {
Deferred: function( func ) {
var tuples = [
- // action, add listener, callbacks,
- // ... .then handlers, argument index, [final state]
- [ "notify", "progress", jQuery.Callbacks( "memory" ),
- jQuery.Callbacks( "memory" ), 2 ],
- [ "resolve", "done", jQuery.Callbacks( "once memory" ),
- jQuery.Callbacks( "once memory" ), 0, "resolved" ],
- [ "reject", "fail", jQuery.Callbacks( "once memory" ),
- jQuery.Callbacks( "once memory" ), 1, "rejected" ]
+ // action, add listener, listener list, final state
+ [ "resolve", "done", jQuery.Callbacks( "once memory" ), "resolved" ],
+ [ "reject", "fail", jQuery.Callbacks( "once memory" ), "rejected" ],
+ [ "notify", "progress", jQuery.Callbacks( "memory" ) ]
],
state = "pending",
promise = {
@@ -3480,33 +3400,23 @@ jQuery.extend( {
deferred.done( arguments ).fail( arguments );
return this;
},
- "catch": function( fn ) {
- return promise.then( null, fn );
- },
-
- // Keep pipe for back-compat
- pipe: function( /* fnDone, fnFail, fnProgress */ ) {
+ then: function( /* fnDone, fnFail, fnProgress */ ) {
var fns = arguments;
-
return jQuery.Deferred( function( newDefer ) {
jQuery.each( tuples, function( i, tuple ) {
+ var fn = jQuery.isFunction( fns[ i ] ) && fns[ i ];
- // Map tuples (progress, done, fail) to arguments (done, fail, progress)
- var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ];
-
- // deferred.progress(function() { bind to newDefer or newDefer.notify })
- // deferred.done(function() { bind to newDefer or newDefer.resolve })
- // deferred.fail(function() { bind to newDefer or newDefer.reject })
+ // deferred[ done | fail | progress ] for forwarding actions to newDefer
deferred[ tuple[ 1 ] ]( function() {
var returned = fn && fn.apply( this, arguments );
- if ( returned && isFunction( returned.promise ) ) {
+ if ( returned && jQuery.isFunction( returned.promise ) ) {
returned.promise()
.progress( newDefer.notify )
.done( newDefer.resolve )
.fail( newDefer.reject );
} else {
newDefer[ tuple[ 0 ] + "With" ](
- this,
+ this === promise ? newDefer.promise() : this,
fn ? [ returned ] : arguments
);
}
@@ -3515,231 +3425,42 @@ jQuery.extend( {
fns = null;
} ).promise();
},
- then: function( onFulfilled, onRejected, onProgress ) {
- var maxDepth = 0;
- function resolve( depth, deferred, handler, special ) {
- return function() {
- var that = this,
- args = arguments,
- mightThrow = function() {
- var returned, then;
-
- // Support: Promises/A+ section 2.3.3.3.3
- // https://promisesaplus.com/#point-59
- // Ignore double-resolution attempts
- if ( depth < maxDepth ) {
- return;
- }
- returned = handler.apply( that, args );
+ // Get a promise for this deferred
+ // If obj is provided, the promise aspect is added to the object
+ promise: function( obj ) {
+ return obj != null ? jQuery.extend( obj, promise ) : promise;
+ }
+ },
+ deferred = {};
- // Support: Promises/A+ section 2.3.1
- // https://promisesaplus.com/#point-48
- if ( returned === deferred.promise() ) {
- throw new TypeError( "Thenable self-resolution" );
- }
+ // Keep pipe for back-compat
+ promise.pipe = promise.then;
- // Support: Promises/A+ sections 2.3.3.1, 3.5
- // https://promisesaplus.com/#point-54
- // https://promisesaplus.com/#point-75
- // Retrieve `then` only once
- then = returned &&
-
- // Support: Promises/A+ section 2.3.4
- // https://promisesaplus.com/#point-64
- // Only check objects and functions for thenability
- ( typeof returned === "object" ||
- typeof returned === "function" ) &&
- returned.then;
-
- // Handle a returned thenable
- if ( isFunction( then ) ) {
-
- // Special processors (notify) just wait for resolution
- if ( special ) {
- then.call(
- returned,
- resolve( maxDepth, deferred, Identity, special ),
- resolve( maxDepth, deferred, Thrower, special )
- );
-
- // Normal processors (resolve) also hook into progress
- } else {
-
- // ...and disregard older resolution values
- maxDepth++;
-
- then.call(
- returned,
- resolve( maxDepth, deferred, Identity, special ),
- resolve( maxDepth, deferred, Thrower, special ),
- resolve( maxDepth, deferred, Identity,
- deferred.notifyWith )
- );
- }
+ // Add list-specific methods
+ jQuery.each( tuples, function( i, tuple ) {
+ var list = tuple[ 2 ],
+ stateString = tuple[ 3 ];
- // Handle all other returned values
- } else {
+ // promise[ done | fail | progress ] = list.add
+ promise[ tuple[ 1 ] ] = list.add;
- // Only substitute handlers pass on context
- // and multiple values (non-spec behavior)
- if ( handler !== Identity ) {
- that = undefined;
- args = [ returned ];
- }
+ // Handle state
+ if ( stateString ) {
+ list.add( function() {
- // Process the value(s)
- // Default process is resolve
- ( special || deferred.resolveWith )( that, args );
- }
- },
-
- // Only normal processors (resolve) catch and reject exceptions
- process = special ?
- mightThrow :
- function() {
- try {
- mightThrow();
- } catch ( e ) {
-
- if ( jQuery.Deferred.exceptionHook ) {
- jQuery.Deferred.exceptionHook( e,
- process.stackTrace );
- }
-
- // Support: Promises/A+ section 2.3.3.3.4.1
- // https://promisesaplus.com/#point-61
- // Ignore post-resolution exceptions
- if ( depth + 1 >= maxDepth ) {
-
- // Only substitute handlers pass on context
- // and multiple values (non-spec behavior)
- if ( handler !== Thrower ) {
- that = undefined;
- args = [ e ];
- }
-
- deferred.rejectWith( that, args );
- }
- }
- };
-
- // Support: Promises/A+ section 2.3.3.3.1
- // https://promisesaplus.com/#point-57
- // Re-resolve promises immediately to dodge false rejection from
- // subsequent errors
- if ( depth ) {
- process();
- } else {
+ // state = [ resolved | rejected ]
+ state = stateString;
- // Call an optional hook to record the stack, in case of exception
- // since it's otherwise lost when execution goes async
- if ( jQuery.Deferred.getStackHook ) {
- process.stackTrace = jQuery.Deferred.getStackHook();
- }
- window.setTimeout( process );
- }
- };
- }
+ // [ reject_list | resolve_list ].disable; progress_list.lock
+ }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
+ }
- return jQuery.Deferred( function( newDefer ) {
-
- // progress_handlers.add( ... )
- tuples[ 0 ][ 3 ].add(
- resolve(
- 0,
- newDefer,
- isFunction( onProgress ) ?
- onProgress :
- Identity,
- newDefer.notifyWith
- )
- );
-
- // fulfilled_handlers.add( ... )
- tuples[ 1 ][ 3 ].add(
- resolve(
- 0,
- newDefer,
- isFunction( onFulfilled ) ?
- onFulfilled :
- Identity
- )
- );
-
- // rejected_handlers.add( ... )
- tuples[ 2 ][ 3 ].add(
- resolve(
- 0,
- newDefer,
- isFunction( onRejected ) ?
- onRejected :
- Thrower
- )
- );
- } ).promise();
- },
-
- // Get a promise for this deferred
- // If obj is provided, the promise aspect is added to the object
- promise: function( obj ) {
- return obj != null ? jQuery.extend( obj, promise ) : promise;
- }
- },
- deferred = {};
-
- // Add list-specific methods
- jQuery.each( tuples, function( i, tuple ) {
- var list = tuple[ 2 ],
- stateString = tuple[ 5 ];
-
- // promise.progress = list.add
- // promise.done = list.add
- // promise.fail = list.add
- promise[ tuple[ 1 ] ] = list.add;
-
- // Handle state
- if ( stateString ) {
- list.add(
- function() {
-
- // state = "resolved" (i.e., fulfilled)
- // state = "rejected"
- state = stateString;
- },
-
- // rejected_callbacks.disable
- // fulfilled_callbacks.disable
- tuples[ 3 - i ][ 2 ].disable,
-
- // rejected_handlers.disable
- // fulfilled_handlers.disable
- tuples[ 3 - i ][ 3 ].disable,
-
- // progress_callbacks.lock
- tuples[ 0 ][ 2 ].lock,
-
- // progress_handlers.lock
- tuples[ 0 ][ 3 ].lock
- );
- }
-
- // progress_handlers.fire
- // fulfilled_handlers.fire
- // rejected_handlers.fire
- list.add( tuple[ 3 ].fire );
-
- // deferred.notify = function() { deferred.notifyWith(...) }
- // deferred.resolve = function() { deferred.resolveWith(...) }
- // deferred.reject = function() { deferred.rejectWith(...) }
+ // deferred[ resolve | reject | notify ]
deferred[ tuple[ 0 ] ] = function() {
- deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments );
+ deferred[ tuple[ 0 ] + "With" ]( this === deferred ? promise : this, arguments );
return this;
};
-
- // deferred.notifyWith = list.fireWith
- // deferred.resolveWith = list.fireWith
- // deferred.rejectWith = list.fireWith
deferred[ tuple[ 0 ] + "With" ] = list.fireWith;
} );
@@ -3756,95 +3477,69 @@ jQuery.extend( {
},
// Deferred helper
- when: function( singleValue ) {
- var
-
- // count of uncompleted subordinates
- remaining = arguments.length,
-
- // count of unprocessed arguments
- i = remaining,
-
- // subordinate fulfillment data
- resolveContexts = Array( i ),
+ when: function( subordinate /* , ..., subordinateN */ ) {
+ var i = 0,
resolveValues = slice.call( arguments ),
+ length = resolveValues.length,
- // the master Deferred
- master = jQuery.Deferred(),
+ // the count of uncompleted subordinates
+ remaining = length !== 1 ||
+ ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,
- // subordinate callback factory
- updateFunc = function( i ) {
+ // the master Deferred.
+ // If resolveValues consist of only a single Deferred, just use that.
+ deferred = remaining === 1 ? subordinate : jQuery.Deferred(),
+
+ // Update function for both resolve and progress values
+ updateFunc = function( i, contexts, values ) {
return function( value ) {
- resolveContexts[ i ] = this;
- resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value;
- if ( !( --remaining ) ) {
- master.resolveWith( resolveContexts, resolveValues );
+ contexts[ i ] = this;
+ values[ i ] = arguments.length > 1 ? slice.call( arguments ) : value;
+ if ( values === progressValues ) {
+ deferred.notifyWith( contexts, values );
+
+ } else if ( !( --remaining ) ) {
+ deferred.resolveWith( contexts, values );
}
};
- };
-
- // Single- and empty arguments are adopted like Promise.resolve
- if ( remaining <= 1 ) {
- adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject,
- !remaining );
+ },
- // Use .then() to unwrap secondary thenables (cf. gh-3000)
- if ( master.state() === "pending" ||
- isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) {
+ progressValues, progressContexts, resolveContexts;
- return master.then();
+ // add listeners to Deferred subordinates; treat others as resolved
+ if ( length > 1 ) {
+ progressValues = new Array( length );
+ progressContexts = new Array( length );
+ resolveContexts = new Array( length );
+ for ( ; i < length; i++ ) {
+ if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {
+ resolveValues[ i ].promise()
+ .progress( updateFunc( i, progressContexts, progressValues ) )
+ .done( updateFunc( i, resolveContexts, resolveValues ) )
+ .fail( deferred.reject );
+ } else {
+ --remaining;
+ }
}
}
- // Multiple arguments are aggregated like Promise.all array elements
- while ( i-- ) {
- adoptValue( resolveValues[ i ], updateFunc( i ), master.reject );
+ // if we're not waiting on anything, resolve the master
+ if ( !remaining ) {
+ deferred.resolveWith( resolveContexts, resolveValues );
}
- return master.promise();
+ return deferred.promise();
}
} );
-// These usually indicate a programmer mistake during development,
-// warn about them ASAP rather than swallowing them by default.
-var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;
-
-jQuery.Deferred.exceptionHook = function( error, stack ) {
-
- // Support: IE 8 - 9 only
- // Console exists when dev tools are open, which can happen at any time
- if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) {
- window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack );
- }
-};
-
-
-
-
-jQuery.readyException = function( error ) {
- window.setTimeout( function() {
- throw error;
- } );
-};
-
-
-
-
// The deferred used on DOM ready
-var readyList = jQuery.Deferred();
+var readyList;
jQuery.fn.ready = function( fn ) {
- readyList
- .then( fn )
-
- // Wrap jQuery.readyException in a function so that the lookup
- // happens at the time of error handling instead of callback
- // registration.
- .catch( function( error ) {
- jQuery.readyException( error );
- } );
+ // Add the callback
+ jQuery.ready.promise().done( fn );
return this;
};
@@ -3858,6 +3553,15 @@ jQuery.extend( {
// the ready event fires. See #6781
readyWait: 1,
+ // Hold (or release) the ready event
+ holdReady: function( hold ) {
+ if ( hold ) {
+ jQuery.readyWait++;
+ } else {
+ jQuery.ready( true );
+ }
+ },
+
// Handle when the DOM is ready
ready: function( wait ) {
@@ -3876,398 +3580,500 @@ jQuery.extend( {
// If there are functions bound, to execute
readyList.resolveWith( document, [ jQuery ] );
+
+ // Trigger any bound ready events
+ if ( jQuery.fn.triggerHandler ) {
+ jQuery( document ).triggerHandler( "ready" );
+ jQuery( document ).off( "ready" );
+ }
}
} );
-jQuery.ready.then = readyList.then;
+/**
+ * Clean-up method for dom ready events
+ */
+function detach() {
+ if ( document.addEventListener ) {
+ document.removeEventListener( "DOMContentLoaded", completed );
+ window.removeEventListener( "load", completed );
+
+ } else {
+ document.detachEvent( "onreadystatechange", completed );
+ window.detachEvent( "onload", completed );
+ }
+}
-// The ready event handler and self cleanup method
+/**
+ * The ready event handler and self cleanup method
+ */
function completed() {
- document.removeEventListener( "DOMContentLoaded", completed );
- window.removeEventListener( "load", completed );
- jQuery.ready();
+
+ // readyState === "complete" is good enough for us to call the dom ready in oldIE
+ if ( document.addEventListener ||
+ window.event.type === "load" ||
+ document.readyState === "complete" ) {
+
+ detach();
+ jQuery.ready();
+ }
}
-// Catch cases where $(document).ready() is called
-// after the browser event has already occurred.
-// Support: IE <=9 - 10 only
-// Older IE sometimes signals "interactive" too soon
-if ( document.readyState === "complete" ||
- ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) {
+jQuery.ready.promise = function( obj ) {
+ if ( !readyList ) {
- // Handle it asynchronously to allow scripts the opportunity to delay ready
- window.setTimeout( jQuery.ready );
+ readyList = jQuery.Deferred();
-} else {
+ // Catch cases where $(document).ready() is called
+ // after the browser event has already occurred.
+ // Support: IE6-10
+ // Older IE sometimes signals "interactive" too soon
+ if ( document.readyState === "complete" ||
+ ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) {
- // Use the handy event callback
- document.addEventListener( "DOMContentLoaded", completed );
+ // Handle it asynchronously to allow scripts the opportunity to delay ready
+ window.setTimeout( jQuery.ready );
- // A fallback to window.onload, that will always work
- window.addEventListener( "load", completed );
-}
+ // Standards-based browsers support DOMContentLoaded
+ } else if ( document.addEventListener ) {
+ // Use the handy event callback
+ document.addEventListener( "DOMContentLoaded", completed );
+ // A fallback to window.onload, that will always work
+ window.addEventListener( "load", completed );
+ // If IE event model is used
+ } else {
-// Multifunctional method to get and set values of a collection
-// The value/s can optionally be executed if it's a function
-var access = function( elems, fn, key, value, chainable, emptyGet, raw ) {
- var i = 0,
- len = elems.length,
- bulk = key == null;
+ // Ensure firing before onload, maybe late but safe also for iframes
+ document.attachEvent( "onreadystatechange", completed );
- // Sets many values
- if ( toType( key ) === "object" ) {
- chainable = true;
- for ( i in key ) {
- access( elems, fn, i, key[ i ], true, emptyGet, raw );
- }
+ // A fallback to window.onload, that will always work
+ window.attachEvent( "onload", completed );
- // Sets one value
- } else if ( value !== undefined ) {
- chainable = true;
+ // If IE and not a frame
+ // continually check to see if the document is ready
+ var top = false;
- if ( !isFunction( value ) ) {
- raw = true;
- }
+ try {
+ top = window.frameElement == null && document.documentElement;
+ } catch ( e ) {}
- if ( bulk ) {
+ if ( top && top.doScroll ) {
+ ( function doScrollCheck() {
+ if ( !jQuery.isReady ) {
- // Bulk operations run against the entire set
- if ( raw ) {
- fn.call( elems, value );
- fn = null;
+ try {
- // ...except when executing function values
- } else {
- bulk = fn;
- fn = function( elem, key, value ) {
- return bulk.call( jQuery( elem ), value );
- };
- }
- }
+ // Use the trick by Diego Perini
+ // http://javascript.nwbox.com/IEContentLoaded/
+ top.doScroll( "left" );
+ } catch ( e ) {
+ return window.setTimeout( doScrollCheck, 50 );
+ }
- if ( fn ) {
- for ( ; i < len; i++ ) {
- fn(
- elems[ i ], key, raw ?
- value :
- value.call( elems[ i ], i, fn( elems[ i ], key ) )
- );
+ // detach all dom ready events
+ detach();
+
+ // and execute any waiting functions
+ jQuery.ready();
+ }
+ } )();
}
}
}
-
- if ( chainable ) {
- return elems;
- }
-
- // Gets
- if ( bulk ) {
- return fn.call( elems );
- }
-
- return len ? fn( elems[ 0 ], key ) : emptyGet;
+ return readyList.promise( obj );
};
+// Kick off the DOM ready check even if the user does not
+jQuery.ready.promise();
-// Matches dashed string for camelizing
-var rmsPrefix = /^-ms-/,
- rdashAlpha = /-([a-z])/g;
-// Used by camelCase as callback to replace()
-function fcamelCase( all, letter ) {
- return letter.toUpperCase();
-}
-// Convert dashed to camelCase; used by the css and data modules
-// Support: IE <=9 - 11, Edge 12 - 15
-// Microsoft forgot to hump their vendor prefix (#9572)
-function camelCase( string ) {
- return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
+
+// Support: IE<9
+// Iteration over object's inherited properties before its own
+var i;
+for ( i in jQuery( support ) ) {
+ break;
}
-var acceptData = function( owner ) {
-
- // Accepts only:
- // - Node
- // - Node.ELEMENT_NODE
- // - Node.DOCUMENT_NODE
- // - Object
- // - Any
- return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType );
-};
+support.ownFirst = i === "0";
+// Note: most support tests are defined in their respective modules.
+// false until the test is run
+support.inlineBlockNeedsLayout = false;
+// Execute ASAP in case we need to set body.style.zoom
+jQuery( function() {
+ // Minified: var a,b,c,d
+ var val, div, body, container;
-function Data() {
- this.expando = jQuery.expando + Data.uid++;
-}
+ body = document.getElementsByTagName( "body" )[ 0 ];
+ if ( !body || !body.style ) {
-Data.uid = 1;
+ // Return for frameset docs that don't have a body
+ return;
+ }
-Data.prototype = {
+ // Setup
+ div = document.createElement( "div" );
+ container = document.createElement( "div" );
+ container.style.cssText = "position:absolute;border:0;width:0;height:0;top:0;left:-9999px";
+ body.appendChild( container ).appendChild( div );
- cache: function( owner ) {
+ if ( typeof div.style.zoom !== "undefined" ) {
- // Check if the owner object already has a cache
- var value = owner[ this.expando ];
+ // Support: IE<8
+ // Check if natively block-level elements act like inline-block
+ // elements when setting their display to 'inline' and giving
+ // them layout
+ div.style.cssText = "display:inline;margin:0;border:0;padding:1px;width:1px;zoom:1";
- // If not, create one
- if ( !value ) {
- value = {};
+ support.inlineBlockNeedsLayout = val = div.offsetWidth === 3;
+ if ( val ) {
- // We can accept data for non-element nodes in modern browsers,
- // but we should not, see #8335.
- // Always return an empty object.
- if ( acceptData( owner ) ) {
+ // Prevent IE 6 from affecting layout for positioned elements #11048
+ // Prevent IE from shrinking the body in IE 7 mode #12869
+ // Support: IE<8
+ body.style.zoom = 1;
+ }
+ }
- // If it is a node unlikely to be stringify-ed or looped over
- // use plain assignment
- if ( owner.nodeType ) {
- owner[ this.expando ] = value;
+ body.removeChild( container );
+} );
- // Otherwise secure it in a non-enumerable property
- // configurable must be true to allow the property to be
- // deleted when data is removed
- } else {
- Object.defineProperty( owner, this.expando, {
- value: value,
- configurable: true
- } );
- }
- }
- }
- return value;
- },
- set: function( owner, data, value ) {
- var prop,
- cache = this.cache( owner );
+( function() {
+ var div = document.createElement( "div" );
- // Handle: [ owner, key, value ] args
- // Always use camelCase key (gh-2257)
- if ( typeof data === "string" ) {
- cache[ camelCase( data ) ] = value;
+ // Support: IE<9
+ support.deleteExpando = true;
+ try {
+ delete div.test;
+ } catch ( e ) {
+ support.deleteExpando = false;
+ }
- // Handle: [ owner, { properties } ] args
- } else {
+ // Null elements to avoid leaks in IE.
+ div = null;
+} )();
+var acceptData = function( elem ) {
+ var noData = jQuery.noData[ ( elem.nodeName + " " ).toLowerCase() ],
+ nodeType = +elem.nodeType || 1;
- // Copy the properties one-by-one to the cache object
- for ( prop in data ) {
- cache[ camelCase( prop ) ] = data[ prop ];
- }
- }
- return cache;
- },
- get: function( owner, key ) {
- return key === undefined ?
- this.cache( owner ) :
+ // Do not set data on non-element DOM nodes because it will not be cleared (#8335).
+ return nodeType !== 1 && nodeType !== 9 ?
+ false :
- // Always use camelCase key (gh-2257)
- owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ];
- },
- access: function( owner, key, value ) {
+ // Nodes accept data unless otherwise specified; rejection can be conditional
+ !noData || noData !== true && elem.getAttribute( "classid" ) === noData;
+};
- // In cases where either:
- //
- // 1. No key was specified
- // 2. A string key was specified, but no value provided
- //
- // Take the "read" path and allow the get method to determine
- // which value to return, respectively either:
- //
- // 1. The entire cache object
- // 2. The data stored at the key
- //
- if ( key === undefined ||
- ( ( key && typeof key === "string" ) && value === undefined ) ) {
- return this.get( owner, key );
- }
- // When the key is not a string, or both a key and value
- // are specified, set or extend (existing objects) with either:
- //
- // 1. An object of properties
- // 2. A key and value
- //
- this.set( owner, key, value );
- // Since the "set" path can have two possible entry points
- // return the expected data based on which path was taken[*]
- return value !== undefined ? value : key;
- },
- remove: function( owner, key ) {
- var i,
- cache = owner[ this.expando ];
+var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,
+ rmultiDash = /([A-Z])/g;
- if ( cache === undefined ) {
- return;
- }
+function dataAttr( elem, key, data ) {
- if ( key !== undefined ) {
+ // If nothing was found internally, try to fetch any
+ // data from the HTML5 data-* attribute
+ if ( data === undefined && elem.nodeType === 1 ) {
- // Support array or space separated string of keys
- if ( Array.isArray( key ) ) {
+ var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();
- // If key is an array of keys...
- // We always set camelCase keys, so remove that.
- key = key.map( camelCase );
- } else {
- key = camelCase( key );
+ data = elem.getAttribute( name );
- // If a key with the spaces exists, use it.
- // Otherwise, create an array by matching non-whitespace
- key = key in cache ?
- [ key ] :
- ( key.match( rnothtmlwhite ) || [] );
- }
+ if ( typeof data === "string" ) {
+ try {
+ data = data === "true" ? true :
+ data === "false" ? false :
+ data === "null" ? null :
+
+ // Only convert to a number if it doesn't change the string
+ +data + "" === data ? +data :
+ rbrace.test( data ) ? jQuery.parseJSON( data ) :
+ data;
+ } catch ( e ) {}
- i = key.length;
+ // Make sure we set the data so it isn't changed later
+ jQuery.data( elem, key, data );
- while ( i-- ) {
- delete cache[ key[ i ] ];
- }
+ } else {
+ data = undefined;
}
+ }
- // Remove the expando if there's no more data
- if ( key === undefined || jQuery.isEmptyObject( cache ) ) {
+ return data;
+}
- // Support: Chrome <=35 - 45
- // Webkit & Blink performance suffers when deleting properties
- // from DOM nodes, so set to undefined instead
- // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted)
- if ( owner.nodeType ) {
- owner[ this.expando ] = undefined;
- } else {
- delete owner[ this.expando ];
- }
+// checks a cache object for emptiness
+function isEmptyDataObject( obj ) {
+ var name;
+ for ( name in obj ) {
+
+ // if the public data object is empty, the private is still empty
+ if ( name === "data" && jQuery.isEmptyObject( obj[ name ] ) ) {
+ continue;
+ }
+ if ( name !== "toJSON" ) {
+ return false;
}
- },
- hasData: function( owner ) {
- var cache = owner[ this.expando ];
- return cache !== undefined && !jQuery.isEmptyObject( cache );
}
-};
-var dataPriv = new Data();
-var dataUser = new Data();
+ return true;
+}
+
+function internalData( elem, name, data, pvt /* Internal Use Only */ ) {
+ if ( !acceptData( elem ) ) {
+ return;
+ }
+ var ret, thisCache,
+ internalKey = jQuery.expando,
+ // We have to handle DOM nodes and JS objects differently because IE6-7
+ // can't GC object references properly across the DOM-JS boundary
+ isNode = elem.nodeType,
-// Implementation Summary
-//
-// 1. Enforce API surface and semantic compatibility with 1.9.x branch
-// 2. Improve the module's maintainability by reducing the storage
-// paths to a single mechanism.
-// 3. Use the same single mechanism to support "private" and "user" data.
-// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData)
-// 5. Avoid exposing implementation details on user objects (eg. expando properties)
-// 6. Provide a clear path for implementation upgrade to WeakMap in 2014
+ // Only DOM nodes need the global jQuery cache; JS object data is
+ // attached directly to the object so GC can occur automatically
+ cache = isNode ? jQuery.cache : elem,
-var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,
- rmultiDash = /[A-Z]/g;
+ // Only defining an ID for JS objects if its cache already exists allows
+ // the code to shortcut on the same path as a DOM node with no cache
+ id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey;
-function getData( data ) {
- if ( data === "true" ) {
- return true;
+ // Avoid doing any more work than we need to when trying to get data on an
+ // object that has no data at all
+ if ( ( !id || !cache[ id ] || ( !pvt && !cache[ id ].data ) ) &&
+ data === undefined && typeof name === "string" ) {
+ return;
}
- if ( data === "false" ) {
- return false;
+ if ( !id ) {
+
+ // Only DOM nodes need a new unique ID for each element since their data
+ // ends up in the global cache
+ if ( isNode ) {
+ id = elem[ internalKey ] = deletedIds.pop() || jQuery.guid++;
+ } else {
+ id = internalKey;
+ }
}
- if ( data === "null" ) {
- return null;
+ if ( !cache[ id ] ) {
+
+ // Avoid exposing jQuery metadata on plain JS objects when the object
+ // is serialized using JSON.stringify
+ cache[ id ] = isNode ? {} : { toJSON: jQuery.noop };
}
- // Only convert to a number if it doesn't change the string
- if ( data === +data + "" ) {
- return +data;
+ // An object can be passed to jQuery.data instead of a key/value pair; this gets
+ // shallow copied over onto the existing cache
+ if ( typeof name === "object" || typeof name === "function" ) {
+ if ( pvt ) {
+ cache[ id ] = jQuery.extend( cache[ id ], name );
+ } else {
+ cache[ id ].data = jQuery.extend( cache[ id ].data, name );
+ }
}
- if ( rbrace.test( data ) ) {
- return JSON.parse( data );
+ thisCache = cache[ id ];
+
+ // jQuery data() is stored in a separate object inside the object's internal data
+ // cache in order to avoid key collisions between internal data and user-defined
+ // data.
+ if ( !pvt ) {
+ if ( !thisCache.data ) {
+ thisCache.data = {};
+ }
+
+ thisCache = thisCache.data;
}
- return data;
-}
+ if ( data !== undefined ) {
+ thisCache[ jQuery.camelCase( name ) ] = data;
+ }
-function dataAttr( elem, key, data ) {
- var name;
+ // Check for both converted-to-camel and non-converted data property names
+ // If a data property was specified
+ if ( typeof name === "string" ) {
- // If nothing was found internally, try to fetch any
- // data from the HTML5 data-* attribute
- if ( data === undefined && elem.nodeType === 1 ) {
- name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase();
- data = elem.getAttribute( name );
+ // First Try to find as-is property data
+ ret = thisCache[ name ];
- if ( typeof data === "string" ) {
- try {
- data = getData( data );
- } catch ( e ) {}
+ // Test for null|undefined property data
+ if ( ret == null ) {
- // Make sure we set the data so it isn't changed later
- dataUser.set( elem, key, data );
- } else {
- data = undefined;
+ // Try to find the camelCased property
+ ret = thisCache[ jQuery.camelCase( name ) ];
}
+ } else {
+ ret = thisCache;
}
- return data;
-}
-jQuery.extend( {
- hasData: function( elem ) {
- return dataUser.hasData( elem ) || dataPriv.hasData( elem );
- },
+ return ret;
+}
- data: function( elem, name, data ) {
- return dataUser.access( elem, name, data );
- },
+function internalRemoveData( elem, name, pvt ) {
+ if ( !acceptData( elem ) ) {
+ return;
+ }
- removeData: function( elem, name ) {
- dataUser.remove( elem, name );
- },
+ var thisCache, i,
+ isNode = elem.nodeType,
- // TODO: Now that all calls to _data and _removeData have been replaced
- // with direct calls to dataPriv methods, these can be deprecated.
- _data: function( elem, name, data ) {
- return dataPriv.access( elem, name, data );
- },
+ // See jQuery.data for more information
+ cache = isNode ? jQuery.cache : elem,
+ id = isNode ? elem[ jQuery.expando ] : jQuery.expando;
- _removeData: function( elem, name ) {
- dataPriv.remove( elem, name );
+ // If there is already no cache entry for this object, there is no
+ // purpose in continuing
+ if ( !cache[ id ] ) {
+ return;
}
-} );
-jQuery.fn.extend( {
- data: function( key, value ) {
- var i, name, data,
- elem = this[ 0 ],
+ if ( name ) {
+
+ thisCache = pvt ? cache[ id ] : cache[ id ].data;
+
+ if ( thisCache ) {
+
+ // Support array or space separated string names for data keys
+ if ( !jQuery.isArray( name ) ) {
+
+ // try the string as a key before any manipulation
+ if ( name in thisCache ) {
+ name = [ name ];
+ } else {
+
+ // split the camel cased version by spaces unless a key with the spaces exists
+ name = jQuery.camelCase( name );
+ if ( name in thisCache ) {
+ name = [ name ];
+ } else {
+ name = name.split( " " );
+ }
+ }
+ } else {
+
+ // If "name" is an array of keys...
+ // When data is initially created, via ("key", "val") signature,
+ // keys will be converted to camelCase.
+ // Since there is no way to tell _how_ a key was added, remove
+ // both plain key and camelCase key. #12786
+ // This will only penalize the array argument path.
+ name = name.concat( jQuery.map( name, jQuery.camelCase ) );
+ }
+
+ i = name.length;
+ while ( i-- ) {
+ delete thisCache[ name[ i ] ];
+ }
+
+ // If there is no data left in the cache, we want to continue
+ // and let the cache object itself get destroyed
+ if ( pvt ? !isEmptyDataObject( thisCache ) : !jQuery.isEmptyObject( thisCache ) ) {
+ return;
+ }
+ }
+ }
+
+ // See jQuery.data for more information
+ if ( !pvt ) {
+ delete cache[ id ].data;
+
+ // Don't destroy the parent cache unless the internal data object
+ // had been the only thing left in it
+ if ( !isEmptyDataObject( cache[ id ] ) ) {
+ return;
+ }
+ }
+
+ // Destroy the cache
+ if ( isNode ) {
+ jQuery.cleanData( [ elem ], true );
+
+ // Use delete when supported for expandos or `cache` is not a window per isWindow (#10080)
+ /* jshint eqeqeq: false */
+ } else if ( support.deleteExpando || cache != cache.window ) {
+ /* jshint eqeqeq: true */
+ delete cache[ id ];
+
+ // When all else fails, undefined
+ } else {
+ cache[ id ] = undefined;
+ }
+}
+
+jQuery.extend( {
+ cache: {},
+
+ // The following elements (space-suffixed to avoid Object.prototype collisions)
+ // throw uncatchable exceptions if you attempt to set expando properties
+ noData: {
+ "applet ": true,
+ "embed ": true,
+
+ // ...but Flash objects (which have this classid) *can* handle expandos
+ "object ": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"
+ },
+
+ hasData: function( elem ) {
+ elem = elem.nodeType ? jQuery.cache[ elem[ jQuery.expando ] ] : elem[ jQuery.expando ];
+ return !!elem && !isEmptyDataObject( elem );
+ },
+
+ data: function( elem, name, data ) {
+ return internalData( elem, name, data );
+ },
+
+ removeData: function( elem, name ) {
+ return internalRemoveData( elem, name );
+ },
+
+ // For internal use only.
+ _data: function( elem, name, data ) {
+ return internalData( elem, name, data, true );
+ },
+
+ _removeData: function( elem, name ) {
+ return internalRemoveData( elem, name, true );
+ }
+} );
+
+jQuery.fn.extend( {
+ data: function( key, value ) {
+ var i, name, data,
+ elem = this[ 0 ],
attrs = elem && elem.attributes;
+ // Special expections of .data basically thwart jQuery.access,
+ // so implement the relevant behavior ourselves
+
// Gets all values
if ( key === undefined ) {
if ( this.length ) {
- data = dataUser.get( elem );
+ data = jQuery.data( elem );
- if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) {
+ if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) {
i = attrs.length;
while ( i-- ) {
- // Support: IE 11 only
+ // Support: IE11+
// The attrs elements can be null (#14894)
if ( attrs[ i ] ) {
name = attrs[ i ].name;
if ( name.indexOf( "data-" ) === 0 ) {
- name = camelCase( name.slice( 5 ) );
+ name = jQuery.camelCase( name.slice( 5 ) );
dataAttr( elem, name, data[ name ] );
}
}
}
- dataPriv.set( elem, "hasDataAttrs", true );
+ jQuery._data( elem, "parsedAttrs", true );
}
}
@@ -4277,50 +4083,25 @@ jQuery.fn.extend( {
// Sets multiple values
if ( typeof key === "object" ) {
return this.each( function() {
- dataUser.set( this, key );
+ jQuery.data( this, key );
} );
}
- return access( this, function( value ) {
- var data;
-
- // The calling jQuery object (element matches) is not empty
- // (and therefore has an element appears at this[ 0 ]) and the
- // `value` parameter was not undefined. An empty jQuery object
- // will result in `undefined` for elem = this[ 0 ] which will
- // throw an exception if an attempt to read a data cache is made.
- if ( elem && value === undefined ) {
-
- // Attempt to get data from the cache
- // The key will always be camelCased in Data
- data = dataUser.get( elem, key );
- if ( data !== undefined ) {
- return data;
- }
-
- // Attempt to "discover" the data in
- // HTML5 custom data-* attrs
- data = dataAttr( elem, key );
- if ( data !== undefined ) {
- return data;
- }
-
- // We tried really hard, but the data doesn't exist.
- return;
- }
+ return arguments.length > 1 ?
- // Set the data...
+ // Sets one value
this.each( function() {
+ jQuery.data( this, key, value );
+ } ) :
- // We always store the camelCased key
- dataUser.set( this, key, value );
- } );
- }, null, value, arguments.length > 1, null, true );
+ // Gets one value
+ // Try to fetch any internally stored data first
+ elem ? dataAttr( elem, key, jQuery.data( elem, key ) ) : undefined;
},
removeData: function( key ) {
return this.each( function() {
- dataUser.remove( this, key );
+ jQuery.removeData( this, key );
} );
}
} );
@@ -4332,12 +4113,12 @@ jQuery.extend( {
if ( elem ) {
type = ( type || "fx" ) + "queue";
- queue = dataPriv.get( elem, type );
+ queue = jQuery._data( elem, type );
// Speed up dequeue by getting out quickly if this is just a lookup
if ( data ) {
- if ( !queue || Array.isArray( data ) ) {
- queue = dataPriv.access( elem, type, jQuery.makeArray( data ) );
+ if ( !queue || jQuery.isArray( data ) ) {
+ queue = jQuery._data( elem, type, jQuery.makeArray( data ) );
} else {
queue.push( data );
}
@@ -4371,7 +4152,7 @@ jQuery.extend( {
queue.unshift( "inprogress" );
}
- // Clear up the last queue stop function
+ // clear up the last queue stop function
delete hooks.stop;
fn.call( elem, next, hooks );
}
@@ -4381,12 +4162,14 @@ jQuery.extend( {
}
},
- // Not public - generate a queueHooks object, or return the current one
+ // not intended for public consumption - generates a queueHooks object,
+ // or returns the current one
_queueHooks: function( elem, type ) {
var key = type + "queueHooks";
- return dataPriv.get( elem, key ) || dataPriv.access( elem, key, {
+ return jQuery._data( elem, key ) || jQuery._data( elem, key, {
empty: jQuery.Callbacks( "once memory" ).add( function() {
- dataPriv.remove( elem, [ type + "queue", key ] );
+ jQuery._removeData( elem, type + "queue" );
+ jQuery._removeData( elem, key );
} )
} );
}
@@ -4411,7 +4194,7 @@ jQuery.fn.extend( {
this.each( function() {
var queue = jQuery.queue( this, type, data );
- // Ensure a hooks for this queue
+ // ensure a hooks for this queue
jQuery._queueHooks( this, type );
if ( type === "fx" && queue[ 0 ] !== "inprogress" ) {
@@ -4449,7 +4232,7 @@ jQuery.fn.extend( {
type = type || "fx";
while ( i-- ) {
- tmp = dataPriv.get( elements[ i ], type + "queueHooks" );
+ tmp = jQuery._data( elements[ i ], type + "queueHooks" );
if ( tmp && tmp.empty ) {
count++;
tmp.empty.add( resolve );
@@ -4459,65 +4242,82 @@ jQuery.fn.extend( {
return defer.promise( obj );
}
} );
-var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source;
-var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" );
+( function() {
+ var shrinkWrapBlocksVal;
-var cssExpand = [ "Top", "Right", "Bottom", "Left" ];
+ support.shrinkWrapBlocks = function() {
+ if ( shrinkWrapBlocksVal != null ) {
+ return shrinkWrapBlocksVal;
+ }
-var isHiddenWithinTree = function( elem, el ) {
+ // Will be changed later if needed.
+ shrinkWrapBlocksVal = false;
- // isHiddenWithinTree might be called from jQuery#filter function;
- // in that case, element will be second argument
- elem = el || elem;
+ // Minified: var b,c,d
+ var div, body, container;
+
+ body = document.getElementsByTagName( "body" )[ 0 ];
+ if ( !body || !body.style ) {
+
+ // Test fired too early or in an unsupported environment, exit.
+ return;
+ }
+
+ // Setup
+ div = document.createElement( "div" );
+ container = document.createElement( "div" );
+ container.style.cssText = "position:absolute;border:0;width:0;height:0;top:0;left:-9999px";
+ body.appendChild( container ).appendChild( div );
- // Inline style trumps all
- return elem.style.display === "none" ||
- elem.style.display === "" &&
+ // Support: IE6
+ // Check if elements with layout shrink-wrap their children
+ if ( typeof div.style.zoom !== "undefined" ) {
- // Otherwise, check computed style
- // Support: Firefox <=43 - 45
- // Disconnected elements can have computed display: none, so first confirm that elem is
- // in the document.
- jQuery.contains( elem.ownerDocument, elem ) &&
+ // Reset CSS: box-sizing; display; margin; border
+ div.style.cssText =
- jQuery.css( elem, "display" ) === "none";
+ // Support: Firefox<29, Android 2.3
+ // Vendor-prefix box-sizing
+ "-webkit-box-sizing:content-box;-moz-box-sizing:content-box;" +
+ "box-sizing:content-box;display:block;margin:0;border:0;" +
+ "padding:1px;width:1px;zoom:1";
+ div.appendChild( document.createElement( "div" ) ).style.width = "5px";
+ shrinkWrapBlocksVal = div.offsetWidth !== 3;
+ }
+
+ body.removeChild( container );
+
+ return shrinkWrapBlocksVal;
};
-var swap = function( elem, options, callback, args ) {
- var ret, name,
- old = {};
+} )();
+var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source;
- // Remember the old values, and insert the new ones
- for ( name in options ) {
- old[ name ] = elem.style[ name ];
- elem.style[ name ] = options[ name ];
- }
+var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" );
- ret = callback.apply( elem, args || [] );
- // Revert the old values
- for ( name in options ) {
- elem.style[ name ] = old[ name ];
- }
+var cssExpand = [ "Top", "Right", "Bottom", "Left" ];
- return ret;
-};
+var isHidden = function( elem, el ) {
+ // isHidden might be called from jQuery#filter function;
+ // in that case, element will be second argument
+ elem = el || elem;
+ return jQuery.css( elem, "display" ) === "none" ||
+ !jQuery.contains( elem.ownerDocument, elem );
+ };
function adjustCSS( elem, prop, valueParts, tween ) {
- var adjusted, scale,
+ var adjusted,
+ scale = 1,
maxIterations = 20,
currentValue = tween ?
- function() {
- return tween.cur();
- } :
- function() {
- return jQuery.css( elem, prop, "" );
- },
+ function() { return tween.cur(); } :
+ function() { return jQuery.css( elem, prop, "" ); },
initial = currentValue(),
unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ),
@@ -4527,33 +4327,30 @@ function adjustCSS( elem, prop, valueParts, tween ) {
if ( initialInUnit && initialInUnit[ 3 ] !== unit ) {
- // Support: Firefox <=54
- // Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144)
- initial = initial / 2;
-
// Trust units reported by jQuery.css
unit = unit || initialInUnit[ 3 ];
+ // Make sure we update the tween properties later on
+ valueParts = valueParts || [];
+
// Iteratively approximate from a nonzero starting point
initialInUnit = +initial || 1;
- while ( maxIterations-- ) {
-
- // Evaluate and update our best guess (doubling guesses that zero out).
- // Finish if the scale equals or crosses 1 (making the old*new product non-positive).
- jQuery.style( elem, prop, initialInUnit + unit );
- if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) {
- maxIterations = 0;
- }
- initialInUnit = initialInUnit / scale;
+ do {
- }
+ // If previous iteration zeroed out, double until we get *something*.
+ // Use string for doubling so we don't accidentally see scale as unchanged below
+ scale = scale || ".5";
- initialInUnit = initialInUnit * 2;
- jQuery.style( elem, prop, initialInUnit + unit );
+ // Adjust and apply
+ initialInUnit = initialInUnit / scale;
+ jQuery.style( elem, prop, initialInUnit + unit );
- // Make sure we update the tween properties later on
- valueParts = valueParts || [];
+ // Update scale, tolerating zero or NaN from tween.cur()
+ // Break the loop if scale is unchanged or perfect, or if we've just had enough.
+ } while (
+ scale !== ( scale = currentValue() / initial ) && scale !== 1 && --maxIterations
+ );
}
if ( valueParts ) {
@@ -4573,126 +4370,175 @@ function adjustCSS( elem, prop, valueParts, tween ) {
}
-var defaultDisplayMap = {};
+// Multifunctional method to get and set values of a collection
+// The value/s can optionally be executed if it's a function
+var access = function( elems, fn, key, value, chainable, emptyGet, raw ) {
+ var i = 0,
+ length = elems.length,
+ bulk = key == null;
+
+ // Sets many values
+ if ( jQuery.type( key ) === "object" ) {
+ chainable = true;
+ for ( i in key ) {
+ access( elems, fn, i, key[ i ], true, emptyGet, raw );
+ }
+
+ // Sets one value
+ } else if ( value !== undefined ) {
+ chainable = true;
+
+ if ( !jQuery.isFunction( value ) ) {
+ raw = true;
+ }
+
+ if ( bulk ) {
+
+ // Bulk operations run against the entire set
+ if ( raw ) {
+ fn.call( elems, value );
+ fn = null;
-function getDefaultDisplay( elem ) {
- var temp,
- doc = elem.ownerDocument,
- nodeName = elem.nodeName,
- display = defaultDisplayMap[ nodeName ];
+ // ...except when executing function values
+ } else {
+ bulk = fn;
+ fn = function( elem, key, value ) {
+ return bulk.call( jQuery( elem ), value );
+ };
+ }
+ }
- if ( display ) {
- return display;
+ if ( fn ) {
+ for ( ; i < length; i++ ) {
+ fn(
+ elems[ i ],
+ key,
+ raw ? value : value.call( elems[ i ], i, fn( elems[ i ], key ) )
+ );
+ }
+ }
}
- temp = doc.body.appendChild( doc.createElement( nodeName ) );
- display = jQuery.css( temp, "display" );
+ return chainable ?
+ elems :
- temp.parentNode.removeChild( temp );
+ // Gets
+ bulk ?
+ fn.call( elems ) :
+ length ? fn( elems[ 0 ], key ) : emptyGet;
+};
+var rcheckableType = ( /^(?:checkbox|radio)$/i );
- if ( display === "none" ) {
- display = "block";
- }
- defaultDisplayMap[ nodeName ] = display;
+var rtagName = ( /<([\w:-]+)/ );
- return display;
-}
+var rscriptType = ( /^$|\/(?:java|ecma)script/i );
-function showHide( elements, show ) {
- var display, elem,
- values = [],
- index = 0,
- length = elements.length;
+var rleadingWhitespace = ( /^\s+/ );
- // Determine new display value for elements that need to change
- for ( ; index < length; index++ ) {
- elem = elements[ index ];
- if ( !elem.style ) {
- continue;
- }
+var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|" +
+ "details|dialog|figcaption|figure|footer|header|hgroup|main|" +
+ "mark|meter|nav|output|picture|progress|section|summary|template|time|video";
- display = elem.style.display;
- if ( show ) {
- // Since we force visibility upon cascade-hidden elements, an immediate (and slow)
- // check is required in this first loop unless we have a nonempty display value (either
- // inline or about-to-be-restored)
- if ( display === "none" ) {
- values[ index ] = dataPriv.get( elem, "display" ) || null;
- if ( !values[ index ] ) {
- elem.style.display = "";
- }
- }
- if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) {
- values[ index ] = getDefaultDisplay( elem );
- }
- } else {
- if ( display !== "none" ) {
- values[ index ] = "none";
- // Remember what we're overwriting
- dataPriv.set( elem, "display", display );
- }
- }
- }
+function createSafeFragment( document ) {
+ var list = nodeNames.split( "|" ),
+ safeFrag = document.createDocumentFragment();
- // Set the display of the elements in a second loop to avoid constant reflow
- for ( index = 0; index < length; index++ ) {
- if ( values[ index ] != null ) {
- elements[ index ].style.display = values[ index ];
+ if ( safeFrag.createElement ) {
+ while ( list.length ) {
+ safeFrag.createElement(
+ list.pop()
+ );
}
}
-
- return elements;
+ return safeFrag;
}
-jQuery.fn.extend( {
- show: function() {
- return showHide( this, true );
- },
- hide: function() {
- return showHide( this );
- },
- toggle: function( state ) {
- if ( typeof state === "boolean" ) {
- return state ? this.show() : this.hide();
- }
- return this.each( function() {
- if ( isHiddenWithinTree( this ) ) {
- jQuery( this ).show();
- } else {
- jQuery( this ).hide();
- }
- } );
- }
-} );
-var rcheckableType = ( /^(?:checkbox|radio)$/i );
+( function() {
+ var div = document.createElement( "div" ),
+ fragment = document.createDocumentFragment(),
+ input = document.createElement( "input" );
+
+ // Setup
+ div.innerHTML = " a ";
+
+ // IE strips leading whitespace when .innerHTML is used
+ support.leadingWhitespace = div.firstChild.nodeType === 3;
+
+ // Make sure that tbody elements aren't automatically inserted
+ // IE will insert them into empty tables
+ support.tbody = !div.getElementsByTagName( "tbody" ).length;
+
+ // Make sure that link elements get serialized correctly by innerHTML
+ // This requires a wrapper element in IE
+ support.htmlSerialize = !!div.getElementsByTagName( "link" ).length;
+
+ // Makes sure cloning an html5 element does not cause problems
+ // Where outerHTML is undefined, this still works
+ support.html5Clone =
+ document.createElement( "nav" ).cloneNode( true ).outerHTML !== "<:nav>";
+
+ // Check if a disconnected checkbox will retain its checked
+ // value of true after appended to the DOM (IE6/7)
+ input.type = "checkbox";
+ input.checked = true;
+ fragment.appendChild( input );
+ support.appendChecked = input.checked;
+
+ // Make sure textarea (and checkbox) defaultValue is properly cloned
+ // Support: IE6-IE11+
+ div.innerHTML = "";
+ support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue;
+
+ // #11217 - WebKit loses check when the name is after the checked attribute
+ fragment.appendChild( div );
+
+ // Support: Windows Web Apps (WWA)
+ // `name` and `type` must use .setAttribute for WWA (#14901)
+ input = document.createElement( "input" );
+ input.setAttribute( "type", "radio" );
+ input.setAttribute( "checked", "checked" );
+ input.setAttribute( "name", "t" );
+
+ div.appendChild( input );
-var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]+)/i );
+ // Support: Safari 5.1, iOS 5.1, Android 4.x, Android 2.3
+ // old WebKit doesn't clone checked state correctly in fragments
+ support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked;
-var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i );
+ // Support: IE<9
+ // Cloned elements keep attachEvent handlers, we use addEventListener on IE9+
+ support.noCloneEvent = !!div.addEventListener;
+ // Support: IE<9
+ // Since attributes and properties are the same in IE,
+ // cleanData must set properties to undefined rather than use removeAttribute
+ div[ jQuery.expando ] = 1;
+ support.attributes = !div.getAttribute( jQuery.expando );
+} )();
// We have to close these tags to support XHTML (#13200)
var wrapMap = {
-
- // Support: IE <=9 only
option: [ 1, "", " " ],
+ legend: [ 1, "", " " ],
+ area: [ 1, "", " " ],
- // XHTML parsers do not magically insert elements in the
- // same way that tag soup parsers do. So we cannot shorten
- // this by omitting or other required elements.
+ // Support: IE8
+ param: [ 1, "", " " ],
thead: [ 1, "" ],
- col: [ 2, "" ],
tr: [ 2, "" ],
+ col: [ 2, "" ],
td: [ 3, "" ],
- _default: [ 0, "", "" ]
+ // IE6-8 can't serialize link, script, style, or any html5 (NoScope) tags,
+ // unless wrapped in a div with non-breaking characters in front of it.
+ _default: support.htmlSerialize ? [ 0, "", "" ] : [ 1, "X", "
" ]
};
-// Support: IE <=9 only
+// Support: IE8-IE9
wrapMap.optgroup = wrapMap.option;
wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
@@ -4700,52 +4546,66 @@ wrapMap.th = wrapMap.td;
function getAll( context, tag ) {
-
- // Support: IE <=9 - 11 only
- // Use typeof to avoid zero-argument method invocation on host objects (#15151)
- var ret;
-
- if ( typeof context.getElementsByTagName !== "undefined" ) {
- ret = context.getElementsByTagName( tag || "*" );
-
- } else if ( typeof context.querySelectorAll !== "undefined" ) {
- ret = context.querySelectorAll( tag || "*" );
-
- } else {
- ret = [];
- }
-
- if ( tag === undefined || tag && nodeName( context, tag ) ) {
- return jQuery.merge( [ context ], ret );
+ var elems, elem,
+ i = 0,
+ found = typeof context.getElementsByTagName !== "undefined" ?
+ context.getElementsByTagName( tag || "*" ) :
+ typeof context.querySelectorAll !== "undefined" ?
+ context.querySelectorAll( tag || "*" ) :
+ undefined;
+
+ if ( !found ) {
+ for ( found = [], elems = context.childNodes || context;
+ ( elem = elems[ i ] ) != null;
+ i++
+ ) {
+ if ( !tag || jQuery.nodeName( elem, tag ) ) {
+ found.push( elem );
+ } else {
+ jQuery.merge( found, getAll( elem, tag ) );
+ }
+ }
}
- return ret;
+ return tag === undefined || tag && jQuery.nodeName( context, tag ) ?
+ jQuery.merge( [ context ], found ) :
+ found;
}
// Mark scripts as having already been evaluated
function setGlobalEval( elems, refElements ) {
- var i = 0,
- l = elems.length;
-
- for ( ; i < l; i++ ) {
- dataPriv.set(
- elems[ i ],
+ var elem,
+ i = 0;
+ for ( ; ( elem = elems[ i ] ) != null; i++ ) {
+ jQuery._data(
+ elem,
"globalEval",
- !refElements || dataPriv.get( refElements[ i ], "globalEval" )
+ !refElements || jQuery._data( refElements[ i ], "globalEval" )
);
}
}
-var rhtml = /<|?\w+;/;
+var rhtml = /<|?\w+;/,
+ rtbody = / from table fragments
+ if ( !support.tbody ) {
+
+ // String was a , *may* have spurious
+ elem = tag === "table" && !rtbody.test( elem ) ?
+ tmp.firstChild :
+
+ // String was a bare or
+ wrap[ 1 ] === "" && !rtbody.test( elem ) ?
+ tmp :
+ 0;
+
+ j = elem && elem.childNodes.length;
+ while ( j-- ) {
+ if ( jQuery.nodeName( ( tbody = elem.childNodes[ j ] ), "tbody" ) &&
+ !tbody.childNodes.length ) {
+
+ elem.removeChild( tbody );
+ }
+ }
+ }
- // Remember the top-level container
- tmp = fragment.firstChild;
+ jQuery.merge( nodes, tmp.childNodes );
- // Ensure the created nodes are orphaned (#12392)
+ // Fix #12392 for WebKit and IE > 9
tmp.textContent = "";
+
+ // Fix #12392 for oldIE
+ while ( tmp.firstChild ) {
+ tmp.removeChild( tmp.firstChild );
+ }
+
+ // Remember the top-level container for proper cleanup
+ tmp = safe.lastChild;
}
}
}
- // Remove wrapper from fragment
- fragment.textContent = "";
+ // Fix #11356: Clear elements from fragment
+ if ( tmp ) {
+ safe.removeChild( tmp );
+ }
+
+ // Reset defaultChecked for any radios and checkboxes
+ // about to be appended to the DOM in IE 6/7 (#8060)
+ if ( !support.appendChecked ) {
+ jQuery.grep( getAll( nodes, "input" ), fixDefaultChecked );
+ }
i = 0;
while ( ( elem = nodes[ i++ ] ) ) {
@@ -4802,13 +4698,14 @@ function buildFragment( elems, context, scripts, selection, ignored ) {
if ( ignored ) {
ignored.push( elem );
}
+
continue;
}
contains = jQuery.contains( elem.ownerDocument, elem );
// Append to fragment
- tmp = getAll( fragment.appendChild( elem ), "script" );
+ tmp = getAll( safe.appendChild( elem ), "script" );
// Preserve script evaluation history
if ( contains ) {
@@ -4826,41 +4723,37 @@ function buildFragment( elems, context, scripts, selection, ignored ) {
}
}
- return fragment;
+ tmp = null;
+
+ return safe;
}
( function() {
- var fragment = document.createDocumentFragment(),
- div = fragment.appendChild( document.createElement( "div" ) ),
- input = document.createElement( "input" );
+ var i, eventName,
+ div = document.createElement( "div" );
- // Support: Android 4.0 - 4.3 only
- // Check state lost if the name is set (#11217)
- // Support: Windows Web Apps (WWA)
- // `name` and `type` must use .setAttribute for WWA (#14901)
- input.setAttribute( "type", "radio" );
- input.setAttribute( "checked", "checked" );
- input.setAttribute( "name", "t" );
+ // Support: IE<9 (lack submit/change bubble), Firefox (lack focus(in | out) events)
+ for ( i in { submit: true, change: true, focusin: true } ) {
+ eventName = "on" + i;
- div.appendChild( input );
+ if ( !( support[ i ] = eventName in window ) ) {
- // Support: Android <=4.1 only
- // Older WebKit doesn't clone checked state correctly in fragments
- support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked;
+ // Beware of CSP restrictions (https://developer.mozilla.org/en/Security/CSP)
+ div.setAttribute( eventName, "t" );
+ support[ i ] = div.attributes[ eventName ].expando === false;
+ }
+ }
- // Support: IE <=11 only
- // Make sure textarea (and checkbox) defaultValue is properly cloned
- div.innerHTML = "";
- support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue;
+ // Null elements to avoid leaks in IE.
+ div = null;
} )();
-var documentElement = document.documentElement;
-
-var
+var rformElems = /^(?:input|select|textarea)$/i,
rkeyEvent = /^key/,
rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/,
+ rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,
rtypenamespace = /^([^.]*)(?:\.(.+)|)/;
function returnTrue() {
@@ -4871,7 +4764,7 @@ function returnFalse() {
return false;
}
-// Support: IE <=9 only
+// Support: IE9
// See #13393 for more info
function safeActiveElement() {
try {
@@ -4949,11 +4842,10 @@ jQuery.event = {
global: {},
add: function( elem, types, handler, data, selector ) {
-
- var handleObjIn, eventHandle, tmp,
- events, t, handleObj,
- special, handlers, type, namespaces, origType,
- elemData = dataPriv.get( elem );
+ var tmp, events, t, handleObjIn,
+ special, eventHandle, handleObj,
+ handlers, type, namespaces, origType,
+ elemData = jQuery._data( elem );
// Don't attach events to noData or text/comment nodes (but allow plain objects)
if ( !elemData ) {
@@ -4967,12 +4859,6 @@ jQuery.event = {
selector = handleObjIn.selector;
}
- // Ensure that invalid selectors throw exceptions at attach time
- // Evaluate against documentElement in case elem is a non-element node (e.g., document)
- if ( selector ) {
- jQuery.find.matchesSelector( documentElement, selector );
- }
-
// Make sure that the handler has a unique ID, used to find/remove it later
if ( !handler.guid ) {
handler.guid = jQuery.guid++;
@@ -4987,13 +4873,19 @@ jQuery.event = {
// Discard the second event of a jQuery.event.trigger() and
// when an event is called after a page has unloaded
- return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ?
- jQuery.event.dispatch.apply( elem, arguments ) : undefined;
+ return typeof jQuery !== "undefined" &&
+ ( !e || jQuery.event.triggered !== e.type ) ?
+ jQuery.event.dispatch.apply( eventHandle.elem, arguments ) :
+ undefined;
};
+
+ // Add elem as a property of the handle fn to prevent a memory leak
+ // with IE non-native events
+ eventHandle.elem = elem;
}
// Handle multiple events separated by a space
- types = ( types || "" ).match( rnothtmlwhite ) || [ "" ];
+ types = ( types || "" ).match( rnotwhite ) || [ "" ];
t = types.length;
while ( t-- ) {
tmp = rtypenamespace.exec( types[ t ] ) || [];
@@ -5031,12 +4923,16 @@ jQuery.event = {
handlers = events[ type ] = [];
handlers.delegateCount = 0;
- // Only use addEventListener if the special events handler returns false
+ // Only use addEventListener/attachEvent if the special events handler returns false
if ( !special.setup ||
special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
+ // Bind the global event handler to the element
if ( elem.addEventListener ) {
- elem.addEventListener( type, eventHandle );
+ elem.addEventListener( type, eventHandle, false );
+
+ } else if ( elem.attachEvent ) {
+ elem.attachEvent( "on" + type, eventHandle );
}
}
}
@@ -5060,22 +4956,24 @@ jQuery.event = {
jQuery.event.global[ type ] = true;
}
+ // Nullify elem to prevent memory leaks in IE
+ elem = null;
},
// Detach an event or set of events from an element
remove: function( elem, types, handler, selector, mappedTypes ) {
-
- var j, origCount, tmp,
- events, t, handleObj,
- special, handlers, type, namespaces, origType,
- elemData = dataPriv.hasData( elem ) && dataPriv.get( elem );
+ var j, handleObj, tmp,
+ origCount, t, events,
+ special, handlers, type,
+ namespaces, origType,
+ elemData = jQuery.hasData( elem ) && jQuery._data( elem );
if ( !elemData || !( events = elemData.events ) ) {
return;
}
// Once for each type.namespace in types; type may be omitted
- types = ( types || "" ).match( rnothtmlwhite ) || [ "" ];
+ types = ( types || "" ).match( rnotwhite ) || [ "" ];
t = types.length;
while ( t-- ) {
tmp = rtypenamespace.exec( types[ t ] ) || [];
@@ -5130,29 +5028,174 @@ jQuery.event = {
}
}
- // Remove data and the expando if it's no longer used
+ // Remove the expando if it's no longer used
if ( jQuery.isEmptyObject( events ) ) {
- dataPriv.remove( elem, "handle events" );
+ delete elemData.handle;
+
+ // removeData also checks for emptiness and clears the expando if empty
+ // so use it instead of delete
+ jQuery._removeData( elem, "events" );
+ }
+ },
+
+ trigger: function( event, data, elem, onlyHandlers ) {
+ var handle, ontype, cur,
+ bubbleType, special, tmp, i,
+ eventPath = [ elem || document ],
+ type = hasOwn.call( event, "type" ) ? event.type : event,
+ namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : [];
+
+ cur = tmp = elem = elem || document;
+
+ // Don't do events on text and comment nodes
+ if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
+ return;
+ }
+
+ // focus/blur morphs to focusin/out; ensure we're not firing them right now
+ if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
+ return;
+ }
+
+ if ( type.indexOf( "." ) > -1 ) {
+
+ // Namespaced trigger; create a regexp to match event type in handle()
+ namespaces = type.split( "." );
+ type = namespaces.shift();
+ namespaces.sort();
+ }
+ ontype = type.indexOf( ":" ) < 0 && "on" + type;
+
+ // Caller can pass in a jQuery.Event object, Object, or just an event type string
+ event = event[ jQuery.expando ] ?
+ event :
+ new jQuery.Event( type, typeof event === "object" && event );
+
+ // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true)
+ event.isTrigger = onlyHandlers ? 2 : 3;
+ event.namespace = namespaces.join( "." );
+ event.rnamespace = event.namespace ?
+ new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) :
+ null;
+
+ // Clean up the event in case it is being reused
+ event.result = undefined;
+ if ( !event.target ) {
+ event.target = elem;
+ }
+
+ // Clone any incoming data and prepend the event, creating the handler arg list
+ data = data == null ?
+ [ event ] :
+ jQuery.makeArray( data, [ event ] );
+
+ // Allow special events to draw outside the lines
+ special = jQuery.event.special[ type ] || {};
+ if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) {
+ return;
+ }
+
+ // Determine event propagation path in advance, per W3C events spec (#9951)
+ // Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
+ if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {
+
+ bubbleType = special.delegateType || type;
+ if ( !rfocusMorph.test( bubbleType + type ) ) {
+ cur = cur.parentNode;
+ }
+ for ( ; cur; cur = cur.parentNode ) {
+ eventPath.push( cur );
+ tmp = cur;
+ }
+
+ // Only add window if we got to document (e.g., not plain obj or detached DOM)
+ if ( tmp === ( elem.ownerDocument || document ) ) {
+ eventPath.push( tmp.defaultView || tmp.parentWindow || window );
+ }
+ }
+
+ // Fire handlers on the event path
+ i = 0;
+ while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) {
+
+ event.type = i > 1 ?
+ bubbleType :
+ special.bindType || type;
+
+ // jQuery handler
+ handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] &&
+ jQuery._data( cur, "handle" );
+
+ if ( handle ) {
+ handle.apply( cur, data );
+ }
+
+ // Native handler
+ handle = ontype && cur[ ontype ];
+ if ( handle && handle.apply && acceptData( cur ) ) {
+ event.result = handle.apply( cur, data );
+ if ( event.result === false ) {
+ event.preventDefault();
+ }
+ }
+ }
+ event.type = type;
+
+ // If nobody prevented the default action, do it now
+ if ( !onlyHandlers && !event.isDefaultPrevented() ) {
+
+ if (
+ ( !special._default ||
+ special._default.apply( eventPath.pop(), data ) === false
+ ) && acceptData( elem )
+ ) {
+
+ // Call a native DOM method on the target with the same name name as the event.
+ // Can't use an .isFunction() check here because IE6/7 fails that test.
+ // Don't do default actions on window, that's where global variables be (#6170)
+ if ( ontype && elem[ type ] && !jQuery.isWindow( elem ) ) {
+
+ // Don't re-trigger an onFOO event when we call its FOO() method
+ tmp = elem[ ontype ];
+
+ if ( tmp ) {
+ elem[ ontype ] = null;
+ }
+
+ // Prevent re-triggering of the same event, since we already bubbled it above
+ jQuery.event.triggered = type;
+ try {
+ elem[ type ]();
+ } catch ( e ) {
+
+ // IE<9 dies on focus/blur to hidden element (#1486,#12518)
+ // only reproducible on winXP IE8 native, not IE9 in IE8 mode
+ }
+ jQuery.event.triggered = undefined;
+
+ if ( tmp ) {
+ elem[ ontype ] = tmp;
+ }
+ }
+ }
}
+
+ return event.result;
},
- dispatch: function( nativeEvent ) {
+ dispatch: function( event ) {
// Make a writable jQuery.Event from the native event object
- var event = jQuery.event.fix( nativeEvent );
+ event = jQuery.event.fix( event );
- var i, j, ret, matched, handleObj, handlerQueue,
- args = new Array( arguments.length ),
- handlers = ( dataPriv.get( this, "events" ) || {} )[ event.type ] || [],
+ var i, j, ret, matched, handleObj,
+ handlerQueue = [],
+ args = slice.call( arguments ),
+ handlers = ( jQuery._data( this, "events" ) || {} )[ event.type ] || [],
special = jQuery.event.special[ event.type ] || {};
// Use the fix-ed jQuery.Event rather than the (read-only) native event
args[ 0 ] = event;
-
- for ( i = 1; i < arguments.length; i++ ) {
- args[ i ] = arguments[ i ];
- }
-
event.delegateTarget = this;
// Call the preDispatch hook for the mapped type, and let it bail if desired
@@ -5201,95 +5244,160 @@ jQuery.event = {
},
handlers: function( event, handlers ) {
- var i, handleObj, sel, matchedHandlers, matchedSelectors,
+ var i, matches, sel, handleObj,
handlerQueue = [],
delegateCount = handlers.delegateCount,
cur = event.target;
+ // Support (at least): Chrome, IE9
// Find delegate handlers
- if ( delegateCount &&
-
- // Support: IE <=9
- // Black-hole SVG instance trees (trac-13180)
- cur.nodeType &&
-
- // Support: Firefox <=42
- // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861)
- // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click
- // Support: IE 11 only
- // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343)
- !( event.type === "click" && event.button >= 1 ) ) {
+ // Black-hole SVG instance trees (#13180)
+ //
+ // Support: Firefox<=42+
+ // Avoid non-left-click in FF but don't block IE radio events (#3861, gh-2343)
+ if ( delegateCount && cur.nodeType &&
+ ( event.type !== "click" || isNaN( event.button ) || event.button < 1 ) ) {
- for ( ; cur !== this; cur = cur.parentNode || this ) {
+ /* jshint eqeqeq: false */
+ for ( ; cur != this; cur = cur.parentNode || this ) {
+ /* jshint eqeqeq: true */
// Don't check non-elements (#13208)
// Don't process clicks on disabled elements (#6911, #8165, #11382, #11764)
- if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) {
- matchedHandlers = [];
- matchedSelectors = {};
+ if ( cur.nodeType === 1 && ( cur.disabled !== true || event.type !== "click" ) ) {
+ matches = [];
for ( i = 0; i < delegateCount; i++ ) {
handleObj = handlers[ i ];
// Don't conflict with Object.prototype properties (#13203)
sel = handleObj.selector + " ";
- if ( matchedSelectors[ sel ] === undefined ) {
- matchedSelectors[ sel ] = handleObj.needsContext ?
+ if ( matches[ sel ] === undefined ) {
+ matches[ sel ] = handleObj.needsContext ?
jQuery( sel, this ).index( cur ) > -1 :
jQuery.find( sel, this, null, [ cur ] ).length;
}
- if ( matchedSelectors[ sel ] ) {
- matchedHandlers.push( handleObj );
+ if ( matches[ sel ] ) {
+ matches.push( handleObj );
}
}
- if ( matchedHandlers.length ) {
- handlerQueue.push( { elem: cur, handlers: matchedHandlers } );
+ if ( matches.length ) {
+ handlerQueue.push( { elem: cur, handlers: matches } );
}
}
}
}
// Add the remaining (directly-bound) handlers
- cur = this;
if ( delegateCount < handlers.length ) {
- handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } );
+ handlerQueue.push( { elem: this, handlers: handlers.slice( delegateCount ) } );
}
return handlerQueue;
},
- addProp: function( name, hook ) {
- Object.defineProperty( jQuery.Event.prototype, name, {
- enumerable: true,
- configurable: true,
+ fix: function( event ) {
+ if ( event[ jQuery.expando ] ) {
+ return event;
+ }
- get: isFunction( hook ) ?
- function() {
- if ( this.originalEvent ) {
- return hook( this.originalEvent );
- }
- } :
- function() {
- if ( this.originalEvent ) {
- return this.originalEvent[ name ];
- }
- },
+ // Create a writable copy of the event object and normalize some properties
+ var i, prop, copy,
+ type = event.type,
+ originalEvent = event,
+ fixHook = this.fixHooks[ type ];
- set: function( value ) {
- Object.defineProperty( this, name, {
- enumerable: true,
- configurable: true,
- writable: true,
- value: value
- } );
+ if ( !fixHook ) {
+ this.fixHooks[ type ] = fixHook =
+ rmouseEvent.test( type ) ? this.mouseHooks :
+ rkeyEvent.test( type ) ? this.keyHooks :
+ {};
+ }
+ copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props;
+
+ event = new jQuery.Event( originalEvent );
+
+ i = copy.length;
+ while ( i-- ) {
+ prop = copy[ i ];
+ event[ prop ] = originalEvent[ prop ];
+ }
+
+ // Support: IE<9
+ // Fix target property (#1925)
+ if ( !event.target ) {
+ event.target = originalEvent.srcElement || document;
+ }
+
+ // Support: Safari 6-8+
+ // Target should not be a text node (#504, #13143)
+ if ( event.target.nodeType === 3 ) {
+ event.target = event.target.parentNode;
+ }
+
+ // Support: IE<9
+ // For mouse/key events, metaKey==false if it's undefined (#3368, #11328)
+ event.metaKey = !!event.metaKey;
+
+ return fixHook.filter ? fixHook.filter( event, originalEvent ) : event;
+ },
+
+ // Includes some event props shared by KeyEvent and MouseEvent
+ props: ( "altKey bubbles cancelable ctrlKey currentTarget detail eventPhase " +
+ "metaKey relatedTarget shiftKey target timeStamp view which" ).split( " " ),
+
+ fixHooks: {},
+
+ keyHooks: {
+ props: "char charCode key keyCode".split( " " ),
+ filter: function( event, original ) {
+
+ // Add which for key events
+ if ( event.which == null ) {
+ event.which = original.charCode != null ? original.charCode : original.keyCode;
}
- } );
+
+ return event;
+ }
},
- fix: function( originalEvent ) {
- return originalEvent[ jQuery.expando ] ?
- originalEvent :
- new jQuery.Event( originalEvent );
+ mouseHooks: {
+ props: ( "button buttons clientX clientY fromElement offsetX offsetY " +
+ "pageX pageY screenX screenY toElement" ).split( " " ),
+ filter: function( event, original ) {
+ var body, eventDoc, doc,
+ button = original.button,
+ fromElement = original.fromElement;
+
+ // Calculate pageX/Y if missing and clientX/Y available
+ if ( event.pageX == null && original.clientX != null ) {
+ eventDoc = event.target.ownerDocument || document;
+ doc = eventDoc.documentElement;
+ body = eventDoc.body;
+
+ event.pageX = original.clientX +
+ ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) -
+ ( doc && doc.clientLeft || body && body.clientLeft || 0 );
+ event.pageY = original.clientY +
+ ( doc && doc.scrollTop || body && body.scrollTop || 0 ) -
+ ( doc && doc.clientTop || body && body.clientTop || 0 );
+ }
+
+ // Add relatedTarget, if necessary
+ if ( !event.relatedTarget && fromElement ) {
+ event.relatedTarget = fromElement === event.target ?
+ original.toElement :
+ fromElement;
+ }
+
+ // Add which for click: 1 === left; 2 === middle; 3 === right
+ // Note: button is not normalized, so don't use it
+ if ( !event.which && button !== undefined ) {
+ event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) );
+ }
+
+ return event;
+ }
},
special: {
@@ -5303,8 +5411,15 @@ jQuery.event = {
// Fire native event if possible so blur/focus sequence is correct
trigger: function() {
if ( this !== safeActiveElement() && this.focus ) {
- this.focus();
- return false;
+ try {
+ this.focus();
+ return false;
+ } catch ( e ) {
+
+ // Support: IE<9
+ // If we error on focus to hidden element (#1486, #12518),
+ // let .trigger() run the handlers
+ }
}
},
delegateType: "focusin"
@@ -5322,7 +5437,7 @@ jQuery.event = {
// For checkbox, fire native event so checked state will be right
trigger: function() {
- if ( this.type === "checkbox" && this.click && nodeName( this, "input" ) ) {
+ if ( jQuery.nodeName( this, "input" ) && this.type === "checkbox" && this.click ) {
this.click();
return false;
}
@@ -5330,7 +5445,7 @@ jQuery.event = {
// For cross-browser consistency, don't fire native .click() on links
_default: function( event ) {
- return nodeName( event.target, "a" );
+ return jQuery.nodeName( event.target, "a" );
}
},
@@ -5344,17 +5459,60 @@ jQuery.event = {
}
}
}
- }
-};
+ },
+
+ // Piggyback on a donor event to simulate a different one
+ simulate: function( type, elem, event ) {
+ var e = jQuery.extend(
+ new jQuery.Event(),
+ event,
+ {
+ type: type,
+ isSimulated: true
+
+ // Previously, `originalEvent: {}` was set here, so stopPropagation call
+ // would not be triggered on donor event, since in our own
+ // jQuery.event.stopPropagation function we had a check for existence of
+ // originalEvent.stopPropagation method, so, consequently it would be a noop.
+ //
+ // Guard for simulated events was moved to jQuery.event.stopPropagation function
+ // since `originalEvent` should point to the original event for the
+ // constancy with other events and for more focused logic
+ }
+ );
-jQuery.removeEvent = function( elem, type, handle ) {
+ jQuery.event.trigger( e, null, elem );
- // This "if" is needed for plain objects
- if ( elem.removeEventListener ) {
- elem.removeEventListener( type, handle );
+ if ( e.isDefaultPrevented() ) {
+ event.preventDefault();
+ }
}
};
+jQuery.removeEvent = document.removeEventListener ?
+ function( elem, type, handle ) {
+
+ // This "if" is needed for plain objects
+ if ( elem.removeEventListener ) {
+ elem.removeEventListener( type, handle );
+ }
+ } :
+ function( elem, type, handle ) {
+ var name = "on" + type;
+
+ if ( elem.detachEvent ) {
+
+ // #8545, #7054, preventing memory leaks for custom events in IE6-8
+ // detachEvent needed property on element, by name of that event,
+ // to properly expose it to GC
+ if ( typeof elem[ name ] === "undefined" ) {
+ elem[ name ] = null;
+ }
+
+ elem.detachEvent( name, handle );
+ }
+ };
+
jQuery.Event = function( src, props ) {
// Allow instantiation without the 'new' keyword
@@ -5372,21 +5530,11 @@ jQuery.Event = function( src, props ) {
this.isDefaultPrevented = src.defaultPrevented ||
src.defaultPrevented === undefined &&
- // Support: Android <=2.3 only
+ // Support: IE < 9, Android < 4.0
src.returnValue === false ?
returnTrue :
returnFalse;
- // Create target properties
- // Support: Safari <=6 - 7 only
- // Target should not be a text node (#504, #13143)
- this.target = ( src.target && src.target.nodeType === 3 ) ?
- src.target.parentNode :
- src.target;
-
- this.currentTarget = src.currentTarget;
- this.relatedTarget = src.relatedTarget;
-
// Event type
} else {
this.type = src;
@@ -5398,28 +5546,36 @@ jQuery.Event = function( src, props ) {
}
// Create a timestamp if incoming event doesn't have one
- this.timeStamp = src && src.timeStamp || Date.now();
+ this.timeStamp = src && src.timeStamp || jQuery.now();
// Mark it as fixed
this[ jQuery.expando ] = true;
};
// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
-// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
+// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
jQuery.Event.prototype = {
constructor: jQuery.Event,
isDefaultPrevented: returnFalse,
isPropagationStopped: returnFalse,
isImmediatePropagationStopped: returnFalse,
- isSimulated: false,
preventDefault: function() {
var e = this.originalEvent;
this.isDefaultPrevented = returnTrue;
+ if ( !e ) {
+ return;
+ }
- if ( e && !this.isSimulated ) {
+ // If preventDefault exists, run it on the original event
+ if ( e.preventDefault ) {
e.preventDefault();
+
+ // Support: IE
+ // Otherwise set the returnValue property of the original event to false
+ } else {
+ e.returnValue = false;
}
},
stopPropagation: function() {
@@ -5427,16 +5583,25 @@ jQuery.Event.prototype = {
this.isPropagationStopped = returnTrue;
- if ( e && !this.isSimulated ) {
+ if ( !e || this.isSimulated ) {
+ return;
+ }
+
+ // If stopPropagation exists, run it on the original event
+ if ( e.stopPropagation ) {
e.stopPropagation();
}
+
+ // Support: IE
+ // Set the cancelBubble property of the original event to true
+ e.cancelBubble = true;
},
stopImmediatePropagation: function() {
var e = this.originalEvent;
this.isImmediatePropagationStopped = returnTrue;
- if ( e && !this.isSimulated ) {
+ if ( e && e.stopImmediatePropagation ) {
e.stopImmediatePropagation();
}
@@ -5444,74 +5609,13 @@ jQuery.Event.prototype = {
}
};
-// Includes all common event props including KeyEvent and MouseEvent specific props
-jQuery.each( {
- altKey: true,
- bubbles: true,
- cancelable: true,
- changedTouches: true,
- ctrlKey: true,
- detail: true,
- eventPhase: true,
- metaKey: true,
- pageX: true,
- pageY: true,
- shiftKey: true,
- view: true,
- "char": true,
- charCode: true,
- key: true,
- keyCode: true,
- button: true,
- buttons: true,
- clientX: true,
- clientY: true,
- offsetX: true,
- offsetY: true,
- pointerId: true,
- pointerType: true,
- screenX: true,
- screenY: true,
- targetTouches: true,
- toElement: true,
- touches: true,
-
- which: function( event ) {
- var button = event.button;
-
- // Add which for key events
- if ( event.which == null && rkeyEvent.test( event.type ) ) {
- return event.charCode != null ? event.charCode : event.keyCode;
- }
-
- // Add which for click: 1 === left; 2 === middle; 3 === right
- if ( !event.which && button !== undefined && rmouseEvent.test( event.type ) ) {
- if ( button & 1 ) {
- return 1;
- }
-
- if ( button & 2 ) {
- return 3;
- }
-
- if ( button & 4 ) {
- return 2;
- }
-
- return 0;
- }
-
- return event.which;
- }
-}, jQuery.event.addProp );
-
// Create mouseenter/leave events using mouseover/out and event-time checks
// so that event delegation works in jQuery.
// Do the same for pointerenter/pointerleave and pointerover/pointerout
//
// Support: Safari 7 only
// Safari sends mouseenter too often; see:
-// https://bugs.chromium.org/p/chromium/issues/detail?id=470258
+// https://code.google.com/p/chromium/issues/detail?id=470258
// for the description of the bug (it existed in older Chrome versions as well).
jQuery.each( {
mouseenter: "mouseover",
@@ -5541,6 +5645,171 @@ jQuery.each( {
};
} );
+// IE submit delegation
+if ( !support.submit ) {
+
+ jQuery.event.special.submit = {
+ setup: function() {
+
+ // Only need this for delegated form submit events
+ if ( jQuery.nodeName( this, "form" ) ) {
+ return false;
+ }
+
+ // Lazy-add a submit handler when a descendant form may potentially be submitted
+ jQuery.event.add( this, "click._submit keypress._submit", function( e ) {
+
+ // Node name check avoids a VML-related crash in IE (#9807)
+ var elem = e.target,
+ form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ?
+
+ // Support: IE <=8
+ // We use jQuery.prop instead of elem.form
+ // to allow fixing the IE8 delegated submit issue (gh-2332)
+ // by 3rd party polyfills/workarounds.
+ jQuery.prop( elem, "form" ) :
+ undefined;
+
+ if ( form && !jQuery._data( form, "submit" ) ) {
+ jQuery.event.add( form, "submit._submit", function( event ) {
+ event._submitBubble = true;
+ } );
+ jQuery._data( form, "submit", true );
+ }
+ } );
+
+ // return undefined since we don't need an event listener
+ },
+
+ postDispatch: function( event ) {
+
+ // If form was submitted by the user, bubble the event up the tree
+ if ( event._submitBubble ) {
+ delete event._submitBubble;
+ if ( this.parentNode && !event.isTrigger ) {
+ jQuery.event.simulate( "submit", this.parentNode, event );
+ }
+ }
+ },
+
+ teardown: function() {
+
+ // Only need this for delegated form submit events
+ if ( jQuery.nodeName( this, "form" ) ) {
+ return false;
+ }
+
+ // Remove delegated handlers; cleanData eventually reaps submit handlers attached above
+ jQuery.event.remove( this, "._submit" );
+ }
+ };
+}
+
+// IE change delegation and checkbox/radio fix
+if ( !support.change ) {
+
+ jQuery.event.special.change = {
+
+ setup: function() {
+
+ if ( rformElems.test( this.nodeName ) ) {
+
+ // IE doesn't fire change on a check/radio until blur; trigger it on click
+ // after a propertychange. Eat the blur-change in special.change.handle.
+ // This still fires onchange a second time for check/radio after blur.
+ if ( this.type === "checkbox" || this.type === "radio" ) {
+ jQuery.event.add( this, "propertychange._change", function( event ) {
+ if ( event.originalEvent.propertyName === "checked" ) {
+ this._justChanged = true;
+ }
+ } );
+ jQuery.event.add( this, "click._change", function( event ) {
+ if ( this._justChanged && !event.isTrigger ) {
+ this._justChanged = false;
+ }
+
+ // Allow triggered, simulated change events (#11500)
+ jQuery.event.simulate( "change", this, event );
+ } );
+ }
+ return false;
+ }
+
+ // Delegated event; lazy-add a change handler on descendant inputs
+ jQuery.event.add( this, "beforeactivate._change", function( e ) {
+ var elem = e.target;
+
+ if ( rformElems.test( elem.nodeName ) && !jQuery._data( elem, "change" ) ) {
+ jQuery.event.add( elem, "change._change", function( event ) {
+ if ( this.parentNode && !event.isSimulated && !event.isTrigger ) {
+ jQuery.event.simulate( "change", this.parentNode, event );
+ }
+ } );
+ jQuery._data( elem, "change", true );
+ }
+ } );
+ },
+
+ handle: function( event ) {
+ var elem = event.target;
+
+ // Swallow native change events from checkbox/radio, we already triggered them above
+ if ( this !== elem || event.isSimulated || event.isTrigger ||
+ ( elem.type !== "radio" && elem.type !== "checkbox" ) ) {
+
+ return event.handleObj.handler.apply( this, arguments );
+ }
+ },
+
+ teardown: function() {
+ jQuery.event.remove( this, "._change" );
+
+ return !rformElems.test( this.nodeName );
+ }
+ };
+}
+
+// Support: Firefox
+// Firefox doesn't have focus(in | out) events
+// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787
+//
+// Support: Chrome, Safari
+// focus(in | out) events fire after focus & blur events,
+// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order
+// Related ticket - https://code.google.com/p/chromium/issues/detail?id=449857
+if ( !support.focusin ) {
+ jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) {
+
+ // Attach a single capturing handler on the document while someone wants focusin/focusout
+ var handler = function( event ) {
+ jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) );
+ };
+
+ jQuery.event.special[ fix ] = {
+ setup: function() {
+ var doc = this.ownerDocument || this,
+ attaches = jQuery._data( doc, fix );
+
+ if ( !attaches ) {
+ doc.addEventListener( orig, handler, true );
+ }
+ jQuery._data( doc, fix, ( attaches || 0 ) + 1 );
+ },
+ teardown: function() {
+ var doc = this.ownerDocument || this,
+ attaches = jQuery._data( doc, fix ) - 1;
+
+ if ( !attaches ) {
+ doc.removeEventListener( orig, handler, true );
+ jQuery._removeData( doc, fix );
+ } else {
+ jQuery._data( doc, fix, attaches );
+ }
+ }
+ };
+ } );
+}
+
jQuery.fn.extend( {
on: function( types, selector, data, fn ) {
@@ -5584,97 +5853,154 @@ jQuery.fn.extend( {
return this.each( function() {
jQuery.event.remove( this, types, fn, selector );
} );
+ },
+
+ trigger: function( type, data ) {
+ return this.each( function() {
+ jQuery.event.trigger( type, data, this );
+ } );
+ },
+ triggerHandler: function( type, data ) {
+ var elem = this[ 0 ];
+ if ( elem ) {
+ return jQuery.event.trigger( type, data, elem, true );
+ }
}
} );
-var
-
- /* eslint-disable max-len */
-
- // See https://github.com/eslint/eslint/issues/3229
- rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([a-z][^\/\0>\x20\t\r\n\f]*)[^>]*)\/>/gi,
-
- /* eslint-enable */
+var rinlinejQuery = / jQuery\d+="(?:null|\d+)"/g,
+ rnoshimcache = new RegExp( "<(?:" + nodeNames + ")[\\s/>]", "i" ),
+ rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi,
- // Support: IE <=10 - 11, Edge 12 - 13 only
+ // Support: IE 10-11, Edge 10240+
// In IE/Edge using regex groups here causes severe slowdowns.
// See https://connect.microsoft.com/IE/feedback/details/1736512/
rnoInnerhtml = /
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+