Is there a way to implement back pagination with firestore?
I am struggling to implement pagination with firestore, and there are limited firestore queries for it. Forward pagination can be made by startAt and limit method, that is ok. But back pagination can't be easily done, because we only have endBefore, and endAt method, and how can we get last n elements from given document? I know realtime database have method limitToLast. Is there any query like this for firestore? (Also I need to implement multiple sorting, so getting last documents with "ASC" or "DESC" sorting will not work)
Help much appreciated.
Thanks!
The equivalent to the limitToLast(...) operation from the Firebase Realtime Database in Cloud Firestore is to order the data descending (which is possible in Firestore) and then just limit(...). If you're having problems implement this, update your question to show what you've done.
I agree that this is a sub-optimal API for back-pagination, since you're receiving the items in reverse order.
Simpler answer: Firestore now has .limitToLast(), which works exactly as you think it does. Used in my own (guess I need to publish it soon) Firestore Wrapper:
//////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////
// *** Paginate API ***
export const PAGINATE_INIT = 0;
export const PAGINATE_PENDING = -1;
export const PAGINATE_UPDATED = 1;
export const PAGINATE_DEFAULT = 10;
export const PAGINATE_CHOICES = [10, 25, 50, 100, 250, 500];
/**
* #classdesc
* An object to allow for paginating a table read from Firestore. REQUIRES a sorting choice
* #property {Query} Query that forms basis for the table read
* #property {number} limit page size
* #property {QuerySnapshot} snapshot last successful snapshot/page fetched
* #property {enum} status status of pagination object
* #method PageForward pages the fetch forward
* #method PageBack pages the fetch backward
*/
export class PaginateFetch {
Query = null;
limit = PAGINATE_DEFAULT;
snapshot = null;
status = null; // -1 pending; 0 uninitialize; 1 updated;
/**
* ----------------------------------------------------------------------
* #constructs PaginateFetch constructs an object to paginate through large
* Firestore Tables
* #param {string} table a properly formatted string representing the requested collection
* - always an ODD number of elements
* #param {array} filterArray an (optional) 3xn array of filter(i.e. "where") conditions
* #param {array} sortArray a 2xn array of sort (i.e. "orderBy") conditions
* #param {ref} ref (optional) allows "table" parameter to reference a sub-collection
* of an existing document reference (I use a LOT of structered collections)
*
* The array is assumed to be sorted in the correct order -
* i.e. filterArray[0] is added first; filterArray[length-1] last
* returns data as an array of objects (not dissimilar to Redux State objects)
* with both the documentID and documentReference added as fields.
* #param {number} limit (optional)
* #returns {PaginateFetchObject}
**********************************************************************/
constructor(
table,
filterArray = null,
sortArray = null,
ref = null,
limit = PAGINATE_DEFAULT
) {
const db = ref ? ref : fdb;
this.limit = limit;
this.Query = sortQuery(
filterQuery(db.collection(table), filterArray),
sortArray
);
this.status = PAGINATE_INIT;
}
/**
* #method Page
* #returns Promise of a QuerySnapshot
*/
PageForward = () => {
const runQuery = this.snapshot
? this.Query.startAfter(_.last(this.snapshot.docs))
: this.Query;
this.status = PAGINATE_PENDING;
return runQuery
.limit(this.limit)
.get()
.then((QuerySnapshot) => {
this.status = PAGINATE_UPDATED;
//*IF* documents (i.e. haven't gone beyond start)
if (!QuerySnapshot.empty) {
//then update document set, and execute callback
//return Promise.resolve(QuerySnapshot);
this.snapshot = QuerySnapshot;
}
return this.snapshot.docs.map((doc) => {
return {
...doc.data(),
Id: doc.id,
ref: doc.ref
};
});
});
};
PageBack = () => {
const runQuery = this.snapshot
? this.Query.endBefore(this.snapshot.docs[0])
: this.Query;
this.status = PAGINATE_PENDING;
return runQuery
.limitToLast(this.limit)
.get()
.then((QuerySnapshot) => {
this.status = PAGINATE_UPDATED;
//*IF* documents (i.e. haven't gone back ebfore start)
if (!QuerySnapshot.empty) {
//then update document set, and execute callback
this.snapshot = QuerySnapshot;
}
return this.snapshot.docs.map((doc) => {
return {
...doc.data(),
Id: doc.id,
ref: doc.ref
};
});
});
};
}
/**
* ----------------------------------------------------------------------
* #function filterQuery
* builds and returns a query built from an array of filter (i.e. "where")
* consitions
* #param {Query} query collectionReference or Query to build filter upong
* #param {array} filterArray an (optional) 3xn array of filter(i.e. "where") conditions
* #returns Firestor Query object
*/
export const filterQuery = (query, filterArray = null) => {
return filterArray
? filterArray.reduce((accQuery, filter) => {
return accQuery.where(filter.fieldRef, filter.opStr, filter.value);
}, query)
: query;
};
/**
* ----------------------------------------------------------------------
* #function sortQuery
* builds and returns a query built from an array of filter (i.e. "where")
* consitions
* #param {Query} query collectionReference or Query to build filter upong
* #param {array} sortArray an (optional) 2xn array of sort (i.e. "orderBy") conditions
* #returns Firestor Query object
*/
export const sortQuery = (query, sortArray = null) => {
return sortArray
? sortArray.reduce((accQuery, sortEntry) => {
return accQuery.orderBy(sortEntry.fieldRef, sortEntry.dirStr || "asc");
//note "||" - if dirStr is not present(i.e. falsy) default to "asc"
}, query)
: query;
};
I also have the equivalent for CollectionGroup queries, and listeners for each as well.
I was running into this same issue, and not understanding why using limit with endAt wasn't returning the results I desired. I was attempting to implement a list in which you could paginate in both directions, first forward and then backward back to the start of the list.
To remedy the situation I decided to just cache the startAfter DocumentSnapshot for each page so that one can move both directions, in this way I never have to use endAt. The only time this will become an issue is if the collection of documents shifts or changes while the user is on a page other than the first page, but by returning to the first page it will reset to the beginning of the collection.
Yes. Building upon Frank's answer...
Have something like this in your query...
if (this.next) {
// if next, orderBy field descending, start after last field
q.orderBy('field', 'desc');
q.startAfter(this.marker);
} else if (this.prev) {
// if prev, orderBy field ascending, start after first field
q.orderBy('field', 'asc');
q.startAfter(this.marker);
} else {
// otherwise just display first page results normally
q.orderBy('field', 'desc');
}
q.limit(this.pageSize);
and then reverse it when you get the query...
this.testsCollection
.valueChanges({ idField: 'id' })
.pipe(
tap(results => {
if (this.prev) {
// if previous, need to reverse the results...
results.reverse();
}
})
)
I just want to share my code for Firestore pagination.
I am using react hooks w/ NextJS.
You will need to have "useFirestoreQuery" hook, which can be found here.
https://usehooks.com/useFirestoreQuery/
So here is my set up.
/* Context User */
const {user} = useUser()
/* States */
const [query, setQuery] = useState(null)
const [ref, setRef] = useState(null)
const [reverse, setReverse] = useState(false)
const [limit, setLimit] = useState(2)
const [lastID, setLastID] = useState(null)
const [firstID, setFirstID] = useState(null)
const [page, setPage] = useState(1)
/* Query Hook */
const fireCollection = useFirestoreQuery(query)
/* Set Ref, **When firebase initialized** */
useEffect(() => {
user?.uid &&
setRef(
firebase
.firestore()
.collection('products')
.where('type', '==', 'vaporizers')
)
}, [user])
/* Initial Query, **When ref set** */
useEffect(() => {
ref && setQuery(ref.orderBy('id', 'asc').limit(limit))
}, [ref])
/* Next Page */
const nextPage = useCallback(() => {
setPage((p) => parseInt(p) + 1)
setReverse(false)
setQuery(ref.orderBy('id', 'asc').startAfter(lastID).limit(limit))
}, [lastID, limit])
/* Prev Page */
const prevPage = useCallback(() => {
setPage((p) => parseInt(p) - 1)
setReverse(true)
setQuery(ref.orderBy('id', 'desc').startAfter(firstID).limit(limit))
}, [firstID, limit])
/* Product List */
const ProductList = ({fireCollection}) => {
const [products, setProducts] = useState([])
useEffect(() => {
let tempProducts = []
let tempIDs = []
const {data} = fireCollection
for (const key in data) {
const product = data[key]
tempIDs.push(product.id)
tempProducts.push(<ProductRow {...{product}} key={key} />)
}
if (reverse) {
tempProducts.reverse()
tempIDs.reverse()
}
setFirstID(tempIDs[0])
setLastID(tempIDs.pop())
setProducts(tempProducts)
}, [fireCollection])
return products
}
I moved the 'ProductList' outside of the component with a context provider, but this is the gist of it.
Note.
If you are looking for the total number of products. I suggest you keep up with the totals with these cloud functions. You will need to store your totals in a separate collection. I call mine 'shortcuts'.
exports.incrementProducts = functions.firestore
.document('products/{id}')
.onCreate(async (snap, context) => {
const createdProduct = snap.data()
/* Increment a shortcut collection that holds the totals to your products */
})
exports.decrementProducts = functions.firestore
.document('products/{id}')
.onDelete((snap, context) => {
const deletedProduct = snap.data()
/* Decrement a shortcut collection that holds the totals to your products */
})
Don't Forget
Make sure you set your indexes for all this to work. Here is what mine looks like.
Related
Spartacus lists 10 products in the pagination defualt on its product listing page. But I wanted it to be 12.
In my .ts file normally for PLP
protected productListComponentServiceDefault: ProductListComponentService,
I was using the structure.
but I created "CutomProductListComponentService" by customizing the "ProductListComponentService" file.
protected defaultPageSize = 12;
I did what I wanted by adding. Pagination artok 12 works.
But after this customization, the "SORTING" operation within the page does not work anymore.
Inside CutomProductListComponentService I added the following:
import { Injectable } from '#angular/core';
import { ActivatedRoute, Router } from '#angular/router';
import {
ActivatedRouterStateSnapshot,
CurrencyService,
LanguageService,
ProductSearchPage,
ProductSearchService,
RouterState,
RoutingService,
} from '#spartacus/core';
import { combineLatest, Observable, using } from 'rxjs';
import {
debounceTime,
distinctUntilChanged,
filter,
map,
shareReplay,
tap,
} from 'rxjs/operators';
// #ts-ignore
import { ProductListRouteParams, SearchCriteria } from './product-list.model';
/**
* The `ProductListComponentService` is used to search products. The service is used
* on the Product Listing Page, for listing products and the facet navigation.
*
* The service exposes the product search results based on the category and search
* route parameters. The route parameters are used to query products by the help of
* the `ProductSearchService`.
*/
#Injectable({ providedIn: 'root' })
export class CustomProductListComponentService {
// TODO: make it configurable
protected defaultPageSize = 12;
protected readonly RELEVANCE_ALLCATEGORIES = ':relevance:allCategories:';
constructor(
protected productSearchService: ProductSearchService,
protected routing: RoutingService,
protected activatedRoute: ActivatedRoute,
protected currencyService: CurrencyService,
protected languageService: LanguageService,
protected router: Router
) {}
/**
* Emits the search results for the current search query.
*
* The `searchResults$` is _not_ concerned with querying, it only observes the
* `productSearchService.getResults()`
*/
protected searchResults$: Observable<
ProductSearchPage
> = this.productSearchService
.getResults()
.pipe(filter((searchResult) => Object.keys(searchResult).length > 0));
/**
* Observes the route and performs a search on each route change.
*
* Context changes, such as language and currencies are also taken
* into account, so that the search is performed again.
*/
protected searchByRouting$: Observable<
ActivatedRouterStateSnapshot
> = combineLatest([
this.routing.getRouterState().pipe(
distinctUntilChanged((x, y) => {
// router emits new value also when the anticipated `nextState` changes
// but we want to perform search only when current url changes
return x.state.url === y.state.url;
})
),
...this.siteContext,
]).pipe(
debounceTime(0),
map(([routerState, ..._context]) => (routerState as RouterState).state),
tap((state: ActivatedRouterStateSnapshot) => {
const criteria = this.getCriteriaFromRoute(
state.params,
state.queryParams
);
this.search(criteria);
})
);
/**
* This stream is used for the Product Listing and Product Facets.
*
* It not only emits search results, but also performs a search on every change
* of the route (i.e. route params or query params).
*
* When a user leaves the PLP route, the PLP component unsubscribes from this stream
* so no longer the search is performed on route change.
*/
readonly model$: Observable<ProductSearchPage> = using(
() => this.searchByRouting$.subscribe(),
() => this.searchResults$
).pipe(shareReplay({ bufferSize: 1, refCount: true }));
/**
* Expose the `SearchCriteria`. The search criteria are driven by the route parameters.
*
* This search route configuration is not yet configurable
* (see https://github.com/SAP/spartacus/issues/7191).
*/
protected getCriteriaFromRoute(
routeParams: ProductListRouteParams,
queryParams: SearchCriteria
): SearchCriteria {
return {
query: queryParams.query || this.getQueryFromRouteParams(routeParams),
pageSize: queryParams.pageSize || this.defaultPageSize,
currentPage: queryParams.currentPage,
sortCode: queryParams.sortCode,
};
}
/**
* Resolves the search query from the given `ProductListRouteParams`.
*/
protected getQueryFromRouteParams({
query,
categoryCode,
brandCode,
}: ProductListRouteParams) {
if (query) {
return query;
}
if (categoryCode) {
return this.RELEVANCE_ALLCATEGORIES + categoryCode;
}
// TODO: drop support for brands as they should be treated
// similarly as any category.
if (brandCode) {
return this.RELEVANCE_ALLCATEGORIES + brandCode;
}
}
/**
* Performs a search based on the given search criteria.
*
* The search is delegated to the `ProductSearchService`.
*/
protected search(criteria: SearchCriteria): void {
const currentPage = criteria.currentPage;
const pageSize = criteria.pageSize;
const sort = criteria.sortCode;
this.productSearchService.search(
criteria.query,
// TODO: consider dropping this complex passing of cleaned object
Object.assign(
{},
currentPage && { currentPage },
pageSize && { pageSize },
sort && { sort }
)
);
}
/**
* Get items from a given page without using navigation
*/
getPageItems(pageNumber: number): void {
this.routing
.getRouterState()
.subscribe((route) => {
const routeCriteria = this.getCriteriaFromRoute(
route.state.params,
route.state.queryParams
);
const criteria = {
...routeCriteria,
currentPage: pageNumber,
};
this.search(criteria);
})
.unsubscribe();
}
/**
* Sort the search results by the given sort code.
*/
sort(sortCode: string): void {
this.route({ sortCode });
}
/**
* Routes to the next product listing page, using the given `queryParams`. The
* `queryParams` support sorting, pagination and querying.
*
* The `queryParams` are delegated to the Angular router `NavigationExtras`.
*/
protected route(queryParams: SearchCriteria): void {
this.router.navigate([], {
queryParams,
queryParamsHandling: 'merge',
relativeTo: this.activatedRoute,
});
}
/**
* The site context is used to update the search query in case of a
* changing context. The context will typically influence the search data.
*
* We keep this private for now, as we're likely refactoring this in the next
* major version.
*/
private get siteContext(): Observable<string>[] {
// TODO: we should refactor this so that custom context will be taken
// into account automatically. Ideally, we drop the specific context
// from the constructor, and query a ContextService for all contexts.
return [this.languageService.getActive(), this.currencyService.getActive()];
}
}
I call the Sort function in plp.ts as follows.
constructor(
protected productListComponentService: CustomProductListComponentService,
) {}
sortList(sortCode: string): void {
this.productListComponentService.sort(sortCode);
}
Sort process doesn't work. Can you help me? Thank you very much in advance.
You also need to replace "ProductListComponentService" to "CustomProductListComponentService" in "ProductFacetService".
The better way to do this is:
In providers of "ProductListModule", add:
{
provide: ProductListComponentService,
useClass: CustomProductListComponentService,
},
Goal: use Google App Script to get {link:url} and {driveFile:alternativeLink} from student submissions (attachments) to a Google Classroom Assignment.
Issue: While I can get all of the attachments, I cannot filter down to the specific type of attachment or it's respected property. Specific types of attachments return 'undefined'. Any help would be greatly appreciated.
I can get the the desired results using the Classroom API website by adding to the "field" input:
studentSubmissions.assignmentSubmission.attachments.driveFile
https://developers.google.com/classroom/reference/rest/v1/courses.courseWork.studentSubmissions/liststrong text
function testStudSubs(){
console.log(getStudSubs());
}
function getStudSubs(){
const COURSE_ID = "60005382479";
const COURSE_WORK_ID = "141252225149";
const USR_ID = {userId:"105308051639096321984"};
const ID = "Cg0IhMWczB0Q_dCnmo4E";
const submissions = Classroom.Courses.CourseWork.StudentSubmissions.list(COURSE_ID, COURSE_WORK_ID, USR_ID).studentSubmissions
return submissions.map(submission => {
return `${submission.assignmentSubmission.attachments}`
});
}
Answer: (Special thanks to Yagisanatode.com for pointing me in the correct direction.)
1st: ensure proper scopes have been added...see response from Sourabh Choraia stackOverflow response. The scopes will ensure we have access to the objects. Once we request a specific object (ex: link or driveFile), attachments that are not of that object type will display as undefined.
2nd: we need to remove the undefined objects. To do this, we can following w3resource (javascript version), adding the format to our "test" function (w3resource example).
We also need to tweak the array by flattening it. Flattening the array will show the correct length by including the undefined objects.
Finally, for the result, we will map it and pull the desired property (Google Api - Student Submissions List).
Here is working example:
function testStudSubs(){
console.log(getStudSubs());
console.log(getStudSubs().length);
console.log(getStudSubs().flat(2)); // creates separate object for each...ex: 4
const myFlat = getStudSubs().flat(2);
let index = -1;
const arr_length = myFlat ? myFlat.length : 0;
let resIndex = -1;
const result = [];
while (++index < arr_length) {
const value = myFlat[index];
if (value) {
result[++resIndex] = value;
}
}
console.log(result.map(result => { return result.alternateLink + `:` + result.title}));
return result.map(result => { return result.alternateLink + `:` + result.title});
}
/*/////////////////////////////
/
/ Pulls student submitted work from Classroom
/
*//////////////////////////////
function getStudSubs(){
const COURSE_ID = "60005382479"; // update
const COURSE_WORK_ID = "141252225149"; //update
const USR_ID = {userId:"105308051639096321984"}; //update
const submissions = Classroom.Courses.CourseWork.StudentSubmissions.list(COURSE_ID, COURSE_WORK_ID, USR_ID).studentSubmissions
return submissions.map(submission => {
return submission.assignmentSubmission.attachments.map(attachments =>
{
return attachments.driveFile
});
});
return submissions
}
By using Maatwebsite/Laravel-Excel to import excel sheet, here I faced an issue date time column of the excel sheet returns float value. How to solve this?
Example : Consider Cell value "08-04-2016 13:08:29" and returns as "42104.487060185" when import.
This only happend when use chunk. This issue could be solved by:
$UNIX_DATE = ($row->DOB - 25569) * 86400;
$date_column = gmdate("d-m-Y H:i:s", $UNIX_DATE);
Known bug, see https://github.com/Maatwebsite/Laravel-Excel/issues/404 for details.
But basically, when using chunk() to read the cells in, it fails to convert Excel's datetime format from a float into a Carbon date object.
There is currently no fix, You can work around this by calling config before the call to load:
config(['excel.import.dates.columns' => [
'deleted_at',
'updated_at'
]]);
Excel::filter('chunk')->load($file)->chunk(100 function($rows) { ... });
If you're not using the chunk filter, then see http://www.maatwebsite.nl/laravel-excel/docs/import#dates on how to explicty set formats on cells (setDateColumns()), but those should be converting automatically unless you change the defaults.
Change the format your import file to .csv and format the date column to your required date format (dd-mm-yyyy)
That "floating number" is an excel timestamp, that way it stores the date and time data internally.
for example:
123213.0: it's just a date
213233.1233: is a date and time
0.1233: it's one hour
To solve this you must convert that floating point number to a date.
if your need involves resolving datetime fields dynamically, I have written a method that is responsible for automatically detecting if the value is a datetime dynamically (regardless of whether or not you know if there will be a datetime in that column) or I have tried various data types and it works fine
/**
* #param Cell $cell
* #param $value
*
* #return boolean;
*/
public function bindValue(Cell $cell, $value)
{
$formatedCellValue = $this->formatDateTimeCell($value, $datetime_output_format = "d-m-Y H:i:s", $date_output_format = "d-m-Y", $time_output_format = "H:i:s" );
if($formatedCellValue != false){
$cell->setValueExplicit($formatedCellValue, DataType::TYPE_STRING);
return true;
}
// else return default behavior
return parent::bindValue($cell, $value);
}
/**
*
* Convert excel-timestamp to Php-timestamp and again to excel-timestamp to compare both compare
* By Leonardo J. Jauregui ( #Nanod10 | siskit dot com )
*
* #param $value (cell value)
* #param String $datetime_output_format
* #param String $date_output_format
* #param String $time_output_format
*
* #return $formatedCellValue
*/
private function formatDateTimeCell( $value, $datetime_output_format = "Y-m-d H:i:s", $date_output_format = "Y-m-d", $time_output_format = "H:i:s" )
{
// is only time flag
$is_only_time = false;
// Divide Excel-timestamp to know if is Only Date, Only Time or both of them
$excel_datetime_exploded = explode(".", $value);
// if has dot, maybe date has time or is only time
if(strstr($value,".")){
// Excel-timestamp to Php-DateTimeObject
$dateTimeObject = \PhpOffice\PhpSpreadsheet\Shared\Date::excelToDateTimeObject($value);
// if Excel-timestamp > 0 then has Date and Time
if(intval($excel_datetime_exploded[0]) > 0){
// Date and Time
$output_format = $datetime_output_format;
$is_only_time = false;
}else{
// Only time
$output_format = $time_output_format;
$is_only_time = true;
}
}else{
// Only Date
// Excel-timestamp to Php-DateTimeObject
$dateTimeObject = \PhpOffice\PhpSpreadsheet\Shared\Date::excelToDateTimeObject($value);
$output_format = $date_output_format;
$is_only_time = false;
}
// Php-DateTimeObject to Php-timestamp
$phpTimestamp = $dateTimeObject->getTimestamp();
// Php-timestamp to Excel-timestamp
$excelTimestamp = \PhpOffice\PhpSpreadsheet\Shared\Date::PHPToExcel( $phpTimestamp );
// if is only Time
if($is_only_time){
// 01-01-1970 = 25569
// Substract to match PhpToExcel conversion
$excelTimestamp = $excelTimestamp - 25569;
}
/*
// uncoment to debug manualy and see if working
$debug_arr = [
"value"=>$value,
"value_float"=>floatval($value),
"dateTimeObject"=>$dateTimeObject,
"phpTimestamp"=>$phpTimestamp,
"excelTimestamp"=>$excelTimestamp,
"default_date_format"=>$dateTimeObject->format('Y-m-d H:i:s'),
"custom_date_format"=>$dateTimeObject->format($output_format)
];
if($cell->getColumn()=="Q"){
if($cell->getRow()=="2"){
if(floatval($value)===$excelTimestamp){
dd($debug_arr);
}
}
}
*/
// if the values match
if( floatval($value) === $excelTimestamp ){
// is a fucking date! ;)
$formatedCellValue = $dateTimeObject->format($output_format);
return $formatedCellValue;
}else{
// return normal value
return false;
}
}
I am using simple google recaptcha.
My requirement is that if google api is not available (i.e. if google server is down, know its not usual case) means not getting any reply from google server then while loding the form I will hide the google reCaptcha wrapper and while submitting the form I don't want to validate google recaptcha.
Please suggest How can I achieve this.
Google does not provide that data (assuming they are always up).
But you could go about it this way. Dynamically load the script and check for the event existence in the callback. If no event is available then it failed.
Check out the #example comment for usage.
var setAttributes = function (el, attrs) {
/**
* #method simple for in loop to help with creating elements programatically
* #param {object} el - HTMLElement attributes are getting added to
* #param {object} attrs - object literal with key/values for desired attributes
* #example setAttributes(info,{
* 'id' : 'info'
* 'class' : 'my-class-name'
* });
*/
'use strict';
var key;
for (key in attrs) {
if (attrs.hasOwnProperty(key)) {
el.setAttribute(key, attrs[key]);
}
}
return el;
};
var getScript = function (url, fullPath) {
/**
* #method dynamically add script tags to the page.
* #param {url} string with relative path and file name - do not include extension
* #param {fullPath} string with absolute path
* #example getScript('FrameAdjustChild');
* #example getScript('','https://www.google-analytics.com/analytics.js');
*/
'use strict';
var setAtt, PATH = /js/, /* or wherever you keep your scripts */
el = document.createElement('script'),
attrs = {
defer: true,
src: null,
type: 'text/javascript'
};
/** look for a string based, protocol agnostic, js file url */
if (typeof fullPath === 'string' && fullPath.indexOf('http') === 0) {
attrs.src = fullPath;
}
/** look for any string with at least 1 character and prefix our root js dir, then append extension */
if (typeof url === 'string' && url.length >= 1) {
attrs.src = PATH + url + '.js';
}
setAtt = setAttributes(el,attrs);
el.addEventListener('load', function (event) {
if (event) {
/* status is good */
}
else {
/* status is bad */
}
}, false);
document.body.appendChild(el);
return el;
};
I'm running into some strange behavior when using Parse.Query.find() and am hoping someone can show me my errors. My scenario is that I'm including an array field in my query and sometimes, at a random record, some of the included array elements are null. I've verified that the array elements are indeed NOT null. Additionally, if I use each() instead of find(), I don't see this problem. Also, if I reduce the # of records I read at a time (CHUNK_SIZE) from Parse's 1000 maximum to 500, things work, so I'm not sure what's going on.
Here's the code I'm using.
/**
Iterates over a query using find(), which is very fast, compared to each().
Works up to 10,000 records maximum.
#param query Parse.Query to iterate.
#param callback Called for each batch of records.
#return Promise, fulfilled when iteration is done.
*/
function findAll(query, callback) {
var queryCount = 0;
var startTime = new Date();
var CHUNK_SIZE=1000;
query.limit(CHUNK_SIZE);
var queryFind = function() {
query.skip(CHUNK_SIZE * queryCount);
queryCount++;
return query.find().then(function(rows) {
callback(rows);
if (rows.length == CHUNK_SIZE) {
return queryFind();
}
});
}
return queryFind();
}
// Example of how to use findAll.
function fetchTree() {
var records = 0;
var query = new Parse.Query('TreeNode');
query.include('scores');
return findAll(query, function(nodes) {
nodes.forEach(function(node) {
records++;
node.get('scores').forEach(function(score, scoreIndex) {
if (!score) {
throw "Null score at row " + node.id + "/" + records + " index " + scoreIndex;
}
});
});
}, true);
}
fetchTree();
Thanks in advance.
Parse limits rows returned per query to a default of 50 with a max of 1000.
This limit includes related records, so if you get 10 records that each have on average 50 pointers in their array and you include() them you are using 500/1000 max records for your query.