<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>JSDoc: Source: paginator.js</title> <script src="scripts/prettify/prettify.js"> </script> <script src="scripts/prettify/lang-css.js"> </script> <!--[if lt IE 9]> <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script> <![endif]--> <link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css"> <link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css"> </head> <body> <div id="main"> <h1 class="page-title">Source: paginator.js</h1> <section> <article> <pre class="prettyprint source linenums"><code>import EventEmitter from 'event-emitter'; import { search } from './search'; import { assign } from './utils'; /** * @module opensearch/paginator */ /** * Event emitter to track the progress of paged searches. * * @fires PagedSearchProgressEmitter#page * @fires PagedSearchProgressEmitter#success * @fires PagedSearchProgressEmitter#error */ class PagedSearchProgressEmitter extends EventEmitter { } /** * Search Progress Event * * @event module:opensearch/paginator~PagedSearchProgressEmitter#page * @type {SearchResult} */ /** * Search Success Event * * @event module:opensearch/paginator~PagedSearchProgressEmitter#success * @type {SearchResult} */ /** * Search Error Event * * @event module:opensearch/paginator~PagedSearchProgressEmitter#error * @type {Error} */ export { PagedSearchProgressEmitter }; function isCancellable(promise) { return promise && typeof promise.cancel === 'function' && !promise.isCancelled(); } function combinePages(pages) { const firstPage = pages[0]; const records = pages.reduce((rs, page) => rs.concat(page.records), []); return { totalResults: firstPage.totalResults, startIndex: firstPage.startIndex, itemsPerPage: firstPage.itemsPerPage, records, }; } /** * Class to help with paginated results of an OpenSearch service. */ export class OpenSearchPaginator { /** * @param {OpenSearchUrl} url The URL to perform all subsequent requests on. * @param {object} parameters Search parameters. * @param {object} [options] Additional options for the pagination * @param {boolean} [options.useCache=true] Whether response pages shall be cached. * @param {int} [options.preferredItemsPerPage=undefined] The preferred page size. This * defaults to the advertised * default of the URL. * @param {boolean} [options.preferStartIndex=true] Whether the paging shall be done * using the `startIndex` parameter * (the default) or the `startPage`. * @param {int} [options.baseOffset=0] The base index offset to apply. This option * is useful when resuming a consecutive search. * @param {int} [options.maxUrlLength=undefined] The maximum URL length. Forwarded to * [search]{@link module:opensearch/search.search}. */ constructor(url, parameters, { useCache = true, preferredItemsPerPage = undefined, preferStartIndex = true, baseOffset = 0, maxUrlLength = undefined } = {}) { this._url = url; this._parameters = parameters; this._cache = useCache ? {} : null; this._preferredItemsPerPage = preferredItemsPerPage; this._preferStartIndex = preferStartIndex; this._baseOffset = baseOffset; this._maxUrlLength = maxUrlLength; this._serverItemsPerPage = undefined; this._totalResults = undefined; } /** * Fetch a single page of the result set. Sets the server side items per page, * when the result is available. * @param {int} [pageIndex=0] The index of the page to be fetched. * @param {int} [maxCount=undefined] The maximum count of objects to be retrieved. * @returns {Promise<SearchResult>} The search result. * @fulfill {module:opensearch/formats~SearchResult} The search result */ fetchPage(pageIndex = 0, maxCount = undefined) { // TODO: implement caching of whole pages // if (this._cache && this._cache[pageIndex]) { // return this._cache[pageIndex]; // } const parameters = assign({}, this._parameters); const pageSize = this.getActualPageSize(); if (pageSize && maxCount) { parameters.count = Math.min(maxCount, pageSize); } else if (pageSize) { parameters.count = pageSize; } else if (maxCount) { parameters.count = maxCount; } if (this._preferStartIndex) { if (typeof pageSize === 'undefined') { parameters.startIndex = this._baseOffset + this._url.indexOffset; } else { parameters.startIndex = this._baseOffset + (pageSize * pageIndex) + this._url.indexOffset; } } else { parameters.startPage = pageIndex + this._url.pageOffset; } return search(this._url, parameters, null, false, this._maxUrlLength) .then((result) => { this._totalResults = result.totalResults; if (!this._serverItemsPerPage && result.itemsPerPage) { this._serverItemsPerPage = result.itemsPerPage; } return result; }); } /** * Fetches all pages from the URL. A probing request is sent to determine how * many succeeding requests have to be sent. * @returns {Promise<SearchResult[]>} The async result of all the pages in the * search. * @fulfill {module:opensearch/formats~SearchResult[]} The search result pages */ fetchAllPages() { return this.fetchPage() .then((firstPage) => { const pageCount = this.getPageCount(); const requests = [firstPage]; for (let i = 1; i < pageCount; ++i) { requests.push(this.fetchPage(i)); } return Promise.all(requests); }); } /** * Convenience method to get the records of all pages in a single result array * @returns {Promise<SearchResult>} The records of all the pages in the search. * @fulfill {module:opensearch/formats~SearchResult} The search result */ fetchAllRecords() { return this.fetchAllPages() .then((pages) => { const firstPage = pages[0]; const records = pages.reduce((rs, page) => rs.concat(page.records), []); return { totalResults: firstPage.totalResults, startIndex: firstPage.startIndex, itemsPerPage: firstPage.itemsPerPage, records, }; }); } /** * Fetches the first X records of a search in a single search result. * @param {int} maxCount The maximum number of records to fetch. * @returns {Promise<SearchResult>} The resulting records as a promise. * @fulfill {module:opensearch/formats~SearchResult} The search result */ fetchFirstRecords(maxCount) { // Get the first page return this.fetchPage(0, maxCount) .then((firstPage) => { // check if all records fit in the first page (then return this page) if (firstPage.totalResults <= firstPage.itemsPerPage) { // return if we already have all records return firstPage; } // fetch other pages until we have the required count const requests = [firstPage]; const usedMaxCount = Math.min( maxCount, (firstPage.totalResults - firstPage.startIndex) + this._url.indexOffset ); // determine the number of pages and issue a request for each const numPages = Math.ceil(usedMaxCount / firstPage.itemsPerPage); for (let i = 1; i < numPages; ++i) { let count = firstPage.itemsPerPage; if (firstPage.itemsPerPage * (i + 1) > usedMaxCount) { count = usedMaxCount - (firstPage.itemsPerPage * i); } requests.push(this.fetchPage(i, count)); } return Promise.all(requests) .then(pages => combinePages(pages)); }); } /** * Fetches the first X records of a search in a single search result. * Use this method when the progressive results are wished and not just a * final result. * @param {int} maxCount The maximum number of records to fetch. * @param {boolean} preserveOrder Whether the results must be returned in the * order received from the server, or the * originally requested order. * @returns {module:opensearch/paginator~PagedSearchProgressEmitter} The resulting * records as a promise. */ searchFirstRecords(maxCount = undefined, preserveOrder = true) { // Get the first page const emitter = new PagedSearchProgressEmitter(); // start requesting the first page const request = this.fetchPage(0, maxCount); let requests = [request]; // cancel requests when issued a cancel event emitter.on('cancel', () => { requests.forEach((req) => { if (isCancellable(req)) { req.cancel(); } }); }); let hasError = false; const onError = (error) => { hasError = true; emitter.emit('error', error); return error; }; request .catch(onError) .then((firstPage) => { if (hasError) { throw firstPage; } // save the first page as a resolved promise (for later use when // collecting results in a uniform fashion) const newRequests = [Promise.resolve(firstPage)]; const usedMaxCount = maxCount ? Math.min( maxCount, (firstPage.totalResults - firstPage.startIndex) + this._url.indexOffset ) : firstPage.totalResults; // determine the number of pages and issue a request for each const numPages = Math.ceil(usedMaxCount / firstPage.itemsPerPage); for (let i = 1; i < numPages; ++i) { let count = firstPage.itemsPerPage; if (firstPage.itemsPerPage * (i + 1) > usedMaxCount) { count = usedMaxCount - (firstPage.itemsPerPage * i); } newRequests.push(this.fetchPage(i, count)); } // save the requests in the global variable to allow cancellation/result collection requests = newRequests; const pages = Array(requests.length); if (preserveOrder) { // when the order of the the responses is important, the algorithm is // more complex let index = 0; const allRequests = Array.from(requests); const onPage = (page) => { if (hasError) { return; } pages[index] = page; index += 1; emitter.emit('page', page); const promise = allRequests.shift(); if (promise) { promise.then(onPage, onError); } else { emitter.emit('success', combinePages(pages)); // TODO: } }; allRequests.shift().then(onPage, onError); } else { let successCount = 0; requests.forEach((req, index) => { req.then((page) => { if (hasError) { return; } successCount += 1; pages[index] = page; if (successCount === requests.length) { emitter.emit('success', combinePages(pages)); // TODO } }, onError); }); } }); return emitter; } /** * Returns the actual page size. * @returns {int} The computed page size. */ getActualPageSize() { if (this._preferredItemsPerPage && this._serverItemsPerPage) { return Math.min(this._preferredItemsPerPage, this._serverItemsPerPage); } else if (this._serverItemsPerPage) { return this._serverItemsPerPage; } else if (this._preferredItemsPerPage) { return this._preferredItemsPerPage; } const countParam = this._url.getParameter('count'); if (countParam) { if (typeof countParam.maxExclusive !== 'undefined') { return countParam.maxExclusive - 1; } else if (countParam.maxInclusive) { return countParam.maxInclusive; } } return undefined; } /** * Returns the computed number of pages, which is available once the first page * was received. * @returns {int} The number of pages. */ getPageCount() { const pageSize = this.getActualPageSize(); if (!this._totalResults) { return this._totalResults; } else if (!pageSize) { return undefined; } return Math.ceil(this._totalResults / pageSize); } } </code></pre> </article> </section> </div> <nav> <h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-opensearch.html">opensearch</a></li><li><a href="module-opensearch_config.html">opensearch/config</a></li><li><a href="module-opensearch_error.html">opensearch/error</a></li><li><a href="module-opensearch_formats.html">opensearch/formats</a></li><li><a href="module-opensearch_formats_atom.html">opensearch/formats/atom</a></li><li><a href="module-opensearch_formats_geojson.html">opensearch/formats/geojson</a></li><li><a href="module-opensearch_formats_rss.html">opensearch/formats/rss</a></li><li><a href="module-opensearch_formats_suggestions-json.html">opensearch/formats/suggestions-json</a></li><li><a href="module-opensearch_paginator.html">opensearch/paginator</a></li><li><a href="module-opensearch_parameter.html">opensearch/parameter</a></li><li><a href="module-opensearch_search.html">opensearch/search</a></li><li><a href="module-opensearch_url.html">opensearch/url</a></li><li><a href="module-opensearch_utils.html">opensearch/utils</a></li></ul><h3>Classes</h3><ul><li><a href="module-opensearch_formats_atom-AtomFormat.html">AtomFormat</a></li><li><a href="module-opensearch_formats_geojson-GeoJSONFormat.html">GeoJSONFormat</a></li><li><a href="module-opensearch_formats_rss-RSSFormat.html">RSSFormat</a></li><li><a href="module-opensearch_formats_suggestions-json-SuggestionsJSONFormat.html">SuggestionsJSONFormat</a></li><li><a href="module-opensearch_paginator.OpenSearchPaginator.html">OpenSearchPaginator</a></li><li><a href="module-opensearch_paginator-PagedSearchProgressEmitter.html">PagedSearchProgressEmitter</a></li><li><a href="module-opensearch_parameter.OpenSearchParameter.html">OpenSearchParameter</a></li><li><a href="module-opensearch_url.OpenSearchUrl.html">OpenSearchUrl</a></li><li><a href="OpenSearchDescription.html">OpenSearchDescription</a></li><li><a href="OpenSearchService.html">OpenSearchService</a></li></ul><h3>Events</h3><ul><li><a href="module-opensearch_paginator-PagedSearchProgressEmitter.html#event:error">error</a></li><li><a href="module-opensearch_paginator-PagedSearchProgressEmitter.html#event:page">page</a></li><li><a href="module-opensearch_paginator-PagedSearchProgressEmitter.html#event:success">success</a></li></ul><h3>Interfaces</h3><ul><li><a href="module-opensearch_formats.FormatInterface.html">FormatInterface</a></li></ul> </nav> <br class="clear"> <footer> Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Wed Jan 10 2018 13:14:22 GMT+0100 (CET) </footer> <script> prettyPrint(); </script> <script src="scripts/linenumber.js"> </script> </body> </html>