All Downloads are FREE. Search and download functionalities are using the official Maven repository.

template.js.plugins.grid.pivot.js Maven / Gradle / Ivy

There is a newer version: 5.0.6
Show newest version
/**
 * jqGrid pivot functions
 * Copyright (c) 2008-2014, Tony Tomov, [email protected], http://trirand.com/blog/
 * Copyright (c) 2014-2018, Oleg Kiriljuk, [email protected]
 * The modul is created initially by Tony Tomov and it's full rewritten
 * for free jqGrid: https://github.com/free-jqgrid/jqGrid by Oleg Kiriljuk
 * Dual licensed under the MIT and GPL licenses:
 * http://www.opensource.org/licenses/mit-license.php
 * http://www.gnu.org/licenses/gpl-2.0.html
*/

/*jshint eqeqeq:false */
/*global jQuery, define, exports, module, require */
/*jslint eqeq: true, plusplus: true, continue: true, white: true */
(function (factory) {
	"use strict";
	if (typeof define === "function" && define.amd) {
		// AMD. Register as an anonymous module.
		define([
			"jquery",
			"./jquery.fmatter",
			"./grid.grouping"
		], factory);
	} else if (typeof module === "object" && module.exports) {
		// Node/CommonJS
		module.exports = function (root, $) {
			if (!root) {
				root = window;
			}
			if ($ === undefined) {
				// require("jquery") returns a factory that requires window to
				// build a jQuery instance, we normalize how we use modules
				// that require this pattern but the window provided is a noop
				// if it's defined (how jquery works)
				$ = typeof window !== "undefined" ?
						require("jquery") :
						require("jquery")(root);
			}
			require("./jquery.fmatter");
			require("./grid.grouping");
			factory($);
			return $;
		};
	} else {
		// Browser globals
		factory(jQuery);
	}
}(function ($) {
	"use strict";
	var jgrid = $.jgrid;
	// begin module grid.pivot
	function Aggregation(aggregator, context, pivotOptions) {
		if (!(this instanceof Aggregation)) {
			return new Aggregation(aggregator);
		}
		//this.result = undefined;
		//this.count = undefined;
		this.aggregator = aggregator;
		this.finilized = false;
		this.context = context;
		this.pivotOptions = pivotOptions;
	}
	Aggregation.prototype.calc = function (v, fieldName, row, iRow, rows) {
		var self = this;
		if (v !== undefined) {
			self.result = self.result || 0; // change undefined to 0
			v = parseFloat(v);
			switch (self.aggregator) {
				case "sum":
					self.result += v;
					break;
				case "count":
					self.result++;
					break;
				case "avg":
					if (self.finilized) {
						self.count = self.count || 0; // change undefined to 0
						self.result = (self.result * self.count + v) / (self.count + 1);
						self.count++;
					} else {
						self.result += v;
						self.count = self.count || 0; // change undefined to 0
						self.count++;
					}
					break;
				case "min":
					self.result = Math.min(self.result, v);
					break;
				case "max":
					self.result = Math.max(self.result, v);
					break;
				default:
					if ($.isFunction(self.aggregator)) {
						self.result = self.aggregator.call(self.context, {
							previousResult: self.result,
							value: v,
							fieldName: fieldName,
							item: row,
							iItem: iRow,
							items: rows
						});
					}
					break;
			}
		}
	};
	Aggregation.prototype.getResult = function (obj, propName, forceSaving) {
		var self = this;
		if (self.result !== undefined || forceSaving) {
			if (forceSaving) {
				if (self.result !== undefined) {
					self.result = 0;
					self.count = 0;
				}
			}
			if (self.result !== undefined && !self.finilized && self.aggregator === "avg") {
				self.result = self.result / self.count;
				self.finilized = true;
			}
			obj[propName] = self.result;
		}
	};

	function ArrayOfFieldsets(trimByCollect, caseSensitive, skipSort, dimension, fieldName) {
		var iField, dimensionLength = dimension.length, dimensionItem, self = this,
			stringCompare = function (a, b) {
				var a1 = a, b1 = b;
				if (a1 == null) { a1 = ""; } // we will place undefined and null values as the lowest TOGETHER with ""
				if (b1 == null) { b1 = ""; }
				// be sure that we have no other input data (Number, Date and so on)
				a1 = String(a1);
				b1 = String(b1);
				if (!this.caseSensitive) {
					a1 = a1.toUpperCase();
					b1 = b1.toUpperCase();
				}
				if (a1 === b1) {
					if (a === b) {//typeof a === typeof b) {
						return 0;
					}
					// either a or b is undefined or null
					if (a === undefined) { return -1; } // make undefined less as all other
					if (b === undefined) { return 1; }
					if (a === null) { return -1; } // make null less as all other with the exception undefined
					if (b === null) { return 1; }
				}
				if (a1 < b1) {
					return -1;
				}
				return 1;
			},
			numberCompare = function (a, b) {
				a = Number(a);
				b = Number(b);
				if (a === b) {
					return 0;
				}
				if (a < b) {
					return -1;
				}
				return 1;
			},
			integerCompare = function (a, b) {
				a = Math.floor(Number(a));
				b = Math.floor(Number(b));
				if (a === b) {
					return 0;
				}
				if (a < b) {
					return -1;
				}
				return 1;
			};

		self.items = [];
		self.indexesOfSourceData = [];
		self.trimByCollect = trimByCollect;
		self.caseSensitive = caseSensitive;
		self.skipSort = skipSort;
		self.fieldLength = dimensionLength;
		self.fieldNames = new Array(dimensionLength);
		self.fieldSortDirection = new Array(dimensionLength);
		self.fieldCompare = new Array(dimensionLength); // 0 - number, 1 - integer, 2 - string, one can extend for Date and other
		for (iField = 0; iField < dimensionLength; iField++) {
			dimensionItem = dimension[iField];
			self.fieldNames[iField] = dimensionItem[fieldName || "dataName"];
			switch (dimensionItem.sorttype) {
				case "integer":
				case "int":
					self.fieldCompare[iField] = integerCompare;
					break;
				case "number":
				case "currency":
				case "float":
					self.fieldCompare[iField] = numberCompare;
					break;
				default:
					self.fieldCompare[iField] = $.isFunction(dimensionItem.compare) ? dimensionItem.compare : stringCompare;
					break;
			}
			self.fieldSortDirection[iField] = dimensionItem.sortorder === "desc" ? -1 : 1;
		}
	}
	ArrayOfFieldsets.prototype.compareVectorsEx = function (vector1, vector2) {
		var self = this, fieldLength = self.fieldLength, iField, compareResult;
		for (iField = 0; iField < fieldLength; iField++) {
			compareResult = self.fieldCompare[iField](vector1[iField], vector2[iField]);
			if (compareResult !== 0) {
				return {
					index: iField,
					result: compareResult
				};
			}
		}
		return {
			index: -1,
			result: 0
		};
	};
	ArrayOfFieldsets.prototype.getIndexOfDifferences = function (vector1, vector2) {
		if (vector2 === null || vector1 === null) {
			return 0;
		}
		return this.compareVectorsEx(vector1, vector2).index;
	};
	ArrayOfFieldsets.prototype.compareVectors = function (vector1, vector2) {
		var compareRestlts = this.compareVectorsEx(vector1, vector2),
			sortDirection = compareRestlts.index >= 0 ? this.fieldSortDirection[compareRestlts.index] : 1;
		return sortDirection > 0 ? compareRestlts.result : -compareRestlts.result;
	};
	ArrayOfFieldsets.prototype.getItem = function (index) {
		return this.items[index];
	};
	ArrayOfFieldsets.prototype.getIndexLength = function () {
		return this.items.length;
	};
	ArrayOfFieldsets.prototype.getIndexesOfSourceData = function (index) {
		return this.indexesOfSourceData[index];
	};
	ArrayOfFieldsets.prototype.createDataIndex = function (data) {
		var self = this, iRow, nRows = data.length, fieldLength = self.fieldLength, values, v,
			fieldNames = self.fieldNames, indexesOfSourceData = self.indexesOfSourceData, iField, compareResult, i, item,
			items = self.items, iMin, iMax;

		for (iRow = 0; iRow < nRows; iRow++) {
			item = data[iRow];

			// build the set of fields with data of the current item
			values = new Array(fieldLength);
			for (iField = 0; iField < fieldLength; iField++) {
				v = item[fieldNames[iField]];
				if (v !== undefined) {
					if (typeof v === "string" && self.trimByCollect) {
						v = $.trim(v);
					}
					values[iField] = v;
				}
			}

			// compare values with items having index iMax and iMin
			// If we use skipSort:true option then we compare always
			// with iMax item only.
			iMin = 0;
			iMax = items.length - 1;
			if (iMax < 0) {
				items.push(values);
				indexesOfSourceData.push([iRow]);
				continue;
			}
			compareResult = self.compareVectors(values, items[iMax]);
			if (compareResult === 0) {
				indexesOfSourceData[iMax].push(iRow);
				continue;
			}
			if (compareResult === 1 || self.skipSort) {
				// in case of the empty array this.items or if the values is larger as the
				// the max (last) element of this.items: append values to the array this.items
				items.push(values);
				indexesOfSourceData.push([iRow]);
				continue;
			}
			compareResult = self.compareVectors(items[0], values);
			if (compareResult === 1) {
				// if the min (first) element values is larger as the values:
				// insert the values as the first element of the array this.items
				items.unshift(values);
				indexesOfSourceData.unshift([iRow]);
				continue;
			}
			if (compareResult === 0) {
				indexesOfSourceData[0].push(iRow);
				continue;
			}
			// we are sure that items[iMin] < values < items[iMax]
			while (true) {
				if (iMax - iMin < 2) {
					// no identical items are found we need to insert the item at i index
					items.splice(iMax, 0, values); // insert after iMin
					indexesOfSourceData.splice(iMax, 0, [iRow]);
					break;
				}
				i = Math.floor((iMin + iMax) / 2); // | 0 means Math.floor, but it's faster sometimes.
				compareResult = self.compareVectors(items[i], values);
				if (compareResult === 0) {
					indexesOfSourceData[i].push(iRow);
					break;
				}
				if (compareResult === 1) {
					iMax = i;
				} else {
					iMin = i;
				}
			}
		}
	};

	jgrid.extend({
		pivotSetup: function (data, options) {
			// data should come in json format
			// The function return the new colModel and the transformed data
			// again with group setup options which then will be passed to the grid
			var self = this[0], isArray = $.isArray, summaries = {},
				groupingView = {
					groupField: [],
					groupSummary: [],
					groupSummaryPos: []
				},
				groupOptions = {
					grouping: true,
					groupingView: groupingView
				},
				o = $.extend({
					totals: false, // replacement for rowTotals. totalText and totalHeader can be used additionally
					useColSpanStyle: false,
					trimByCollect: true,
					skipSortByX: false,
					skipSortByY: false,
					caseSensitive: false,
					footerTotals: false, // replacement colTotals. footerAggregator option and totalText properties of xDimension[i] can be used additionally
					groupSummary: true,
					groupSummaryPos: "header",
					frozenStaticCols: false,
					defaultFormatting: true,
					data: data
				}, options || {}),
				row, i, k, nRows = data.length, x, y, cm, iRow, cmName, iXData, itemXData, pivotInfos, rows,
				xDimension = o.xDimension, yDimension = o.yDimension, aggregates = o.aggregates, aggrContext,
				isRowTotal = o.totalText || o.totals || o.rowTotals || o.totalHeader, aggrTotal, gi,
				xlen = isArray(xDimension) ? xDimension.length : 0,
				ylen = isArray(yDimension) ? yDimension.length : 0,
				aggrlen = isArray(aggregates) ? aggregates.length : 0,
				headerLevels = ylen - (aggrlen === 1 ? 1 : 0),
				colHeaders = [], hasGroupTotal = [], colModel = [], outputItems = [],
				additionalProperties = ["pivotInfos"],
				aggrContextTotalRows = new Array(aggrlen), aggrContextGroupTotalRows = new Array(ylen),
				xIndexLength, indexesOfDataWithTheSameXValues, iYData, itemYData, indexesOfDataWithTheSameYValues,
				iRows, agr, outputItem, previousY, groupHeaders, iRowsY, xIndex, yIndex, yIndexLength,
				indexDataBy = function (dimension, skipSort, compareVectors) {
					var index = new ArrayOfFieldsets(o.trimByCollect, o.caseSensitive, skipSort, dimension);
					if ($.isFunction(compareVectors)) {
						index.compareVectorsEx = compareVectors;
					}
					index.createDataIndex(data);
					return index;
				},
				buildColModelItem = function (colType, agr1, iAggr, level, iyData) {
					var label, name, cmItem;
					switch (colType) {
						case 1: // total group
							label = yDimension[level].totalText || "{0} {1} {2}";
							name = "y" + iyData + "t" + level;
							break;
						case 2: // grand total
							label = o.totalText || "{0}";
							name = "t";
							break;
						//case 0: // standard column
						default:
							label = aggrlen > 1 ?
									agr1.label || "{0}" :
									($.isFunction(yDimension[level].label) ?
										yDimension[level].label :
										yIndex.getItem(iyData)[level]);
							name = "y" + iyData;
							break;
					}
					cmItem = $.extend({}, agr1, {
						name: name + (aggrlen > 1 ? "a" + iAggr : ""),
						label: $.isFunction(label) ?
									(label.call(self, colType === 2 ?
											{ aggregate: agr1, iAggregate: iAggr, pivotOptions: o } :
											(colType === 1 ?
												{ yIndex: yIndex.getItem(iyData), aggregate: agr1, iAggregate: iAggr, yLevel: level, pivotOptions: o } :
												{ yData: yIndex.getItem(iyData)[level], yIndex: yIndex.getItem(iyData), yLevel: level, pivotOptions: o }))) :
									(jgrid.template.apply(self, colType === 2 ?
											[String(label), agr1.aggregator, agr1.member, iAggr] :
											[String(label), agr1.aggregator, agr1.member, yIndex.getItem(iyData)[level], level]))
					});
					delete cmItem.member;
					delete cmItem.aggregator;
					return cmItem;
				},
				addColumnToColModel = function (colType, level, iyData) {
					var iAggr, aggregate;
					for (iAggr = 0; iAggr < aggrlen; iAggr++) {
						aggregate = aggregates[iAggr];
						if (aggregate.template === undefined && aggregate.formatter === undefined && o.defaultFormatting) {
							aggregate.template = aggregate.aggregator === "count" ? "integer" : "number";
						}
						colModel.push(buildColModelItem(colType, aggregate, iAggr, level, iyData));
					}
				},
				addGroupTotalHeaders = function (iyData, level, previousY1) {
					var iLevel, j, totalHeader, headerOnTop;
					for (iLevel = headerLevels - 1; iLevel >= level; iLevel--) {
						if (hasGroupTotal[iLevel]) {
							for (j = 0; j <= iLevel; j++) {
								groupHeaders = colHeaders[j].groupHeaders;
								groupHeaders[groupHeaders.length - 1].numberOfColumns += aggrlen;
							}
							y = yDimension[iLevel];
							totalHeader = y.totalHeader;
							headerOnTop = y.headerOnTop;
							for (j = iLevel + 1; j <= headerLevels - 1; j++) {
								colHeaders[j].groupHeaders.push({
									titleText: ((headerOnTop && j === iLevel + 1) || (!headerOnTop && j === headerLevels - 1)) ?
											($.isFunction(totalHeader) ?
													totalHeader.call(self, previousY1, iLevel) :
													jgrid.template.call(self, String(totalHeader || ""), previousY1[iLevel], iLevel)) :
											"",
									startColumnName: "y" + (iyData - 1) + "t" + iLevel + (aggrlen === 1 ? "" : "a0"),
									numberOfColumns: aggrlen
								});
							}
						}
					}
				},
				createTotalAggregation = function (iAggr) {
					var aggrGroup = new Aggregation(aggregates[iAggr].aggregator === "count" ? "sum" : aggregates[iAggr].aggregator, self, options);
					aggrGroup.groupInfo = { iRows: [], rows: [], ys: [], iYs: [] };
					return aggrGroup;
				},
				initializeGroupTotals = function () {
					var iLevel, iAggr;
					for (iLevel = headerLevels - 1; iLevel >= 0; iLevel--) {
						if (hasGroupTotal[iLevel]) {
							if (aggrContextGroupTotalRows[iLevel] == null) {// first call
								aggrContextGroupTotalRows[iLevel] = new Array(aggrlen);
							}
							for (iAggr = 0; iAggr < aggrlen; iAggr++) {
								aggrContextGroupTotalRows[iLevel][iAggr] = createTotalAggregation(iAggr);
							}
						}
					}
				},
				finalizeGroupTotals = function (iyData, itemYData1, previousY1, iAggr) {
					var iLevel, level = yIndex.getIndexOfDifferences(itemYData1, previousY1), fieldName, aggrGroup;

					if (previousY1 !== null) {
						// test whether the group is finished and one need to get results
						level = Math.max(level, 0); // change -1 to 0 for the last call (itemYData === previousY)
						for (iLevel = headerLevels - 1; iLevel >= level; iLevel--) {
							fieldName = "y" + iyData + "t" + iLevel + (aggrlen > 1 ? "a" + iAggr : "");
							if (hasGroupTotal[iLevel] && outputItem[fieldName] === undefined) {
								aggrGroup = aggrContextGroupTotalRows[iLevel][iAggr];
								aggrGroup.getResult(outputItem, fieldName);
								outputItem.pivotInfos[fieldName] = {
									colType: 1,
									iA: iAggr,
									a: aggregates[iAggr],
									level: iLevel,
									iRows: aggrGroup.groupInfo.iRows,
									rows: aggrGroup.groupInfo.rows,
									ys: aggrGroup.groupInfo.ys,
									iYs: aggrGroup.groupInfo.iYs
								};
								if (itemYData1 !== previousY1) {
									aggrContextGroupTotalRows[iLevel][iAggr] = createTotalAggregation(iAggr);
								}
							}
						}
					}
				},
				calculateGroupTotals = function (itemYData1, previousY1, aggregate, iAggr, row1, iRow1, iyData) {
					// the method will be called at the first time with previousY === null in every output row
					// and finally with itemYData === previousY for getting results of all aggregation contexts
					var iLevel, aggrGroup, groupInfo;

					if (itemYData1 !== previousY1) { // not the last call in the row
						for (iLevel = headerLevels - 1; iLevel >= 0; iLevel--) {
							if (hasGroupTotal[iLevel]) {
								aggrGroup = aggrContextGroupTotalRows[iLevel][iAggr];
								aggrGroup.calc(row1[aggregate.member], aggregate.member, row1, iRow1, data);
								groupInfo = aggrGroup.groupInfo;
								if ($.inArray(iyData, groupInfo.iYs) < 0) {
									groupInfo.iYs.push(iyData);
									groupInfo.ys.push(itemYData1);
								}
								if ($.inArray(iRow1, groupInfo.iRows) < 0) {
									groupInfo.iRows.push(iRow1);
									groupInfo.rows.push(row1);
								}
							}
						}
					}
				};

			if (xlen === 0 || aggrlen === 0) {
				throw ("xDimension or aggregates options are not set!");
			}

			// ****************************************************************
			// The step 1: scan input data and build the list of unique indexes
			// ****************************************************************
			xIndex = indexDataBy(xDimension, o.skipSortByX, o.compareVectorsByX);
			yIndex = indexDataBy(yDimension, o.skipSortByY, o.compareVectorsByY);

			// save to be used probably later
			options.xIndex = xIndex;
			options.yIndex = yIndex;

			// *******************************************
			// The step 2: build colModel and groupOptions
			// *******************************************
			// fill the first xlen columns of colModel and fill the groupOptions
			// the names of the first columns will be "x"+i. The first column have the name "x0".
			for (i = 0; i < xlen; i++) {
				x = xDimension[i];
				cm = {
					name: "x" + i,
					label: x.label != null ?
								($.isFunction(x.label) ? x.label.call(self, x, i, o) : x.label) :
								x.dataName,
					frozen: o.frozenStaticCols
				};
				if (i < xlen - 1 && !x.skipGrouping && !x.additionalProperty) {
					// based on xDimension levels build grouping
					groupingView.groupField.push(cm.name);
					groupingView.groupSummary.push(o.groupSummary);
					groupingView.groupSummaryPos.push(o.groupSummaryPos);
				}
				cm = $.extend(cm, x);
				delete cm.dataName;
				delete cm.footerText;
				if (!x.additionalProperty) {
					colModel.push(cm);
					groupOptions.sortname = cm.name;
				} else {
					additionalProperties.push(cm.name);
				}
			}
			if (xlen < 2) {
				groupOptions.grouping = false; // no grouping is needed
			}
			groupingView.hideFirstGroupCol = true;

			// Fill hasGroupTotal and groupColumnsPerLevel arrays
			// The hasGroupTotal just shows whether one need create additional totals for every group.
			for (i = 0; i < ylen; i++) {
				y = yDimension[i];
				hasGroupTotal.push(y.totals || y.rowTotals || y.totalText || y.totalHeader ? true : false);
			}

			// fill other columns of colModel based on collected uniqueYData and aggregates options
			// the names of the first columns will be "y"+i in case of one aggregate and
			// "y"+i+"a"+k in case of multiple aggregates. The name of the first "y"-column is "y0" or "y0a0"
			// The next function build and insert item in colModel
			// colType: 0 - means standard column, 1 - total group, 2 - grand total
			previousY = yIndex.getItem(0);
			addColumnToColModel(0, ylen - 1, 0); // add standard column
			yIndexLength = yIndex.getIndexLength();
			for (iYData = 1; iYData < yIndexLength; iYData++) {
				itemYData = yIndex.getItem(iYData);
				/*
				 * find where (on which level) the itemYData have the differences to
				 * the previous y (previousY). If the level has (totals:true/rowTotals:true) in yDimension
				 * then one should insert new total columns for all levels starting with the highest one
				 * (yDimension[yDimension.length-1]) and till the current one.
				 */
				i = yIndex.getIndexOfDifferences(itemYData, previousY);
				for (k = headerLevels - 1; k >= i; k--) {
					if (hasGroupTotal[k]) {
						addColumnToColModel(1, k, iYData - 1); // add group total columns
					}
				}
				previousY = itemYData;
				addColumnToColModel(0, ylen - 1, iYData); // add standard column
			}
			// finalize of all totals
			for (i = headerLevels - 1; i >= 0; i--) {
				if (hasGroupTotal[i]) {
					addColumnToColModel(1, i, yIndexLength - 1); // add the last group total columns
				}
			}
			// add total columns calculated over all data of the row
			if (isRowTotal) {
				addColumnToColModel(2);
			}

			// ********************************
			// The step 3: build column headers
			// ********************************
			// initialize colHeaders
			previousY = yIndex.getItem(0);
			for (k = 0; k < headerLevels; k++) {
				colHeaders.push({
					useColSpanStyle: o.useColSpanStyle,
					groupHeaders: [{
						titleText: ($.isFunction(yDimension[k].label) ?
										yDimension[k].label.call(self, { yData: previousY[k], yIndex: previousY, yLevel: k, pivotOptions: o }) :
										previousY[k]),
						startColumnName: aggrlen === 1 ? "y0" : "y0a0",
						numberOfColumns: aggrlen
					}]
				});
			}
			for (iYData = 1; iYData < yIndexLength; iYData++) {
				itemYData = yIndex.getItem(iYData);
				i = yIndex.getIndexOfDifferences(itemYData, previousY);
				// We placed QNIQUE data in uniqueYData array.
				// So we always find a difference on one level

				addGroupTotalHeaders(iYData, i, previousY);
				// add column headers which corresponds the main data
				for (k = headerLevels - 1; k >= i; k--) {
					colHeaders[k].groupHeaders.push({
						titleText: ($.isFunction(yDimension[k].label) ?
										yDimension[k].label.call(self, { yData: itemYData[k], yIndex: itemYData, yLevel: k, pivotOptions: o }) :
										itemYData[k]),
						startColumnName: "y" + iYData + (aggrlen === 1 ? "" : "a0"),
						numberOfColumns: aggrlen
					});
				}
				for (k = 0; k < i; k++) {
					groupHeaders = colHeaders[k].groupHeaders;
					groupHeaders[groupHeaders.length - 1].numberOfColumns += aggrlen;
				}
				previousY = itemYData;
			}
			addGroupTotalHeaders(yIndexLength, 0, previousY);

			// fill groupHeaders without taking in consideration group total columns
			if (isRowTotal) {
				for (i = 0; i < headerLevels; i++) {
					colHeaders[i].groupHeaders.push({
						titleText: (i < headerLevels - 1 ? "" : o.totalHeader || ""),
						startColumnName: "t" + (aggrlen === 1 ? "" : "a0"),
						numberOfColumns: aggrlen
					});
				}
			}

			// *****************************
			// The step 4: fill data of grid
			// *****************************
			xIndexLength = xIndex.getIndexLength();
			for (iXData = 0; iXData < xIndexLength; iXData++) {
				itemXData = xIndex.getItem(iXData);
				pivotInfos = { iX: iXData, x: itemXData };
				outputItem = { pivotInfos: pivotInfos }; // item of output data
				// itemXData corresponds to the row of output data
				for (i = 0; i < xlen; i++) {
					// fill first columns of data
					outputItem["x" + i] = itemXData[i];
				}

				indexesOfDataWithTheSameXValues = xIndex.getIndexesOfSourceData(iXData);
				// The rows of input data with indexes from indexesOfDataWithTheSameXValues contains itemXData
				// Now we build columns of itemXData row
				if (isRowTotal) {
					for (k = 0; k < aggrlen; k++) {
						aggrContextTotalRows[k] = createTotalAggregation(k);
					}
				}
				previousY = null;
				initializeGroupTotals();
				for (iYData = 0; iYData < yIndexLength; iYData++) {
					itemYData = yIndex.getItem(iYData);
					indexesOfDataWithTheSameYValues = yIndex.getIndexesOfSourceData(iYData);
					// we calculate aggregate in every itemYData
					for (k = 0; k < aggrlen; k++) {
						if (previousY !== null) { // empty input data
							finalizeGroupTotals(iYData - 1, itemYData, previousY, k);
						}
						iRows = [];
						for (i = 0; i < indexesOfDataWithTheSameYValues.length; i++) {
							iRowsY = indexesOfDataWithTheSameYValues[i];
							if ($.inArray(iRowsY, indexesOfDataWithTheSameXValues) >= 0) {
								iRows.push(iRowsY);
							}
						}
						if (iRows.length > 0) {
							// iRows array have all indexes of input data which have both itemXData and itemYData
							// We need calculate aggregate agr over all the items
							rows = new Array(iRows.length);
							agr = aggregates[k];
							aggrContext = new Aggregation(agr.aggregator, self, options);
							for (iRow = 0; iRow < iRows.length; iRow++) {
								i = iRows[iRow];
								row = data[i];
								rows[iRow] = row;
								aggrContext.calc(row[agr.member], agr.member, row, i, data);
								if (isRowTotal) {
									aggrTotal = aggrContextTotalRows[k];
									aggrTotal.calc(row[agr.member], agr.member, row, i, data);
									gi = aggrTotal.groupInfo;
									if ($.inArray(i, gi.iYs) < 0) {
										gi.iYs.push(iYData);
										gi.ys.push(itemYData);
									}
									if ($.inArray(i, gi.iRows) < 0) {
										gi.iRows.push(i);
										gi.rows.push(row);
									}
								}
								calculateGroupTotals(itemYData, previousY, agr, k, row, i, iYData);
							}
							cmName = "y" + iYData + (aggrlen === 1 ? "" : "a" + k);
							aggrContext.getResult(outputItem, cmName);
							pivotInfos[cmName] = {
								colType: 0, // standard row
								iY: iYData,
								y: itemYData,
								iA: k,
								a: agr,
								iRows: iRows,
								rows: rows
							};
						}
					}
					previousY = itemYData;
				}
				if (previousY !== null) { // if non-empty input data
					for (k = 0; k < aggrlen; k++) {
						finalizeGroupTotals(yIndexLength - 1, previousY, previousY, k);
					}
				}
				if (isRowTotal) {
					for (k = 0; k < aggrlen; k++) {
						cmName = "t" + (aggrlen === 1 ? "" : "a" + k);
						aggrTotal = aggrContextTotalRows[k];
						aggrTotal.getResult(outputItem, cmName);
						gi = aggrTotal.groupInfo;
						pivotInfos[cmName] = {
							colType: 2, // row total
							iA: k,
							a: aggregates[k],
							iRows: gi.iRows,
							rows: gi.rows,
							iYs: gi.iYs,
							ys: gi.ys
						};
					}
				}
				outputItems.push(outputItem);
			}

			// *****************************
			// The step 5: fill total footer
			// *****************************
			if (o.footerTotals || o.colTotals) {
				nRows = outputItems.length;
				for (i = 0; i < xlen; i++) {
					summaries["x" + i] = xDimension[i].footerText || "";
				}
				for (i = xlen; i < colModel.length; i++) {
					cmName = colModel[i].name;
					aggrContext = new Aggregation(o.footerAggregator || "sum", self, options);
					for (iRow = 0; iRow < nRows; iRow++) {
						outputItem = outputItems[iRow];
						aggrContext.calc(outputItem[cmName], cmName, outputItem, iRow, outputItems);
					}
					aggrContext.getResult(summaries, cmName);
				}
			}

			// return the final result.
			options.colHeaders = colHeaders;
			return {
				colModel: colModel,
				additionalProperties: additionalProperties,
				options: options,
				rows: outputItems,
				groupOptions: groupOptions,
				groupHeaders: colHeaders,
				summary: summaries
			};
		},
		jqPivot: function (data, pivotOpt, gridOpt, ajaxOpt) {
			return this.each(function () {
				var $t = this, $self = $($t), $j = $.fn.jqGrid;

				function pivot() {
					var pivotGrid = $j.pivotSetup.call($self, data, pivotOpt),
						gHead = pivotGrid.groupHeaders,
						assocArraySize = function (obj) {
							// http://stackoverflow.com/a/6700/11236
							var size = 0, key;
							for (key in obj) {
								if (obj.hasOwnProperty(key)) {
									size++;
								}
							}
							return size;
						},
						footerrow = assocArraySize(pivotGrid.summary) > 0 ? true : false,
						groupingView = pivotGrid.groupOptions.groupingView,
						query = jgrid.from.call($t, pivotGrid.rows), i;
					if (!pivotOpt.skipSortByX) {
						for (i = 0; i < groupingView.groupField.length; i++) {
							query.orderBy(groupingView.groupField[i],
								gridOpt != null && gridOpt.groupingView && gridOpt.groupingView.groupOrder != null && gridOpt.groupingView.groupOrder[i] === "desc" ? "d" : "a",
								"text",
								"");
						}
					}
					pivotOpt.data = data;
					$j.call($self, $.extend(true, {
						datastr: $.extend(query.select(), footerrow ? { userdata: pivotGrid.summary } : {}),
						datatype: "jsonstring",
						footerrow: footerrow,
						userDataOnFooter: footerrow,
						colModel: pivotGrid.colModel,
						additionalProperties: pivotGrid.additionalProperties,
						pivotOptions: pivotGrid.options,
						viewrecords: true,
						sortname: pivotOpt.xDimension[0].dataName // ?????
					}, pivotGrid.groupOptions, gridOpt || {}));
					if (gHead.length) {
						for (i = 0; i < gHead.length; i++) {
							// Multiple calls of setGroupHeaders for one grid are wrong,
							// but there are produces good results in case of usage
							// useColSpanStyle: false option. The rowspan values
							// needed be increased in case of usage useColSpanStyle: true
							if (gHead[i] && gHead[i].groupHeaders.length) {
								$j.setGroupHeaders.call($self, gHead[i]);
							}
						}
					}
					if (pivotOpt.frozenStaticCols) {
						$j.setFrozenColumns.call($self);
					}
				}

				if (typeof data === "string") {
					$.ajax($.extend({
						url: data,
						dataType: "json",
						success: function (data1) {
							data = jgrid.getAccessor(data1, ajaxOpt && ajaxOpt.reader ? ajaxOpt.reader : "rows");
							pivot();
						}
					}, ajaxOpt || {}));
				} else {
					pivot();
				}
			});
		}
	});
	// end module grid.pivot
}));




© 2015 - 2025 Weber Informatics LLC | Privacy Policy