subtotal
Version:
Subtotal.js is a JavaScript plugin for PivotTable.js. It renders subtotals of rows and columns with the ability to expand and collapse rows.
696 lines (608 loc) • 35 kB
text/coffeescript
callWithJQuery = (pivotModule) ->
if typeof exports is "object" and typeof module is "object" # CommonJS
pivotModule require("jquery")
else if typeof define is "function" and define.amd # AMD
define ["jquery"], pivotModule
# Plain browser env
else
pivotModule jQuery
callWithJQuery ($) ->
class SubtotalPivotData extends $.pivotUtilities.PivotData
constructor: (input, opts) ->
super input, opts
processKey = (record, totals, keys, attrs, getAggregator) ->
key = []
addKey = false
for attr in attrs
key.push record[attr] ? "null"
flatKey = key.join String.fromCharCode(0)
if not totals[flatKey]
totals[flatKey] = getAggregator key.slice()
addKey = true
totals[flatKey].push record
keys.push key if addKey
return key
processRecord: (record) -> #this code is called in a tight loop
rowKey = []
colKey = []
@allTotal.push record
rowKey = processKey record, @rowTotals, @rowKeys, @rowAttrs, (key) =>
return @aggregator this, key, []
colKey = processKey record, @colTotals, @colKeys, @colAttrs, (key) =>
return @aggregator this, [], key
m = rowKey.length-1
n = colKey.length-1
return if m < 0 or n < 0
for i in [0..m]
fRowKey = rowKey.slice(0, i+1)
flatRowKey = fRowKey.join String.fromCharCode(0)
@tree[flatRowKey] = {} if not @tree[flatRowKey]
for j in [0..n]
fColKey = colKey.slice 0, j+1
flatColKey = fColKey.join String.fromCharCode(0)
@tree[flatRowKey][flatColKey] = @aggregator this, fRowKey, fColKey if not @tree[flatRowKey][flatColKey]
@tree[flatRowKey][flatColKey].push record
$.pivotUtilities.SubtotalPivotData = SubtotalPivotData
SubtotalRenderer = (pivotData, opts) ->
defaults =
table: clickCallback: null
localeStrings: totals: "Totals", subtotalOf: "Subtotal of"
arrowCollapsed: "\u25B6"
arrowExpanded: "\u25E2"
rowSubtotalDisplay:
displayOnTop: true
disableFrom: 99999
collapseAt: 99999
hideOnExpand: false
disableExpandCollapse: false
colSubtotalDisplay:
displayOnTop: true
disableFrom: 99999
collapseAt: 99999
hideOnExpand: false
disableExpandCollapse: false
opts = $.extend true, {}, defaults, opts
opts.rowSubtotalDisplay.disableFrom = 0 if opts.rowSubtotalDisplay.disableSubtotal
opts.rowSubtotalDisplay.disableFrom = opts.rowSubtotalDisplay.disableAfter+1 if typeof opts.rowSubtotalDisplay.disableAfter isnt 'undefined' and opts.rowSubtotalDisplay.disableAfter isnt null
opts.rowSubtotalDisplay.collapseAt = opts.collapseRowsAt if typeof opts.rowSubtotalDisplay.collapseAt isnt 'undefined' and opts.collapseRowsAt isnt null
opts.colSubtotalDisplay.disableFrom = 0 if opts.colSubtotalDisplay.disableSubtotal
opts.colSubtotalDisplay.disableFrom = opts.colSubtotalDisplay.disableAfter+1 if typeof opts.colSubtotalDisplay.disableAfter isnt 'undefined' and opts.colSubtotalDisplay.disableAfter isnt null
opts.colSubtotalDisplay.collapseAt = opts.collapseColsAt if typeof opts.colSubtotalDisplay.collapseAt isnt 'undefined' and opts.collapseColsAt isnt null
colAttrs = pivotData.colAttrs
rowAttrs = pivotData.rowAttrs
rowKeys = pivotData.getRowKeys()
colKeys = pivotData.getColKeys()
tree = pivotData.tree
rowTotals = pivotData.rowTotals
colTotals = pivotData.colTotals
allTotal = pivotData.allTotal
classRowHide = "rowhide"
classRowShow = "rowshow"
classColHide = "colhide"
classColShow = "colshow"
clickStatusExpanded = "expanded"
clickStatusCollapsed = "collapsed"
classExpanded = "expanded"
classCollapsed = "collapsed"
classRowExpanded = "rowexpanded"
classRowCollapsed = "rowcollapsed"
classColExpanded = "colexpanded"
classColCollapsed = "colcollapsed"
arrowExpanded = opts.arrowExpanded
arrowCollapsed = opts.arrowCollapsed
# Based on http://stackoverflow.com/questions/195951/change-an-elements-class-with-javascript -- Begin
hasClass = (element, className) ->
regExp = new RegExp "(?:^|\\s)" + className + "(?!\\S)", "g"
element.className.match(regExp) isnt null
removeClass = (element, className) ->
for name in className.split " "
regExp = new RegExp "(?:^|\\s)" + name + "(?!\\S)", "g"
element.className = element.className.replace regExp, ''
addClass = (element, className) ->
for name in className.split " "
element.className += (" " + name) if not hasClass element, name
replaceClass = (element, replaceClassName, byClassName) ->
removeClass element, replaceClassName
addClass element, byClassName
# Based on http://stackoverflow.com/questions/195951/change-an-elements-class-with-javascript -- End
createElement = (elementType, className, textContent, attributes, eventHandlers) ->
e = document.createElement elementType
e.className = className if className?
e.textContent = textContent if textContent?
e.setAttribute attr, val for own attr, val of attributes if attributes?
e.addEventListener event, handler for own event, handler of eventHandlers if eventHandlers?
return e
setAttributes = (e, attrs) ->
e.setAttribute a, v for own a, v of attrs
processKeys = (keysArr, className, opts) ->
lastIdx = keysArr[0].length-1
headers = children: []
row = 0
keysArr.reduce(
(val0, k0) =>
col = 0
k0.reduce(
(acc, curVal, curIdx, arr) =>
if not acc[curVal]
key = k0.slice 0, col+1
acc[curVal] =
row: row
col: col
descendants: 0
children: []
text: curVal
key: key
flatKey: key.join String.fromCharCode(0)
firstLeaf: null
leaves: 0
parent: if col isnt 0 then acc else null
th: createElement "th", className, curVal
childrenSpan: 0
acc.children.push curVal
if col > 0
acc.descendants++
col++
if curIdx == lastIdx
node = headers
for i in [0..lastIdx-1] when lastIdx > 0
node[k0[i]].leaves++
if not node[k0[i]].firstLeaf
node[k0[i]].firstLeaf = acc[curVal]
node = node[k0[i]]
return headers
return acc[curVal]
headers)
row++
return headers
headers)
return headers
buildAxisHeader = (axisHeaders, col, attrs, opts) ->
ah =
text: attrs[col]
expandedCount: 0
expandables: 0
attrHeaders: []
clickStatus: clickStatusExpanded
onClick: collapseAxis
arrow = "#{arrowExpanded} "
hClass = classExpanded
if col >= opts.collapseAt
arrow = "#{arrowCollapsed} "
hClass = classCollapsed
ah.clickStatus = clickStatusCollapsed
ah.onClick = expandAxis
if col == attrs.length-1 or col >= opts.disableFrom or opts.disableExpandCollapse
arrow = ""
ah.th = createElement "th", "pvtAxisLabel #{hClass}", "#{arrow}#{ah.text}"
if col < attrs.length-1 and col < opts.disableFrom and not opts.disableExpandCollapse
ah.th.onclick = (event) ->
event = event || window.event
ah.onClick axisHeaders, col, attrs, opts
axisHeaders.ah.push ah
return ah
buildColAxisHeaders = (thead, rowAttrs, colAttrs, opts) ->
axisHeaders =
collapseAttrHeader: collapseCol
expandAttrHeader: expandCol
ah: []
for attr, col in colAttrs
ah = buildAxisHeader axisHeaders, col, colAttrs, opts.colSubtotalDisplay
ah.tr = createElement "tr"
ah.tr.appendChild createElement "th", null, null, {colspan: rowAttrs.length, rowspan: colAttrs.length} if col is 0 and rowAttrs.length isnt 0
ah.tr.appendChild ah.th
thead.appendChild ah.tr
return axisHeaders
buildRowAxisHeaders = (thead, rowAttrs, colAttrs, opts) ->
axisHeaders =
collapseAttrHeader: collapseRow
expandAttrHeader: expandRow
ah: []
tr: createElement "tr"
for col in [0..rowAttrs.length-1]
ah = buildAxisHeader axisHeaders, col, rowAttrs, opts.rowSubtotalDisplay
axisHeaders.tr.appendChild ah.th
if colAttrs.length != 0
th = createElement "th"
axisHeaders.tr.appendChild th
thead.appendChild axisHeaders.tr
return axisHeaders
getHeaderText = (h, attrs, opts) ->
arrow = " #{arrowExpanded} "
arrow = "" if h.col == attrs.length-1 or h.col >= opts.disableFrom or opts.disableExpandCollapse or h.children.length is 0
return "#{arrow}#{h.text}"
buildColHeader = (axisHeaders, attrHeaders, h, rowAttrs, colAttrs, node, opts) ->
# DF Recurse
buildColHeader axisHeaders, attrHeaders, h[chKey], rowAttrs, colAttrs, node, opts for chKey in h.children
# Process
ah = axisHeaders.ah[h.col]
ah.attrHeaders.push h
h.node = node.counter
h.onClick = collapseCol
addClass h.th, "#{classColShow} col#{h.row} colcol#{h.col} #{classColExpanded}"
h.th.setAttribute "data-colnode", h.node
h.th.colSpan = h.childrenSpan if h.children.length isnt 0
h.th.rowSpan = 2 if h.children.length is 0 and rowAttrs.length isnt 0
h.th.textContent = getHeaderText h, colAttrs, opts.colSubtotalDisplay
if h.children.length isnt 0 and h.col < opts.colSubtotalDisplay.disableFrom
ah.expandables++
ah.expandedCount += 1
h.th.colSpan++ if not opts.colSubtotalDisplay.hideOnExpand
if not opts.colSubtotalDisplay.disableExpandCollapse
h.th.onclick = (event) ->
event = event || window.event
h.onClick axisHeaders, h, opts.colSubtotalDisplay
h.sTh = createElement "th", "pvtColLabelFiller #{classColShow} col#{h.row} colcol#{h.col} #{classColExpanded}"
h.sTh.setAttribute "data-colnode", h.node
h.sTh.rowSpan = colAttrs.length-h.col
replaceClass h.sTh, classColShow, classColHide if opts.colSubtotalDisplay.hideOnExpand
h[h.children[0]].tr.appendChild h.sTh
h.parent?.childrenSpan += h.th.colSpan
h.clickStatus = clickStatusExpanded
ah.tr.appendChild h.th
h.tr = ah.tr
attrHeaders.push h
node.counter++
buildRowTotalsHeader = (tr, rowAttrs, colAttrs) ->
th = createElement "th", "pvtTotalLabel rowTotal", opts.localeStrings.totals,
rowspan: if colAttrs.length is 0 then 1 else colAttrs.length + (if rowAttrs.length is 0 then 0 else 1)
tr.appendChild th
buildRowHeader = (tbody, axisHeaders, attrHeaders, h, rowAttrs, colAttrs, node, opts) ->
# DF Recurse
buildRowHeader tbody, axisHeaders, attrHeaders, h[chKey], rowAttrs, colAttrs, node, opts for chKey in h.children
# Process
ah = axisHeaders.ah[h.col]
ah.attrHeaders.push h
h.node = node.counter
h.onClick = collapseRow
firstChild = h[h.children[0]] if h.children.length isnt 0
addClass h.th, "#{classRowShow} row#{h.row} rowcol#{h.col} #{classRowExpanded}"
h.th.setAttribute "data-rownode", h.node
h.th.colSpan = 2 if h.col is rowAttrs.length-1 and colAttrs.length isnt 0
h.th.rowSpan = h.childrenSpan if h.children.length isnt 0
h.th.textContent = getHeaderText h, rowAttrs, opts.rowSubtotalDisplay
h.tr = createElement "tr", "row#{h.row}"
h.tr.appendChild h.th
if h.children.length is 0
tbody.appendChild h.tr
else
tbody.insertBefore h.tr, firstChild.tr
if h.children.length isnt 0 and h.col < opts.rowSubtotalDisplay.disableFrom
++ah.expandedCount
++ah.expandables
if not opts.rowSubtotalDisplay.disableExpandCollapse
h.th.onclick = (event) ->
event = event || window.event
h.onClick axisHeaders, h, opts.rowSubtotalDisplay
h.sTh = createElement "th", "pvtRowLabelFiller row#{h.row} rowcol#{h.col} #{classRowExpanded} #{classRowShow}"
replaceClass h.sTh, classRowShow, classRowHide if opts.rowSubtotalDisplay.hideOnExpand
h.sTh.setAttribute "data-rownode", h.node
h.sTh.colSpan = rowAttrs.length-(h.col+1) + if colAttrs.length != 0 then 1 else 0
if opts.rowSubtotalDisplay.displayOnTop
h.tr.appendChild h.sTh
else
h.th.rowSpan += 1 # if not opts.rowSubtotalDisplay.hideOnExpand
h.sTr = createElement "tr", "row#{h.row}"
h.sTr.appendChild h.sTh
tbody.appendChild h.sTr
h.th.rowSpan++ if h.children.length isnt 0
h.parent?.childrenSpan += h.th.rowSpan
h.clickStatus = clickStatusExpanded
attrHeaders.push h
node.counter++
getTableEventHandlers = (value, rowKey, colKey, rowAttrs, colAttrs, opts) ->
return if not opts.table?.eventHandlers
eventHandlers = {}
for own event, handler of opts.table.eventHandlers
filters = {}
filters[attr] = colKey[i] for own i, attr of colAttrs when colKey[i]?
filters[attr] = rowKey[i] for own i, attr of rowAttrs when rowKey[i]?
eventHandlers[event] = (e) -> handler(e, value, filters, pivotData)
return eventHandlers
buildValues = (tbody, colAttrHeaders, rowAttrHeaders, rowAttrs, colAttrs, opts) ->
for rh in rowAttrHeaders when rh.col is rowAttrs.length-1 or (rh.children.length isnt 0 and rh.col < opts.rowSubtotalDisplay.disableFrom)
rCls = "pvtVal row#{rh.row} rowcol#{rh.col} #{classRowExpanded}"
if rh.children.length > 0
rCls += " pvtRowSubtotal"
rCls += if opts.rowSubtotalDisplay.hideOnExpand then " #{classRowHide}" else " #{classRowShow}"
else
rCls += " #{classRowShow}"
tr = if rh.sTr then rh.sTr else rh.tr
for ch in colAttrHeaders when ch.col is colAttrs.length-1 or (ch.children.length isnt 0 and ch.col < opts.colSubtotalDisplay.disableFrom)
aggregator = tree[rh.flatKey][ch.flatKey] ? {value: (-> null), format: -> ""}
val = aggregator.value()
cls = " #{rCls} col#{ch.row} colcol#{ch.col} #{classColExpanded}"
if ch.children.length > 0
cls += " pvtColSubtotal"
cls += if opts.colSubtotalDisplay.hideOnExpand then " #{classColHide}" else " #{classColShow}"
else
cls += " #{classColShow}"
td = createElement "td", cls, aggregator.format(val),
"data-value": val
"data-rownode": rh.node
"data-colnode": ch.node,
getTableEventHandlers val, rh.key, ch.key, rowAttrs, colAttrs, opts
tr.appendChild td
# buildRowTotal
totalAggregator = rowTotals[rh.flatKey]
val = totalAggregator.value()
td = createElement "td", "pvtTotal rowTotal #{rCls}", totalAggregator.format(val),
"data-value": val
"data-row": "row#{rh.row}"
"data-rowcol": "col#{rh.col}"
"data-rownode": rh.node,
getTableEventHandlers val, rh.key, [], rowAttrs, colAttrs, opts
tr.appendChild td
buildColTotalsHeader = (rowAttrs, colAttrs) ->
tr = createElement "tr"
colspan = rowAttrs.length + (if colAttrs.length == 0 then 0 else 1)
th = createElement "th", "pvtTotalLabel colTotal", opts.localeStrings.totals, {colspan: colspan}
tr.appendChild th
return tr
buildColTotals = (tr, attrHeaders, rowAttrs, colAttrs, opts) ->
for h in attrHeaders when h.col is colAttrs.length-1 or (h.children.length isnt 0 and h.col < opts.colSubtotalDisplay.disableFrom)
clsNames = "pvtVal pvtTotal colTotal #{classColExpanded} col#{h.row} colcol#{h.col}"
if h.children.length isnt 0
clsNames += " pvtColSubtotal"
clsNames += if opts.colSubtotalDisplay.hideOnExpand then " #{classColHide}" else " #{classColShow}"
else
clsNames += " #{classColShow}"
totalAggregator = colTotals[h.flatKey]
val = totalAggregator.value()
td = createElement "td", clsNames, totalAggregator.format(val),
"data-value": val
"data-for": "col#{h.col}"
"data-colnode": "#{h.node}",
getTableEventHandlers val, [], h.key, rowAttrs, colAttrs, opts
tr.appendChild td
buildGrandTotal = (tbody, tr, rowAttrs, colAttrs, opts) ->
totalAggregator = allTotal
val = totalAggregator.value()
td = createElement "td", "pvtGrandTotal", totalAggregator.format(val),
{"data-value": val},
getTableEventHandlers val, [], [], rowAttrs, colAttrs, opts
tr.appendChild td
tbody.appendChild tr
collapseAxisHeaders = (axisHeaders, col, opts) ->
collapsible = Math.min axisHeaders.ah.length-2, opts.disableFrom-1
return if col > collapsible
for i in [col..collapsible]
ah = axisHeaders.ah[i]
replaceClass ah.th, classExpanded, classCollapsed
ah.th.textContent = " #{arrowCollapsed} #{ah.text}"
ah.clickStatus = clickStatusCollapsed
ah.onClick = expandAxis
adjustAxisHeader = (axisHeaders, col, opts) ->
ah = axisHeaders.ah[col]
if ah.expandedCount is 0
collapseAxisHeaders axisHeaders, col, opts
else if ah.expandedCount is ah.expandables
replaceClass ah.th, classCollapsed, classExpanded
ah.th.textContent = " #{arrowExpanded} #{ah.text}"
ah.clickStatus = clickStatusExpanded
ah.onClick = collapseAxis
hideChildCol = (ch) ->
$(ch.th).closest 'table.pvtTable'
.find "tbody tr td[data-colnode=\"#{ch.node}\"], th[data-colnode=\"#{ch.node}\"]"
.removeClass classColShow
.addClass classColHide
collapseHiddenColSubtotal = (h, opts) ->
$(h.th).closest 'table.pvtTable'
.find "tbody tr td[data-colnode=\"#{h.node}\"], th[data-colnode=\"#{h.node}\"]"
.removeClass classColExpanded
.addClass classColCollapsed
h.th.textContent = " #{arrowCollapsed} #{h.text}" if h.children.length isnt 0
h.th.colSpan = 1
collapseShowColSubtotal = (h, opts) ->
$(h.th).closest 'table.pvtTable'
.find "tbody tr td[data-colnode=\"#{h.node}\"], th[data-colnode=\"#{h.node}\"]"
.removeClass classColExpanded
.addClass classColCollapsed
.removeClass classColHide
.addClass classColShow
h.th.textContent = " #{arrowCollapsed} #{h.text}" if h.children.length isnt 0
h.th.colSpan = 1
collapseChildCol = (ch, h) ->
collapseChildCol ch[chKey], h for chKey in ch.children when hasClass ch[chKey].th, classColShow
hideChildCol ch
collapseCol = (axisHeaders, h, opts) ->
colSpan = h.th.colSpan - 1
collapseChildCol h[chKey], h for chKey in h.children when hasClass h[chKey].th, classColShow
if h.col < opts.disableFrom
if hasClass h.th, classColHide
collapseHiddenColSubtotal h, opts
else
collapseShowColSubtotal h, opts
p = h.parent
while p
p.th.colSpan -= colSpan
p = p.parent
h.clickStatus = clickStatusCollapsed
h.onClick = expandCol
axisHeaders.ah[h.col].expandedCount--
adjustAxisHeader axisHeaders, h.col, opts
showChildCol = (ch) ->
$(ch.th).closest 'table.pvtTable'
.find "tbody tr td[data-colnode=\"#{ch.node}\"], th[data-colnode=\"#{ch.node}\"]"
.removeClass classColHide
.addClass classColShow
expandHideColSubtotal = (h) ->
$(h.th).closest 'table.pvtTable'
.find "tbody tr td[data-colnode=\"#{h.node}\"], th[data-colnode=\"#{h.node}\"]"
.removeClass "#{classColCollapsed} #{classColShow}"
.addClass "#{classColExpanded} #{classColHide}"
replaceClass h.th, classColHide, classColShow
h.th.textContent = " #{arrowExpanded} #{h.text}"
expandShowColSubtotal = (h) ->
$(h.th).closest 'table.pvtTable'
.find "tbody tr td[data-colnode=\"#{h.node}\"], th[data-colnode=\"#{h.node}\"]"
.removeClass "#{classColCollapsed} #{classColHide}"
.addClass "#{classColExpanded} #{classColShow}"
h.th.colSpan++
h.th.textContent = " #{arrowExpanded} #{h.text}"
expandChildCol = (ch, opts) ->
if ch.children.length isnt 0 and opts.hideOnExpand and ch.clickStatus is clickStatusExpanded
replaceClass ch.th, classColHide, classColShow
else
showChildCol ch
if ch.sTh and ch.clickStatus is clickStatusExpanded and opts.hideOnExpand
replaceClass ch.sTh, classColShow, classColHide
expandChildCol ch[chKey], opts for chKey in ch.children if (ch.clickStatus is clickStatusExpanded or ch.col >= opts.disableFrom)
expandCol = (axisHeaders, h, opts) ->
if h.clickStatus is clickStatusExpanded
adjustAxisHeader axisHeaders, h.col, opts
return
colSpan = 0
for chKey in h.children
ch = h[chKey]
expandChildCol ch, opts
colSpan += ch.th.colSpan
h.th.colSpan = colSpan
if h.col < opts.disableFrom
if opts.hideOnExpand
expandHideColSubtotal h
--colSpan
else
expandShowColSubtotal h
p = h.parent
while p
p.th.colSpan += colSpan
p = p.parent
h.clickStatus = clickStatusExpanded
h.onClick = collapseCol
axisHeaders.ah[h.col].expandedCount++
adjustAxisHeader axisHeaders, h.col, opts
hideChildRow = (ch, opts) ->
replaceClass cell, classRowShow, classRowHide for cell in ch.tr.querySelectorAll "th, td"
replaceClass cell, classRowShow, classRowHide for cell in ch.sTr.querySelectorAll "th, td" if ch.sTr
collapseShowRowSubtotal = (h, opts) ->
h.th.textContent = " #{arrowCollapsed} #{h.text}"
for cell in h.tr.querySelectorAll "th, td"
removeClass cell, "#{classRowExpanded} #{classRowHide}"
addClass cell, "#{classRowCollapsed} #{classRowShow}"
if h.sTr
for cell in h.sTr.querySelectorAll "th, td"
removeClass cell, "#{classRowExpanded} #{classRowHide}"
addClass cell, "#{classRowCollapsed} #{classRowShow}"
collapseChildRow = (ch, h, opts) ->
collapseChildRow ch[chKey], h, opts for chKey in ch.children
hideChildRow ch, opts
collapseRow = (axisHeaders, h, opts) ->
collapseChildRow h[chKey], h, opts for chKey in h.children
collapseShowRowSubtotal h, opts
h.clickStatus = clickStatusCollapsed
h.onClick = expandRow
axisHeaders.ah[h.col].expandedCount--
adjustAxisHeader axisHeaders, h.col, opts
showChildRow = (ch, opts) ->
replaceClass cell, classRowHide, classRowShow for cell in ch.tr.querySelectorAll "th, td"
replaceClass cell, classRowHide, classRowShow for cell in ch.sTr.querySelectorAll "th, td" if ch.sTr
expandShowRowSubtotal = (h, opts) ->
h.th.textContent = " #{arrowExpanded} #{h.text}"
for cell in h.tr.querySelectorAll "th, td"
removeClass cell, "#{classRowCollapsed} #{classRowHide}"
addClass cell, "#{classRowExpanded} #{classRowShow}"
if h.sTr
for cell in h.sTr.querySelectorAll "th, td"
removeClass cell, "#{classRowCollapsed} #{classRowHide}"
addClass cell, "#{classRowExpanded} #{classRowShow}"
expandHideRowSubtotal = (h, opts) ->
h.th.textContent = " #{arrowExpanded} #{h.text}"
for cell in h.tr.querySelectorAll "th, td"
removeClass cell, "#{classRowCollapsed} #{classRowShow}"
addClass cell, "#{classRowExpanded} #{classRowHide}"
removeClass h.th, "#{classRowCollapsed} #{classRowHide}"
addClass cell, "#{classRowExpanded} #{classRowShow}"
if h.sTr
for cell in h.sTr.querySelectorAll "th, td"
removeClass cell, "#{classRowCollapsed} #{classRowShow}"
addClass cell, "#{classRowExpanded} #{classRowHide}"
expandChildRow = (ch, opts) ->
if ch.children.length isnt 0 and opts.hideOnExpand and ch.clickStatus is clickStatusExpanded
replaceClass ch.th, classRowHide, classRowShow
else
showChildRow ch, opts
if ch.sTh and ch.clickStatus is clickStatusExpanded and opts.hideOnExpand
replaceClass ch.sTh, classRowShow, classRowHide
expandChildRow ch[chKey], opts for chKey in ch.children if (ch.clickStatus is clickStatusExpanded or ch.col >= opts.disableFrom)
expandRow = (axisHeaders, h, opts) ->
if h.clickStatus is clickStatusExpanded
adjustAxisHeader axisHeaders, h.col, opts
return
for chKey in h.children
ch = h[chKey]
expandChildRow ch, opts
if h.children.length isnt 0
if opts.hideOnExpand
expandHideRowSubtotal h, opts
else
expandShowRowSubtotal h, opts
h.clickStatus = clickStatusExpanded
h.onClick = collapseRow
axisHeaders.ah[h.col].expandedCount++
adjustAxisHeader axisHeaders, h.col, opts
collapseAxis = (axisHeaders, col, attrs, opts) ->
collapsible = Math.min attrs.length-2, opts.disableFrom-1
return if col > collapsible
axisHeaders.collapseAttrHeader axisHeaders, h, opts for h in axisHeaders.ah[i].attrHeaders when h.clickStatus is clickStatusExpanded and h.children.length isnt 0 for i in [collapsible..col] by -1
expandAxis = (axisHeaders, col, attrs, opts) ->
ah = axisHeaders.ah[col]
axisHeaders.expandAttrHeader axisHeaders, h, opts for h in axisHeaders.ah[i].attrHeaders for i in [0..col]
# when h.clickStatus is clickStatusCollapsed and h.children.length isnt 0 for i in [0..col]
main = (rowAttrs, rowKeys, colAttrs, colKeys) ->
rowAttrHeaders = []
colAttrHeaders = []
colKeyHeaders = processKeys colKeys, "pvtColLabel" if colAttrs.length isnt 0 and colKeys.length isnt 0
rowKeyHeaders = processKeys rowKeys, "pvtRowLabel" if rowAttrs.length isnt 0 and rowKeys.length isnt 0
result = createElement "table", "pvtTable", null, {style: "display: none;"}
thead = createElement "thead"
result.appendChild thead
if colAttrs.length isnt 0
colAxisHeaders = buildColAxisHeaders thead, rowAttrs, colAttrs, opts
node = counter: 0
buildColHeader colAxisHeaders, colAttrHeaders, colKeyHeaders[chKey], rowAttrs, colAttrs, node, opts for chKey in colKeyHeaders.children
buildRowTotalsHeader colAxisHeaders.ah[0].tr, rowAttrs, colAttrs
tbody = createElement "tbody"
result.appendChild tbody
if rowAttrs.length isnt 0
rowAxisHeaders = buildRowAxisHeaders thead, rowAttrs, colAttrs, opts
buildRowTotalsHeader rowAxisHeaders.tr, rowAttrs, colAttrs if colAttrs.length is 0
node = counter: 0
buildRowHeader tbody, rowAxisHeaders, rowAttrHeaders, rowKeyHeaders[chKey], rowAttrs, colAttrs, node, opts for chKey in rowKeyHeaders.children
buildValues tbody, colAttrHeaders, rowAttrHeaders, rowAttrs, colAttrs, opts
tr = buildColTotalsHeader rowAttrs, colAttrs
buildColTotals tr, colAttrHeaders, rowAttrs, colAttrs, opts if colAttrs.length > 0
buildGrandTotal tbody, tr, rowAttrs, colAttrs, opts
collapseAxis colAxisHeaders, opts.colSubtotalDisplay.collapseAt, colAttrs, opts.colSubtotalDisplay
collapseAxis rowAxisHeaders, opts.rowSubtotalDisplay.collapseAt, rowAttrs, opts.rowSubtotalDisplay
result.setAttribute "data-numrows", rowKeys.length
result.setAttribute "data-numcols", colKeys.length
result.style.display = ""
return result
return main rowAttrs, rowKeys, colAttrs, colKeys
$.pivotUtilities.subtotal_renderers =
"Table With Subtotal": (pvtData, opts) -> SubtotalRenderer pvtData, opts
"Table With Subtotal Bar Chart": (pvtData, opts) -> $(SubtotalRenderer pvtData, opts).barchart()
"Table With Subtotal Heatmap": (pvtData, opts) -> $(SubtotalRenderer pvtData, opts).heatmap "heatmap", opts
"Table With Subtotal Row Heatmap": (pvtData, opts) -> $(SubtotalRenderer pvtData, opts).heatmap "rowheatmap", opts
"Table With Subtotal Col Heatmap": (pvtData, opts) -> $(SubtotalRenderer pvtData, opts).heatmap "colheatmap", opts
#
# Aggregators
#
usFmtPct = $.pivotUtilities.numberFormat digitsAfterDecimal:1, scaler: 100, suffix: "%"
aggregatorTemplates = $.pivotUtilities.aggregatorTemplates;
subtotalAggregatorTemplates =
fractionOf: (wrapped, type="row", formatter=usFmtPct) -> (x...) -> (data, rowKey, colKey) ->
rowKey = [] if typeof rowKey is "undefined"
colKey = [] if typeof colKey is "undefined"
selector: {row: [rowKey.slice(0, -1),[]], col: [[], colKey.slice(0, -1)]}[type]
inner: wrapped(x...)(data, rowKey, colKey)
push: (record) -> @inner.push record
format: formatter
value: -> @inner.value() / data.getAggregator(@selector...).inner.value()
numInputs: wrapped(x...)().numInputs
$.pivotUtilities.subtotalAggregatorTemplates = subtotalAggregatorTemplates
$.pivotUtilities.subtotal_aggregators = do (tpl = aggregatorTemplates, sTpl = subtotalAggregatorTemplates) ->
"Sum As Fraction Of Parent Row": sTpl.fractionOf(tpl.sum(), "row", usFmtPct)
"Sum As Fraction Of Parent Column": sTpl.fractionOf(tpl.sum(), "col", usFmtPct)
"Count As Fraction Of Parent Row": sTpl.fractionOf(tpl.count(), "row", usFmtPct)
"Count As Fraction Of Parent Column": sTpl.fractionOf(tpl.count(), "col", usFmtPct)